summaryrefslogtreecommitdiffstats
path: root/dmi-service
diff options
context:
space:
mode:
Diffstat (limited to 'dmi-service')
-rw-r--r--dmi-service/openapi/components.yml324
-rw-r--r--dmi-service/openapi/openapi-datajob.yml323
-rw-r--r--dmi-service/openapi/openapi.yml177
-rw-r--r--dmi-service/pom.xml660
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/Application.java33
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java86
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiPluginConfig.java51
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/WebSecurityConfig.java105
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfig.java142
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestController.java92
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CloudEventConstructionException.java37
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CmHandleRegistrationException.java36
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiException.java59
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java74
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/HttpClientRequestException.java44
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/InvalidDatastoreException.java32
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java38
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModulesNotFoundException.java38
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/SdncException.java49
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutor.java121
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventCreator.java90
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventProducer.java47
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcCloudEventCreator.java104
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventProducer.java50
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventSimulationController.java61
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java108
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapper.java62
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/model/CmNotificationSubscriptionStatus.java32
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapper.java62
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java253
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/handlers/DatastoreType.java65
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java95
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java196
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/YangResourceExtractor.java59
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java64
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java87
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CmHandleOperation.java35
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CreatedCmHandle.java36
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleReference.java37
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchema.java36
-rw-r--r--dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java270
-rw-r--r--dmi-service/src/main/resources/application.yml128
-rw-r--r--dmi-service/src/main/resources/logback-spring.xml75
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/api/kafka/MessagingBaseSpec.groovy79
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy66
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiPluginConfigSpec.groovy52
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfigSpec.groovy62
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestControllerSpec.groovy94
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy110
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/avc/AvcEventExecutorIntegrationSpec.groovy61
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy149
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapperSpec.groovy69
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapperSpec.groovy53
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/ControllerSecuritySpec.groovy76
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy406
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy273
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/YangResourceExtractorSpec.groovy98
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy57
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy102
-rw-r--r--dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy176
-rw-r--r--dmi-service/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java54
-rw-r--r--dmi-service/src/test/java/org/onap/cps/ncmp/dmi/rest/controller/TestController.java35
-rw-r--r--dmi-service/src/test/resources/ModuleSchema.json15
-rw-r--r--dmi-service/src/test/resources/application.yml79
-rw-r--r--dmi-service/src/test/resources/cmNotificationSubscriptionCreationEvent.json43
-rw-r--r--dmi-service/src/test/resources/createDataWithNormalChar.json8
-rw-r--r--dmi-service/src/test/resources/createDataWithSpecialChar.json8
-rw-r--r--dmi-service/src/test/resources/deleteData.json8
-rw-r--r--dmi-service/src/test/resources/moduleResources.json18
-rw-r--r--dmi-service/src/test/resources/patchData.json8
-rw-r--r--dmi-service/src/test/resources/readData.json9
-rw-r--r--dmi-service/src/test/resources/updateData.json8
72 files changed, 6849 insertions, 0 deletions
diff --git a/dmi-service/openapi/components.yml b/dmi-service/openapi/components.yml
new file mode 100644
index 00000000..4a6d1729
--- /dev/null
+++ b/dmi-service/openapi/components.yml
@@ -0,0 +1,324 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2021-2023 Nordix Foundation
+# Modifications Copyright (C) 2022 Bell Canada
+# ================================================================================
+# 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=========================================================
+
+components:
+ securitySchemes:
+ basicAuth:
+ type: http
+ scheme: basic
+ schemas:
+ ErrorMessage:
+ type: object
+ title: Error
+ properties:
+ status:
+ type: string
+ message:
+ type: string
+ details:
+ type: string
+
+ CmHandles:
+ type: object
+ properties:
+ cmHandles:
+ type: array
+ example: ["cmHandleId1","cmHandleId2","cmHandleId3"]
+ items:
+ type: string
+
+ ModuleReferencesRequest:
+ type: object
+ properties:
+ moduleSetTag:
+ type: string
+ cmHandleProperties:
+ $ref: '#/components/schemas/cmHandleProperties'
+
+ ResourceDataOperationRequests:
+ type: array
+ items:
+ type: object
+ title: 'DataOperationRequest'
+ properties:
+ operation:
+ type: string
+ example: 'read'
+ operationId:
+ description: 'it is recommended that the operationId is unique within the scope of the request'
+ type: string
+ example: '12'
+ datastore:
+ type: string
+ example: 'ncmp-datastore:passthrough-operational'
+ options:
+ type: string
+ example: 'some option'
+ resourceIdentifier:
+ type: string
+ example: 'some resource identifier'
+ cmHandles:
+ type: array
+ items:
+ $ref: '#/components/schemas/dmiOperationCmHandle'
+ required:
+ - operation
+ - operationId
+ - datastore
+ - cmHandles
+
+ dmiOperationCmHandle:
+ type: object
+ title: 'CmHandle with properties for DMI'
+ properties:
+ id:
+ type: string
+ cmHandleProperties:
+ additionalProperties:
+ type: string
+ moduleSetTag:
+ type: string
+ example: module-set-tag1
+ example:
+ id: cmHandle123
+ cmHandleProperties:
+ myProp: some value
+ otherProp: other value
+ moduleSetTag: module-set-tag1
+
+ ModuleResourcesReadRequest:
+ type: object
+ properties:
+ moduleSetTag:
+ type: string
+ description: Module set tag of the given cm handle
+ example: Module-set-tag-1
+ required: false
+ data:
+ type: object
+ properties:
+ modules:
+ type: array
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ example: my-name
+ revision:
+ type: string
+ example: my-revision
+ cmHandleProperties:
+ $ref: '#/components/schemas/cmHandleProperties'
+
+ ModuleSet:
+ type: object
+ properties:
+ schemas:
+ type: array
+ items:
+ type: object
+ properties:
+ moduleName:
+ type: string
+ example: my-module-name
+ revision:
+ type: string
+ example: my-revision
+ namespace:
+ type: string
+ example: my-namespace
+
+ YangResources:
+ type: array
+ items:
+ $ref: '#/components/schemas/YangResource'
+
+ YangResource:
+ properties:
+ yangSource:
+ type: string
+ example: my-yang-source
+ moduleName:
+ type: string
+ example: my-module-name
+ revision:
+ type: string
+ example: my-revision
+
+ DataAccessRequest:
+ type: object
+ properties:
+ operation:
+ type: string
+ enum: [ read, create, update, patch, delete ]
+ example: read
+ dataType:
+ type: string
+ example: my-data-type
+ data:
+ type: string
+ example: my-data
+ cmHandleProperties:
+ $ref: '#/components/schemas/cmHandleProperties'
+ requestId:
+ type: string
+ example: 3a9ce55c-e365-4dc9-8da3-a06f07cbc6d7
+ moduleSetTag:
+ type: string
+ example: module-set-tag1
+
+ cmHandleProperties:
+ type: object
+ nullable: true
+ additionalProperties:
+ type: string
+ example: {"prop1":"value1","prop2":"value2"}
+
+ responses:
+ NoContent:
+ description: No Content
+ content: {}
+
+ BadRequest:
+ description: Bad Request
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: 400
+ message: Bad Request
+ details: The provided request is not valid
+
+ NotFound:
+ description: The specified resource was not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: 404
+ message: Resource Not Found
+ details: The requested resource is not found
+
+ ServerError:
+ description: Internal Server Error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: 500
+ message: Internal Server Error
+ details: Internal Server Error occured
+
+ NotImplemented:
+ description: Not Implemented
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: 501
+ message: Not Implemented
+ details: Method Not Implemented
+
+ parameters:
+ cmHandleInPath:
+ name: cmHandle
+ in: path
+ description: The identifier for a network function, network element, subnetwork, or any other cm object by managed Network CM Proxy
+ required: true
+ schema:
+ type: string
+ example: my-cm-handle
+
+ resourceIdentifierInQuery:
+ name: resourceIdentifier
+ in: query
+ description: Resource identifier to get/set the resource data
+ required: true
+ schema:
+ type: string
+ example: my-schema:my-node
+
+ optionsParamInQuery:
+ name: options
+ in: query
+ description: options parameter in query, it is mandatory to wrap key(s)=value(s) in parenthesis'()'.
+ required: false
+ schema:
+ type: string
+ examples:
+ sample1:
+ value:
+ options: (key1=value1,key2=value2)
+ sample2:
+ value:
+ options: (key1=value1,key2=value1/value2)
+ sample3:
+ value:
+ options: (key1=10,key2=value2,key3=val31,val32)
+
+ topicParamInQuery:
+ name: topic
+ in: query
+ description: topic name passed from client(NCMP).
+ required: false
+ schema:
+ type: string
+ examples:
+ sample1:
+ value: my-topic-name
+
+ requiredTopicParamInQuery:
+ name: topic
+ in: query
+ description: mandatory topic name passed from client(NCMP).
+ required: true
+ schema:
+ type: string
+ examples:
+ sample1:
+ value:
+ topic: my-topic-name
+
+ requiredRequestIdParamInQuery:
+ name: requestId
+ in: query
+ description: request Id generated by NCMP and sent as an acknowledgement for the client request the same including here.
+ required: true
+ schema:
+ type: string
+ examples:
+ sample1:
+ value: 4753fc1f-7de2-449a-b306-a6204b5370b3
+
+ datastoreName:
+ name: datastore-name
+ in: path
+ description: The type of the requested data
+ required: true
+ schema:
+ type: string
+ example: ncmp-datastore:passthrough-operational or ncmp-datastore:passthrough-running
+
+security:
+ - basicAuth: []
diff --git a/dmi-service/openapi/openapi-datajob.yml b/dmi-service/openapi/openapi-datajob.yml
new file mode 100644
index 00000000..aa93623c
--- /dev/null
+++ b/dmi-service/openapi/openapi-datajob.yml
@@ -0,0 +1,323 @@
+# ============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/readJob/{requestId}:
+ post:
+ description: Create a read request
+ operationId: readDataJob
+ parameters:
+ - $ref: '#/components/parameters/requestIdInPath'
+ requestBody:
+ description: Operation body
+ content:
+ application/3gpp-json-patch+json:
+ schema:
+ $ref: '#/components/schemas/SubjobReadRequest'
+ tags:
+ - dmi-datajob
+ responses:
+ "501":
+ $ref: '#/components/responses/NotImplemented'
+ /v1/writeJob/{requestId}:
+ post:
+ description: Create a write request
+ operationId: writeDataJob
+ parameters:
+ - $ref: '#/components/parameters/requestIdInPath'
+ requestBody:
+ description: Operation body
+ content:
+ application/3gpp-json-patch+json:
+ schema:
+ $ref: '#/components/schemas/SubjobWriteRequest'
+ tags:
+ - dmi-datajob
+ responses:
+ "501":
+ $ref: '#/components/responses/NotImplemented'
+ /v1/dataJob/{requestId}/dataProducerJob/{dataProducerJobId}/status:
+ get:
+ description: Retrieve the status of a specific data job.
+ operationId: getDataJobStatus
+ parameters:
+ - $ref: '#/components/parameters/requestIdInPath'
+ - $ref: '#/components/parameters/dataProducerJobIdInPath'
+ - $ref: '#/components/parameters/dataProducerIdInQuery'
+ tags:
+ - dmi-datajob
+ responses:
+ "501":
+ $ref: '#/components/responses/NotImplemented'
+ /v1/dataJob/{requestId}/dataProducerJob/{dataProducerJobId}/result:
+ get:
+ description: Retrieve the result of a data job.
+ operationId: getDataJobResult
+ parameters:
+ - $ref: '#/components/parameters/requestIdInPath'
+ - $ref: '#/components/parameters/dataProducerJobIdInPath'
+ - $ref: '#/components/parameters/dataProducerIdInQuery'
+ - $ref: '#/components/parameters/destinationInQuery'
+ tags:
+ - dmi-datajob
+ responses:
+ "501":
+ $ref: '#/components/responses/NotImplemented'
+
+components:
+ parameters:
+ requestIdInPath:
+ description: Identifier for the overall Datajob
+ in: path
+ name: requestId
+ required: true
+ schema:
+ example: some-identifier
+ type: string
+ dataProducerJobIdInPath:
+ description: Identifier for the data producer job
+ in: path
+ name: dataProducerJobId
+ required: true
+ schema:
+ example: some-producer-job-identifier
+ type: string
+ dataProducerIdInQuery:
+ name: dataProducerId
+ in: query
+ description: Identifier for the data producer
+ required: true
+ schema:
+ type: string
+ example: some-data-producer-identifier
+ destinationInQuery:
+ name: destination
+ in: query
+ description: The destination of the results (Kafka topic name or s3 bucket name)
+ required: true
+ schema:
+ type: string
+ example: some-destination
+ schemas:
+ ErrorMessage:
+ type: object
+ title: Error
+ properties:
+ status:
+ type: string
+ message:
+ type: string
+ details:
+ type: string
+ SubjobReadRequest:
+ 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:
+ 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'
+ SubjobWriteRequest:
+ 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:
+ 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
+ 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'
+ 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
+ SubjobWriteResponse:
+ type: object
+ required:
+ - subJobId
+ - dmiServiceName
+ - dataProducerId
+ properties:
+ subJobId:
+ description: Unique identifier for the sub-job
+ example: my-sub-job-id
+ type: string
+ dmiServiceName:
+ description: Name of the relevant DMI Service
+ example: my-dmi-service
+ 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
+ responses:
+ NotImplemented:
+ description: Not Implemented
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorMessage'
+ example:
+ status: 501
+ message: Not Implemented
+ details: Method Not Implemented
diff --git a/dmi-service/openapi/openapi.yml b/dmi-service/openapi/openapi.yml
new file mode 100644
index 00000000..6dbc19f3
--- /dev/null
+++ b/dmi-service/openapi/openapi.yml
@@ -0,0 +1,177 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2021-2023 Nordix Foundation
+# Modifications Copyright (C) 2022 Bell Canada
+# ================================================================================
+# 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.1
+info:
+ title: NCMP DMI Plugin
+ description: Adds Data Model Inventory Registry capability for ONAP
+ version: "1.0.0"
+servers:
+ - url: /dmi
+tags:
+ - name: dmi-plugin-internal
+ description: DMI plugin internal rest apis
+ - name: dmi-plugin
+ description: DMI plugin rest apis
+
+
+paths:
+ /v1/ch/{cmHandle}/modules:
+ post:
+ tags:
+ - dmi-plugin
+ summary: Get all modules for cm handle
+ description: Get all modules for given cm handle
+ operationId: getModuleReferences
+ parameters:
+ - $ref: 'components.yml#/components/parameters/cmHandleInPath'
+ requestBody:
+ description: Operational body
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yml#/components/schemas/ModuleReferencesRequest'
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yml#/components/schemas/ModuleSet'
+ '404':
+ $ref: 'components.yml#/components/responses/NotFound'
+ '500':
+ $ref: 'components.yml#/components/responses/ServerError'
+
+
+ /v1/ch/{cmHandle}/moduleResources:
+ post:
+ description: Retrieve module resources for one or more modules
+ tags:
+ - dmi-plugin
+ summary: Retrieve module resources
+ operationId: retrieveModuleResources
+ parameters:
+ - $ref: 'components.yml#/components/parameters/cmHandleInPath'
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yml#/components/schemas/ModuleResourcesReadRequest'
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yml#/components/schemas/YangResources'
+ '404':
+ $ref: 'components.yml#/components/responses/NotFound'
+ '500':
+ $ref: 'components.yml#/components/responses/ServerError'
+
+ /v1/inventory/cmHandles:
+ post:
+ tags:
+ - dmi-plugin-internal
+ summary: register given list of cm handles (internal use only)
+ description: register given list of cm handles (internal use only)
+ x-api-audience: component-internal
+ operationId: registerCmHandles
+ requestBody:
+ description: list of cm handles
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yml#/components/schemas/CmHandles'
+ required: true
+ responses:
+ '201':
+ description: Created
+ content:
+ text/plain:
+ schema:
+ type: string
+ example: cm-handle registered successfully
+ '400':
+ $ref: 'components.yml#/components/responses/BadRequest'
+ '500':
+ $ref: 'components.yml#/components/responses/ServerError'
+
+ /v1/ch/{cmHandle}/data/ds/{datastore-name}:
+ post:
+ tags:
+ - dmi-plugin
+ summary: Get resource data from passthrough operational or running for a cm handle
+ description: Get resource data from passthrough operational or running for a cm handle
+ operationId: dataAccessPassthrough
+ parameters:
+ - $ref: 'components.yml#/components/parameters/datastoreName'
+ - $ref: 'components.yml#/components/parameters/cmHandleInPath'
+ - $ref: 'components.yml#/components/parameters/resourceIdentifierInQuery'
+ - $ref: 'components.yml#/components/parameters/optionsParamInQuery'
+ - $ref: 'components.yml#/components/parameters/topicParamInQuery'
+ requestBody:
+ description: Contains collection of cm handles with it's private properties and requestId
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yml#/components/schemas/DataAccessRequest'
+ responses:
+ '200':
+ description: OK
+ content:
+ application/json:
+ schema:
+ type: object
+ example:
+ - yangSource: my-yang-source
+ moduleName: my-module-name
+ revision: my-revision
+ '400':
+ $ref: 'components.yml#/components/responses/BadRequest'
+ '500':
+ $ref: 'components.yml#/components/responses/ServerError'
+
+ /v1/data:
+ post:
+ tags:
+ - dmi-plugin
+ summary: Execute a data operation for group of cm handle ids.
+ description: Execute a data operation for group of cm handle ids by supplied operation details
+ operationId: getResourceDataForCmHandleDataOperation
+ parameters:
+ - $ref: 'components.yml#/components/parameters/requiredTopicParamInQuery'
+ - $ref: 'components.yml#/components/parameters/requiredRequestIdParamInQuery'
+ requestBody:
+ description: list of operation details
+ content:
+ application/json:
+ schema:
+ $ref: 'components.yml#/components/schemas/ResourceDataOperationRequests'
+ responses:
+ '202':
+ description: Accepted
+ '400':
+ $ref: 'components.yml#/components/responses/BadRequest'
+ '500':
+ $ref: 'components.yml#/components/responses/ServerError'
+ '501':
+ $ref: 'components.yml#/components/responses/NotImplemented' \ No newline at end of file
diff --git a/dmi-service/pom.xml b/dmi-service/pom.xml
new file mode 100644
index 00000000..a2a1f054
--- /dev/null
+++ b/dmi-service/pom.xml
@@ -0,0 +1,660 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.onap.cps</groupId>
+ <artifactId>ncmp-dmi-plugin</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>dmi-service</artifactId>
+
+ <properties>
+ <app>org.onap.cps.ncmp.dmi.Application</app>
+ <base.image>${docker.pull.registry}/onap/integration-java17:12.0.0</base.image>
+ <cps.version>3.4.9</cps.version>
+ <image.tag>${project.version}-${maven.build.timestamp}</image.tag>
+ <jacoco.minimum.coverage>0.98</jacoco.minimum.coverage>
+ <maven.build.timestamp.format>yyyyMMdd'T'HHmmss'Z'</maven.build.timestamp.format>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-dependencies</artifactId>
+ <version>3.1.2</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>2022.0.3</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.8.9</version>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger.core.v3</groupId>
+ <artifactId>swagger-annotations</artifactId>
+ <version>2.2.22</version>
+ </dependency>
+ <dependency>
+ <groupId>io.cloudevents</groupId>
+ <artifactId>cloudevents-json-jackson</artifactId>
+ <version>2.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.cloudevents</groupId>
+ <artifactId>cloudevents-kafka</artifactId>
+ <version>2.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>io.cloudevents</groupId>
+ <artifactId>cloudevents-spring</artifactId>
+ <version>2.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>net.logstash.logback</groupId>
+ <artifactId>logstash-logback-encoder</artifactId>
+ <version>7.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>net.minidev</groupId>
+ <artifactId>json-smart</artifactId>
+ <version>2.5.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.5.13</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ <version>3.0.18</version>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-core</artifactId>
+ <version>2.4-M1-groovy-3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-spring</artifactId>
+ <version>2.4-M1-groovy-3.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <version>2.0.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>testcontainers-bom</artifactId>
+ <version>1.18.3</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.janino</groupId>
+ <artifactId>janino</artifactId>
+ <version>3.1.10</version>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>1.18.24</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.cps</groupId>
+ <artifactId>cps-ncmp-events</artifactId>
+ <version>${cps.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.client5</groupId>
+ <artifactId>httpclient5</artifactId>
+ <version>5.2.1</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-validation</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.kafka</groupId>
+ <artifactId>spring-kafka</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.jayway.jsonpath</groupId>
+ <artifactId>json-path</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.micrometer</groupId>
+ <artifactId>micrometer-registry-prometheus</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.swagger.core.v3</groupId>
+ <artifactId>swagger-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.cloudevents</groupId>
+ <artifactId>cloudevents-json-jackson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.cloudevents</groupId>
+ <artifactId>cloudevents-kafka</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.cloudevents</groupId>
+ <artifactId>cloudevents-spring</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.logstash.logback</groupId>
+ <artifactId>logstash-logback-encoder</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>net.minidev</groupId>
+ <artifactId>json-smart</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.janino</groupId>
+ <artifactId>janino</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-http</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.cps</groupId>
+ <artifactId>cps-ncmp-events</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents.client5</groupId>
+ <artifactId>httpclient5</artifactId>
+ </dependency>
+ <!-- T E S T - D E P E N D E N C I E S -->
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-spring</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.kafka</groupId>
+ <artifactId>spring-kafka-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>spock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>kafka</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ <resource>
+ <directory>target/generated-sources/license</directory>
+ <includes>
+ <include>third-party-licenses.txt</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>target/generated-resources/licenses</directory>
+ <includes>
+ <include>*.*</include>
+ </includes>
+ <targetPath>third-party-licenses</targetPath>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.openapitools</groupId>
+ <artifactId>openapi-generator-maven-plugin</artifactId>
+ <version>6.6.0</version>
+ <executions>
+ <execution>
+ <id>dmi-code-gen</id>
+ <goals>
+ <goal>generate</goal>
+ </goals>
+ <configuration>
+ <inputSpec>${project.basedir}/openapi/openapi.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>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.datajobs.rest.controller</invokerPackage>
+ <apiPackage>org.onap.cps.ncmp.dmi.datajobs.rest.api</apiPackage>
+ <modelPackage>org.onap.cps.ncmp.dmi.datajobs.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>
+ </goals>
+ <phase>compile</phase>
+ <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>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-resources</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.basedir}/target/classes/static/api-docs</outputDirectory>
+ <resources>
+ <resource>
+ <directory>${project.basedir}/target/generated-sources/openapi/openapi</directory>
+ <includes>
+ <include>openapi*.yaml</include>
+ </includes>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>3.1.2</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>build-info</goal>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.gmavenplus</groupId>
+ <artifactId>gmavenplus-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>compileTests</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <argLine>${surefireArgLine}</argLine>
+ <includes>
+ <include>**/*Spec.java</include>
+ </includes>
+ <excludes>
+ <exclude>**/IT*.java</exclude>
+ </excludes>
+ <environmentVariables>
+ <!--
+ Disable privileged container usage to cleanup the test containers;
+ these are removed automatically on jvm termination;
+ see https://www.testcontainers.org/features/configuration/#disabling-ryuk
+ -->
+ <TESTCONTAINERS_RYUK_DISABLED>true</TESTCONTAINERS_RYUK_DISABLED>
+ </environmentVariables>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>onap-license</id>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <phase>process-sources</phase>
+ <configuration>
+ <configLocation>onap-checkstyle/check-license.xml</configLocation>
+ <includeResources>false</includeResources>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <includeTestResources>false</includeTestResources>
+ <sourceDirectories>
+ <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
+ </sourceDirectories>
+ <consoleOutput>false</consoleOutput>
+ <violationSeverity>warning</violationSeverity>
+ <failOnViolation>true</failOnViolation>
+ </configuration>
+ </execution>
+ <execution>
+ <id>onap-java-style</id>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <phase>process-sources</phase>
+ <configuration>
+ <configLocation>onap-checkstyle/onap-java-style.xml</configLocation>
+ <sourceDirectories>
+ <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
+ </sourceDirectories>
+ <includeResources>true</includeResources>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <includeTestResources>true</includeTestResources>
+ <consoleOutput>false</consoleOutput>
+ <violationSeverity>warning</violationSeverity>
+ <failOnViolation>true</failOnViolation>
+ </configuration>
+ </execution>
+ <execution>
+ <id>cps-java-style</id>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <phase>process-sources</phase>
+ <configuration>
+ <configLocation>cps-java-style.xml</configLocation>
+ <sourceDirectories>
+ <sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
+ </sourceDirectories>
+ <includeResources>true</includeResources>
+ <includeTestSourceDirectory>true</includeTestSourceDirectory>
+ <includeTestResources>true</includeTestResources>
+ <consoleOutput>true</consoleOutput>
+ <violationSeverity>warning</violationSeverity>
+ <failOnViolation>true</failOnViolation>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>checkstyle</artifactId>
+ <version>${cps.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <version>4.4.2</version>
+ <executions>
+ <execution>
+ <id>analyze-compile</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>spotbugs</artifactId>
+ <version>${cps.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+ <configuration>
+ <plugins>
+ <plugin>
+ <groupId>jp.skypencil.findbugs.slf4j</groupId>
+ <artifactId>bug-pattern</artifactId>
+ <version>1.5.0</version>
+ </plugin>
+ </plugins>
+ <effort>Max</effort>
+ <threshold>Low</threshold>
+ <failOnError>true</failOnError>
+ <excludeFilterFile>spotbugs-exclude.xml</excludeFilterFile>
+ <addSourceDirs>true</addSourceDirs>
+ <xmlOutput>true</xmlOutput>
+ <xmlOutputDirectory>${basedir}/target/spotbugs</xmlOutputDirectory>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.8.10</version>
+ <configuration>
+ <excludes>
+ <exclude>org/onap/cps/ncmp/dmi/model/*</exclude>
+ <exclude>org/onap/cps/ncmp/dmi/datajobs/model/*</exclude>
+ </excludes>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default-prepare-agent</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>coverage-check</id>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <dataFile>${basedir}/target/code-coverage/jacoco-ut.exec</dataFile>
+ <rules>
+ <rule>
+ <element>BUNDLE</element>
+ <limits>
+ <limit>
+ <counter>INSTRUCTION</counter>
+ <value>COVEREDRATIO</value>
+ <minimum>${jacoco.minimum.coverage}</minimum>
+ </limit>
+ </limits>
+ </rule>
+ </rules>
+ </configuration>
+ </execution>
+ <execution>
+ <id>report</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>report-aggregate</goal>
+ </goals>
+ <configuration>
+ <dataFileIncludes>
+ <fileInclude>**/code-coverage/jacoco-ut.exec</fileInclude>
+ </dataFileIncludes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>com.google.cloud.tools</groupId>
+ <artifactId>jib-maven-plugin</artifactId>
+ <version>3.3.2</version>
+ <configuration>
+ <container>
+ <mainClass>${app}</mainClass>
+ <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
+ </container>
+ <from>
+ <image>${base.image}</image>
+ </from>
+ <to>
+ <tags>
+ <tag>latest</tag>
+ <tag>${project.version}-latest</tag>
+ </tags>
+ <image>${docker.push.registry}/onap/${image.name}:${image.tag}</image>
+ </to>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <id>build</id>
+ <goals>
+ <goal>dockerBuild</goal>
+ </goals>
+ </execution>
+ <execution>
+ <phase>deploy</phase>
+ <id>buildAndPush</id>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+ <profiles>
+ <profile>
+ <id>docker</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <properties>
+ <image.name>ncmp-dmi-plugin</image.name>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.google.cloud.tools</groupId>
+ <artifactId>jib-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project> \ No newline at end of file
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/Application.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/Application.java
new file mode 100644
index 00000000..69d21ba1
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/Application.java
@@ -0,0 +1,33 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(final String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java
new file mode 100644
index 00000000..83ef6f89
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiConfiguration.java
@@ -0,0 +1,86 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.config;
+
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * Provides access to cps base url and cps authentication.
+ */
+@Configuration
+public class DmiConfiguration {
+
+ private static final int TIMEOUT = 5000;
+
+ @Getter
+ @Component
+ public static class CpsProperties {
+
+ @Value("${cps-core.baseUrl}")
+ private String baseUrl;
+ @Value("${cps-core.dmiRegistrationUrl}")
+ private String dmiRegistrationUrl;
+ @Value("${cps-core.auth.username}")
+ private String authUsername;
+ @Value("${cps-core.auth.password}")
+ private String authPassword;
+ }
+
+ @Getter
+ @Component
+ public static class SdncProperties {
+
+ @Value("${sdnc.baseUrl}")
+ private String baseUrl;
+ @Value("${sdnc.auth.username}")
+ private String authUsername;
+ @Value("${sdnc.auth.password}")
+ private String authPassword;
+ @Value("${sdnc.topologyId}")
+ public String topologyId;
+ }
+
+ /**
+ * Returns restTemplate bean for the spring context.
+ *
+ * @param restTemplateBuilder restTemplate builder
+ * @return {@code RestTemplate} rest template
+ */
+ @Bean
+ public RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder) {
+ final RestTemplate restTemplate = restTemplateBuilder.build();
+ setCustomRequestFactoryToSupportPatch(restTemplate);
+ return restTemplate;
+ }
+
+ private void setCustomRequestFactoryToSupportPatch(final RestTemplate restTemplate) {
+ final HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
+ requestFactory.setConnectTimeout(TIMEOUT);
+ restTemplate.setRequestFactory(requestFactory);
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiPluginConfig.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiPluginConfig.java
new file mode 100644
index 00000000..fb22b358
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/DmiPluginConfig.java
@@ -0,0 +1,51 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.dmi.config;
+
+import lombok.Getter;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+
+@Configuration
+public class DmiPluginConfig {
+
+ /**
+ * Swagger-ui configuration using springdoc.
+ */
+ @Bean("dmi-plugin-api")
+ public GroupedOpenApi api() {
+ return GroupedOpenApi.builder().group("dmi-plugin-api")
+ .pathsToMatch("/swagger-ui/**,/swagger-resources/**,/v3/api-docs").build();
+ }
+
+ @Getter
+ @Component
+ public static class DmiPluginProperties {
+
+ @Value("${dmi.service.url}")
+ private String dmiServiceUrl;
+ }
+}
+
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/WebSecurityConfig.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/WebSecurityConfig.java
new file mode 100644
index 00000000..ac92cb4a
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/WebSecurityConfig.java
@@ -0,0 +1,105 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.dmi.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.SecurityFilterChain;
+
+/**
+ * Configuration class to implement application security.
+ * It enforces Basic Authentication access control.
+ */
+@Configuration
+@EnableWebSecurity
+public class WebSecurityConfig {
+
+ private static final String USER_ROLE = "USER";
+
+ private final String username;
+ private final String password;
+ private final String[] permitUris;
+
+ /**
+ * Constructor. Accepts parameters from configuration.
+ *
+ * @param permitUris comma-separated list of uri patterns for endpoints permitted
+ * @param username username
+ * @param password password
+ */
+ public WebSecurityConfig(
+ @Autowired @Value("${security.permit-uri}") final String permitUris,
+ @Autowired @Value("${security.auth.username}") final String username,
+ @Autowired @Value("${security.auth.password}") final String password
+ ) {
+ super();
+ this.permitUris = permitUris.isEmpty() ? new String[] {"/v3/api-docs"} : permitUris.split("\\s{0,9},\\s{0,9}");
+ this.username = username;
+ this.password = password;
+ }
+
+ /**
+ * Return the configuration for secure access to the modules REST end points.
+ *
+ * @param http the HTTP security settings.
+ * @return the HTTP security settings.
+ */
+ @Bean
+ // The team decided to disable default CSRF Spring protection and not implement CSRF tokens validation.
+ // ncmp is a stateless REST API that is not as vulnerable to CSRF attacks as web applications running in
+ // web browsers are. ncmp does not manage sessions, each request requires the authentication token in the header.
+ // See https://docs.spring.io/spring-security/site/docs/5.3.8.RELEASE/reference/html5/#csrf
+ @SuppressWarnings("squid:S4502")
+ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
+ http
+ .httpBasic(httpBasicCustomizer -> {})
+ .authorizeHttpRequests(authorizeHttpRequestsCustomizer -> {
+ authorizeHttpRequestsCustomizer.requestMatchers(permitUris).permitAll();
+ authorizeHttpRequestsCustomizer.anyRequest().authenticated();
+ })
+ .csrf(AbstractHttpConfigurer::disable);
+
+ return http.build();
+ }
+
+ /**
+ * In memory user authenticaion details.
+ *
+ * @return in memory authentication.
+ */
+ @Bean
+ public InMemoryUserDetailsManager userDetailsService() {
+ final UserDetails user = User.builder()
+ .username(username)
+ .password("{noop}" + password)
+ .roles(USER_ROLE)
+ .build();
+ return new InMemoryUserDetailsManager(user);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfig.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfig.java
new file mode 100644
index 00000000..25ee92ae
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfig.java
@@ -0,0 +1,142 @@
+/*
+ * ============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.dmi.config.kafka;
+
+import io.cloudevents.CloudEvent;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+import org.springframework.kafka.support.serializer.JsonDeserializer;
+import org.springframework.kafka.support.serializer.JsonSerializer;
+
+/**
+ * kafka Configuration for legacy and cloud events.
+ *
+ * @param <T> valid legacy event to be published over the wire.
+ */
+@Configuration
+@EnableKafka
+@RequiredArgsConstructor
+public class KafkaConfig<T> {
+
+ private final KafkaProperties kafkaProperties;
+
+ /**
+ * This sets the strategy for creating legacy Kafka producer instance from kafka properties defined into
+ * application.yml and replaces value-serializer by JsonSerializer.
+ *
+ * @return legacy event producer instance.
+ */
+ @Bean
+ public ProducerFactory<String, T> legacyEventProducerFactory() {
+ final Map<String, Object> producerConfigProperties = kafkaProperties.buildProducerProperties();
+ producerConfigProperties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
+ return new DefaultKafkaProducerFactory<>(producerConfigProperties);
+ }
+
+ /**
+ * The ConsumerFactory implementation is to produce new legacy instance for provided kafka properties defined
+ * into application.yml and replaces deserializer-value by JsonDeserializer.
+ *
+ * @return an instance of legacy consumer factory.
+ */
+ @Bean
+ public ConsumerFactory<String, T> legacyEventConsumerFactory() {
+ final Map<String, Object> consumerConfigProperties = kafkaProperties.buildConsumerProperties();
+ consumerConfigProperties.put("spring.deserializer.value.delegate.class", JsonDeserializer.class);
+ return new DefaultKafkaConsumerFactory<>(consumerConfigProperties);
+ }
+
+ /**
+ * This sets the strategy for creating cloud Kafka producer instance from kafka properties defined into
+ * application.yml with CloudEventSerializer.
+ *
+ * @return cloud event producer instance.
+ */
+ @Bean
+ public ProducerFactory<String, CloudEvent> cloudEventProducerFactory() {
+ final Map<String, Object> producerConfigProperties = kafkaProperties.buildProducerProperties();
+ return new DefaultKafkaProducerFactory<>(producerConfigProperties);
+ }
+
+ /**
+ * The ConsumerFactory implementation to produce new legacy instance for provided kafka properties defined
+ * into application.yml having CloudEventDeserializer as deserializer-value.
+ *
+ * @return an instance of cloud consumer factory.
+ */
+ @Bean
+ public ConsumerFactory<String, CloudEvent> cloudEventConsumerFactory() {
+ final Map<String, Object> consumerConfigProperties = kafkaProperties.buildConsumerProperties();
+ return new DefaultKafkaConsumerFactory<>(consumerConfigProperties);
+ }
+
+ /**
+ * A legacy Kafka event template for executing high-level operations. The legacy producer factory ensure this.
+ *
+ * @return an instance of legacy Kafka template.
+ */
+ @Bean
+ @Primary
+ public KafkaTemplate<String, T> legacyEventKafkaTemplate() {
+ final KafkaTemplate<String, T> kafkaTemplate = new KafkaTemplate<>(legacyEventProducerFactory());
+ kafkaTemplate.setConsumerFactory(legacyEventConsumerFactory());
+ return kafkaTemplate;
+ }
+
+ /**
+ * A cloud Kafka event template for executing high-level operations. The cloud producer factory ensure this.
+ *
+ * @return an instance of cloud Kafka template.
+ */
+ @Bean
+ public KafkaTemplate<String, CloudEvent> cloudEventKafkaTemplate() {
+ final KafkaTemplate<String, CloudEvent> kafkaTemplate = new KafkaTemplate<>(cloudEventProducerFactory());
+ kafkaTemplate.setConsumerFactory(cloudEventConsumerFactory());
+ return kafkaTemplate;
+ }
+
+ /**
+ * A cloud Kafka event template for executing high-level operations. The cloud producer factory ensure this.
+ *
+ * @return an instance of cloud Kafka template.
+ */
+ @Bean
+ public ConcurrentKafkaListenerContainerFactory<String, CloudEvent>
+ cloudEventConcurrentKafkaListenerContainerFactory() {
+ final ConcurrentKafkaListenerContainerFactory<String, CloudEvent> containerFactory =
+ new ConcurrentKafkaListenerContainerFactory<>();
+ containerFactory.setConsumerFactory(cloudEventConsumerFactory());
+ return containerFactory;
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestController.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestController.java
new file mode 100644
index 00000000..928ec6b6
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestController.java
@@ -0,0 +1,92 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.datajobs.rest.controller;
+
+import org.onap.cps.ncmp.dmi.datajobs.model.SubjobReadRequest;
+import org.onap.cps.ncmp.dmi.datajobs.model.SubjobWriteRequest;
+import org.onap.cps.ncmp.dmi.datajobs.rest.api.DmiDatajobApi;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequestMapping("${rest.api.dmi-base-path}")
+@RestController
+public class DmiDatajobsRestController implements DmiDatajobApi {
+ /**
+ * This method is not implemented for ONAP DMI plugin.
+ *
+ * @param requestId Identifier for the overall Datajob (required)
+ * @param subjobReadRequest Operation body (optional)
+ * @return (@ code ResponseEntity) Response entity
+ */
+ @Override
+ public ResponseEntity<Void> readDataJob(final String requestId,
+ final SubjobReadRequest subjobReadRequest) {
+
+ return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * This method is not implemented for ONAP DMI plugin.
+ *
+ * @param requestId Identifier for the overall Datajob (required)
+ * @param subjobWriteRequest Operation body (optional)
+ * @return (@ code ResponseEntity) Response entity
+ */
+ @Override
+ public ResponseEntity<Void> writeDataJob(final String requestId,
+ final SubjobWriteRequest subjobWriteRequest) {
+ return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * This method is not implemented for ONAP DMI plugin.
+ *
+ * @param requestId Identifier for the overall Datajob (required)
+ * @param dataProducerJobId Identifier for the data producer job (required)
+ * @param dataProducerId Identifier for the data producer as a query parameter (required)
+ * @return ResponseEntity Response entity indicating the method is not implemented
+ */
+ @Override
+ public ResponseEntity<Void> getDataJobStatus(final String requestId,
+ final String dataProducerJobId,
+ final String dataProducerId) {
+ return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * This method is not implemented for ONAP DMI plugin.
+ *
+ * @param requestId Identifier for the overall Datajob (required)
+ * @param dataProducerJobId Identifier for the data producer job (required)
+ * @param dataProducerId Identifier for the data producer as a query parameter (required)
+ * @param destination The destination of the results, Kafka topic name or s3 bucket name (required)
+ * @return ResponseEntity Response entity indicating the method is not implemented
+ */
+ @Override
+ public ResponseEntity<Void> getDataJobResult(final String requestId,
+ final String dataProducerJobId,
+ final String dataProducerId,
+ final String destination) {
+ return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CloudEventConstructionException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CloudEventConstructionException.java
new file mode 100644
index 00000000..f61c156a
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CloudEventConstructionException.java
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.exception;
+
+public class CloudEventConstructionException extends DmiException {
+
+ private static final long serialVersionUID = 7747941311132087621L;
+
+ /**
+ * CloudEventConstructionException.
+ *
+ * @param message the error message
+ * @param details the error details
+ * @param cause the error cause
+ */
+ public CloudEventConstructionException(final String message, final String details, final Throwable cause) {
+ super(message, details, cause);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CmHandleRegistrationException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CmHandleRegistrationException.java
new file mode 100644
index 00000000..1874389e
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/CmHandleRegistrationException.java
@@ -0,0 +1,36 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.exception;
+
+public class CmHandleRegistrationException extends DmiException {
+
+ private static final long serialVersionUID = 8973438585188332404L;
+
+ /**
+ * Constructor.
+ *
+ * @param details the error details
+ */
+ public CmHandleRegistrationException(final String details) {
+ super("Not able to register the given cm-handles.", details);
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiException.java
new file mode 100644
index 00000000..c099a1cc
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiException.java
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.exception;
+
+import lombok.Getter;
+
+/**
+ * Dmi exception.
+ */
+public class DmiException extends RuntimeException {
+
+ private static final long serialVersionUID = 1481520410918497487L;
+
+ @Getter
+ final String details;
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param details the error details
+ */
+ public DmiException(final String message, final String details) {
+ super(message);
+ this.details = details;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param details the error details
+ * @param cause the cause of the exception
+ */
+ public DmiException(final String message, final String details, final Throwable cause) {
+ super(message, cause);
+ this.details = details;
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java
new file mode 100644
index 00000000..49db7d8b
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/DmiExceptionHandler.java
@@ -0,0 +1,74 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.exception;
+
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.dmi.model.ErrorMessage;
+import org.onap.cps.ncmp.dmi.rest.controller.DmiRestController;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@Slf4j
+@RestControllerAdvice(assignableTypes = {DmiRestController.class})
+public class DmiExceptionHandler {
+
+ private DmiExceptionHandler() {
+ }
+
+ /**
+ * Default exception handler.
+ *
+ * @param exception the exception to handle
+ * @return response with response code 500.
+ */
+ @ExceptionHandler
+ public static ResponseEntity<Object> handleInternalServerErrorExceptions(final Exception exception) {
+ return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
+ }
+
+ @ExceptionHandler({ModulesNotFoundException.class, ModuleResourceNotFoundException.class})
+ public static ResponseEntity<Object> handleNotFoundExceptions(final DmiException exception) {
+ return buildErrorResponse(HttpStatus.NOT_FOUND, exception);
+ }
+
+ @ExceptionHandler({CmHandleRegistrationException.class, DmiException.class})
+ public static ResponseEntity<Object> handleAnyOtherDmiExceptions(final DmiException exception) {
+ return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
+ }
+
+ private static ResponseEntity<Object> buildErrorResponse(final HttpStatus httpStatus, final Exception exception) {
+ logForNonDmiException(exception);
+ final var errorMessage = new ErrorMessage();
+ errorMessage.setStatus(httpStatus.toString());
+ errorMessage.setMessage(exception.getMessage());
+ errorMessage.setDetails(exception instanceof DmiException ? ((DmiException) exception).getDetails() :
+ "Check logs for details.");
+ return new ResponseEntity<>(errorMessage, httpStatus);
+ }
+
+ private static void logForNonDmiException(final Exception exception) {
+ if (exception.getCause() != null || !(exception instanceof DmiException)) {
+ log.error("Exception occurred", exception);
+ }
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/HttpClientRequestException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/HttpClientRequestException.java
new file mode 100644
index 00000000..b4b0249f
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/HttpClientRequestException.java
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.ncmp.dmi.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+@Getter
+public class HttpClientRequestException extends DmiException {
+
+ private static final long serialVersionUID = 881438585188332404L;
+
+ private final HttpStatus httpStatus;
+
+ /**
+ * Constructor.
+ *
+ * @param cmHandle cmHandle identifier
+ * @param details response body from the client available as details
+ * @param httpStatus http status from the client
+ */
+ public HttpClientRequestException(final String cmHandle, final String details, final HttpStatus httpStatus) {
+ super("Resource data request failed for CM Handle: " + cmHandle, details);
+ this.httpStatus = httpStatus;
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/InvalidDatastoreException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/InvalidDatastoreException.java
new file mode 100644
index 00000000..aa5b0cb7
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/InvalidDatastoreException.java
@@ -0,0 +1,32 @@
+/*
+ * ============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.dmi.exception;
+
+public class InvalidDatastoreException extends RuntimeException {
+ /**
+ * Instantiates a new Invalid datastore exception.
+ *
+ * @param message the message
+ */
+ public InvalidDatastoreException(final String message) {
+ super(message);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java
new file mode 100644
index 00000000..65db2712
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModuleResourceNotFoundException.java
@@ -0,0 +1,38 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.exception;
+
+public class ModuleResourceNotFoundException extends DmiException {
+
+ private static final long serialVersionUID = 4764849097602543408L;
+
+ private static final String ERROR_MESSAGE = "Module resource not found for given cmHandle: ";
+
+ /**
+ * Constructor.
+ *
+ * @param cmHandle the cm handle
+ * @param details the details of the error
+ */
+ public ModuleResourceNotFoundException(final String cmHandle, final String details) {
+ super(ERROR_MESSAGE + cmHandle, details);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModulesNotFoundException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModulesNotFoundException.java
new file mode 100644
index 00000000..ded54d91
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/ModulesNotFoundException.java
@@ -0,0 +1,38 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.exception;
+
+public class ModulesNotFoundException extends DmiException {
+
+ private static final long serialVersionUID = 980438585188332404L;
+
+ private static final String ERROR_MESSAGE = "Not able to register the given cm-handles: ";
+
+ /**
+ * Constructor.
+ *
+ * @param cmHandle cmHandle identifier
+ * @param details the error details
+ */
+ public ModulesNotFoundException(final String cmHandle, final String details) {
+ super(ERROR_MESSAGE + cmHandle, details);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/SdncException.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/SdncException.java
new file mode 100644
index 00000000..a83485a9
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/exception/SdncException.java
@@ -0,0 +1,49 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Bell Canada
+ * ================================================================================
+ * 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.dmi.exception;
+
+import org.springframework.http.HttpStatus;
+
+/*
+Use this exception when SDNC contract fails
+ */
+public class SdncException extends DmiException {
+
+ private static final long serialVersionUID = -2076096996672060566L;
+
+ /**
+ * Constructor.
+ *
+ * @param message message
+ * @param httpStatus httpStatus
+ * @param responseBody responseBody
+ */
+ public SdncException(final String message, final HttpStatus httpStatus, final String responseBody) {
+ super(message, String.format("sdnc http status: %s, response body : %s ",
+ httpStatus.toString(),
+ responseBody));
+ }
+
+ public SdncException(final String message, final String details, final Throwable cause) {
+ super(message, details, cause);
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutor.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutor.java
new file mode 100644
index 00000000..6a1c6d1d
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutor.java
@@ -0,0 +1,121 @@
+/*
+ * ============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.ncmp.dmi.notifications.async;
+
+import com.google.gson.JsonObject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AsyncTaskExecutor {
+
+ private final DmiAsyncRequestResponseEventProducer dmiAsyncRequestResponseEventProducer;
+
+ private static final DmiAsyncRequestResponseEventCreator dmiAsyncRequestResponseEventCreator =
+ new DmiAsyncRequestResponseEventCreator();
+
+ private static final Map<DataAccessRequest.OperationEnum, HttpStatus> operationToHttpStatusMap = new HashMap<>(6);
+
+ static {
+ operationToHttpStatusMap.put(null, HttpStatus.OK);
+ operationToHttpStatusMap.put(DataAccessRequest.OperationEnum.READ, HttpStatus.OK);
+ operationToHttpStatusMap.put(DataAccessRequest.OperationEnum.CREATE, HttpStatus.CREATED);
+ operationToHttpStatusMap.put(DataAccessRequest.OperationEnum.PATCH, HttpStatus.OK);
+ operationToHttpStatusMap.put(DataAccessRequest.OperationEnum.UPDATE, HttpStatus.OK);
+ operationToHttpStatusMap.put(DataAccessRequest.OperationEnum.DELETE, HttpStatus.NO_CONTENT);
+ }
+
+ /**
+ * Execute task asynchronously and publish response to supplied topic.
+ *
+ * @param taskSupplier functional method is get() task need to executed asynchronously
+ * @param topicName topic name where message need to be published
+ * @param requestId unique requestId for async request
+ * @param operation the operation performed
+ * @param timeOutInMilliSeconds task timeout in milliseconds
+ */
+ public void executeAsyncTask(final Supplier<String> taskSupplier,
+ final String topicName,
+ final String requestId,
+ final DataAccessRequest.OperationEnum operation,
+ final int timeOutInMilliSeconds) {
+ CompletableFuture.supplyAsync(taskSupplier::get)
+ .orTimeout(timeOutInMilliSeconds, TimeUnit.MILLISECONDS)
+ .whenCompleteAsync((resourceDataAsJson, throwable) -> {
+ if (throwable == null) {
+ final String status = operationToHttpStatusMap.get(operation).getReasonPhrase();
+ final String code = String.valueOf(operationToHttpStatusMap.get(operation).value());
+ publishAsyncEvent(topicName, requestId, resourceDataAsJson, status, code);
+ } else {
+ log.error("Error occurred with async request {}", throwable.getMessage());
+ publishAsyncFailureEvent(topicName, requestId, throwable);
+ }
+ });
+ log.info("Async task completed.");
+ }
+
+ private void publishAsyncEvent(final String topicName,
+ final String requestId,
+ final String resourceDataAsJson,
+ final String status,
+ final String code) {
+ final DmiAsyncRequestResponseEvent cpsAsyncRequestResponseEvent =
+ dmiAsyncRequestResponseEventCreator.createEvent(resourceDataAsJson, topicName, requestId, status, code);
+
+ dmiAsyncRequestResponseEventProducer.sendMessage(requestId, cpsAsyncRequestResponseEvent);
+ }
+
+ private void publishAsyncFailureEvent(final String topicName,
+ final String requestId,
+ final Throwable throwable) {
+ HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
+
+ if (throwable instanceof HttpClientRequestException) {
+ final HttpClientRequestException httpClientRequestException = (HttpClientRequestException) throwable;
+ httpStatus = httpClientRequestException.getHttpStatus();
+ }
+
+ final JsonObject errorDetails = new JsonObject();
+ errorDetails.addProperty("errorDetails", throwable.getMessage());
+ publishAsyncEvent(
+ topicName,
+ requestId,
+ errorDetails.toString(),
+ httpStatus.getReasonPhrase(),
+ String.valueOf(httpStatus.value())
+ );
+ }
+}
+
+
+
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventCreator.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventCreator.java
new file mode 100644
index 00000000..1e6c84b1
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventCreator.java
@@ -0,0 +1,90 @@
+/*
+ * ============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.ncmp.dmi.notifications.async;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.UUID;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.dmi.Application;
+import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent;
+import org.onap.cps.ncmp.event.model.EventContent;
+
+/**
+ * Helper to create DmiAsyncRequestResponseEvent.
+ */
+@Slf4j
+public class DmiAsyncRequestResponseEventCreator {
+
+ private static final DateTimeFormatter dateTimeFormatter
+ = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * Create an event.
+ *
+ * @param resourceDataAsJson the resource data as json
+ * @param topicParamInQuery the topic to send response to
+ * @param requestId the request id
+ * @param status the status of the request
+ * @param code the code of the response
+ *
+ * @return DmiAsyncRequestResponseEvent
+ */
+ public DmiAsyncRequestResponseEvent createEvent(final String resourceDataAsJson,
+ final String topicParamInQuery,
+ final String requestId,
+ final String status,
+ final String code) {
+ final DmiAsyncRequestResponseEvent dmiAsyncRequestResponseEvent = new DmiAsyncRequestResponseEvent();
+
+ dmiAsyncRequestResponseEvent.setEventId(UUID.randomUUID().toString());
+ dmiAsyncRequestResponseEvent.setEventCorrelationId(requestId);
+ dmiAsyncRequestResponseEvent.setEventType(DmiAsyncRequestResponseEvent.class.getName());
+ dmiAsyncRequestResponseEvent.setEventSchema("urn:cps:" + DmiAsyncRequestResponseEvent.class.getName());
+ dmiAsyncRequestResponseEvent.setEventSchemaVersion("v1");
+ dmiAsyncRequestResponseEvent.setEventSource(Application.class.getPackageName());
+ dmiAsyncRequestResponseEvent.setEventTarget(topicParamInQuery);
+ dmiAsyncRequestResponseEvent.setEventTime(ZonedDateTime.now().format(dateTimeFormatter));
+ dmiAsyncRequestResponseEvent.setEventContent(getEventContent(resourceDataAsJson, status, code));
+
+ return dmiAsyncRequestResponseEvent;
+ }
+
+ @SneakyThrows
+ private EventContent getEventContent(final String resourceDataAsJson, final String status, final String code) {
+ final EventContent eventContent = new EventContent();
+
+ eventContent.setResponseDataSchema("urn:cps:" + DmiAsyncRequestResponseEvent.class.getName() + ":v1");
+ eventContent.setResponseStatus(status);
+ eventContent.setResponseCode(code);
+
+ eventContent.setAdditionalProperty("response-data",
+ objectMapper.readValue(resourceDataAsJson, HashMap.class));
+
+ return eventContent;
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventProducer.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventProducer.java
new file mode 100644
index 00000000..00fea330
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/async/DmiAsyncRequestResponseEventProducer.java
@@ -0,0 +1,47 @@
+/*
+ * ============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.ncmp.dmi.notifications.async;
+
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.event.model.DmiAsyncRequestResponseEvent;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DmiAsyncRequestResponseEventProducer {
+
+ private final KafkaTemplate<String, DmiAsyncRequestResponseEvent> kafkaTemplate;
+
+ @Value("${app.ncmp.async.topic}")
+ private String dmiNcmpTopic;
+
+ /**
+ * Sends message to the configured topic with a message key.
+ *
+ * @param requestId the request id
+ * @param dmiAsyncRequestResponseEvent the event to publish
+ */
+ public void sendMessage(final String requestId, final DmiAsyncRequestResponseEvent dmiAsyncRequestResponseEvent) {
+ kafkaTemplate.send(dmiNcmpTopic, requestId, dmiAsyncRequestResponseEvent);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcCloudEventCreator.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcCloudEventCreator.java
new file mode 100644
index 00000000..b8bd277d
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcCloudEventCreator.java
@@ -0,0 +1,104 @@
+/*
+ * ============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.dmi.notifications.avc;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+import java.net.URI;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.events.avc1_0_0.AvcEvent;
+import org.onap.cps.ncmp.events.avc1_0_0.Data;
+import org.onap.cps.ncmp.events.avc1_0_0.DatastoreChanges;
+import org.onap.cps.ncmp.events.avc1_0_0.Edit;
+import org.onap.cps.ncmp.events.avc1_0_0.IetfYangPatchYangPatch;
+import org.onap.cps.ncmp.events.avc1_0_0.PushChangeUpdate;
+import org.onap.cps.ncmp.events.avc1_0_0.Value;
+
+/**
+ * Helper to create AvcEvents.
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DmiDataAvcCloudEventCreator {
+
+ private static final DateTimeFormatter dateTimeFormatter =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * Creates CloudEvent for DMI Data AVC.
+ *
+ * @param eventCorrelationId correlationid
+ * @return Cloud Event
+ */
+ public static CloudEvent createCloudEvent(final String eventCorrelationId) {
+
+ CloudEvent cloudEvent = null;
+
+ try {
+ cloudEvent = CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withSource(URI.create("NCMP"))
+ .withType(AvcEvent.class.getName())
+ .withDataSchema(URI.create("urn:cps:" + AvcEvent.class.getName() + ":1.0.0"))
+ .withExtension("correlationid", eventCorrelationId)
+ .withData(objectMapper.writeValueAsBytes(createDmiDataAvcEvent())).build();
+ } catch (final JsonProcessingException jsonProcessingException) {
+ log.error("Unable to convert object to json : {}", jsonProcessingException.getMessage());
+ }
+
+ return cloudEvent;
+ }
+
+ private static AvcEvent createDmiDataAvcEvent() {
+ final AvcEvent avcEvent = new AvcEvent();
+ final Data data = new Data();
+ final PushChangeUpdate pushChangeUpdate = new PushChangeUpdate();
+ final DatastoreChanges datastoreChanges = new DatastoreChanges();
+ final IetfYangPatchYangPatch ietfYangPatchYangPatch = new IetfYangPatchYangPatch();
+ ietfYangPatchYangPatch.setPatchId("abcd");
+ final Edit edit1 = new Edit();
+ final Value value = new Value();
+ final Map<String, Object> attributeMap = new LinkedHashMap<>();
+ attributeMap.put("isHoAllowed", false);
+ value.setAttributes(List.of(attributeMap));
+ edit1.setEditId("editId");
+ edit1.setOperation("replace");
+ edit1.setTarget("target_xpath");
+ edit1.setValue(value);
+ ietfYangPatchYangPatch.setEdit(List.of(edit1));
+ datastoreChanges.setIetfYangPatchYangPatch(ietfYangPatchYangPatch);
+ pushChangeUpdate.setDatastoreChanges(datastoreChanges);
+ data.setPushChangeUpdate(pushChangeUpdate);
+
+ avcEvent.setData(data);
+ return avcEvent;
+ }
+
+} \ No newline at end of file
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventProducer.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventProducer.java
new file mode 100644
index 00000000..075dcf20
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventProducer.java
@@ -0,0 +1,50 @@
+/*
+ * ============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.dmi.notifications.avc;
+
+
+import io.cloudevents.CloudEvent;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class DmiDataAvcEventProducer {
+
+ private final KafkaTemplate<String, CloudEvent> cloudEventKafkaTemplate;
+
+ /**
+ * Publishing DMI Data AVC event payload as CloudEvent.
+ *
+ * @param requestId the request id
+ * @param cloudAvcEvent event with data as DMI DataAVC event
+ */
+ public void publishDmiDataAvcCloudEvent(final String requestId, final CloudEvent cloudAvcEvent) {
+ final ProducerRecord<String, CloudEvent> producerRecord =
+ new ProducerRecord<>("dmi-cm-events", requestId, cloudAvcEvent);
+ cloudEventKafkaTemplate.send(producerRecord);
+ log.debug("AVC event sent");
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventSimulationController.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventSimulationController.java
new file mode 100644
index 00000000..c5fb8fbe
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/DmiDataAvcEventSimulationController.java
@@ -0,0 +1,61 @@
+/*
+ * ============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.dmi.notifications.avc;
+
+import io.cloudevents.CloudEvent;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+
+@RequestMapping("${rest.api.dmi-base-path}")
+@RestController
+@Slf4j
+@RequiredArgsConstructor
+public class DmiDataAvcEventSimulationController {
+
+ private final DmiDataAvcEventProducer dmiDataAvcEventProducer;
+
+ /**
+ * Simulate Event for AVC.
+ *
+ * @param numberOfSimulatedEvents number of events to be generated
+ * @return ResponseEntity
+ */
+ @GetMapping(path = "/v1/simulateDmiDataEvent")
+ public ResponseEntity<Void> simulateEvents(
+ @RequestParam("numberOfSimulatedEvents") final Integer numberOfSimulatedEvents) {
+
+ for (int i = 0; i < numberOfSimulatedEvents; i++) {
+ final String eventCorrelationId = UUID.randomUUID().toString();
+ final CloudEvent cloudEvent = DmiDataAvcCloudEventCreator.createCloudEvent(eventCorrelationId);
+ dmiDataAvcEventProducer.publishDmiDataAvcCloudEvent(eventCorrelationId, cloudEvent);
+ }
+
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java
new file mode 100644
index 00000000..3a9838b0
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumer.java
@@ -0,0 +1,108 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.cmsubscription;
+
+import io.cloudevents.CloudEvent;
+import lombok.RequiredArgsConstructor;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.onap.cps.ncmp.dmi.notifications.cmsubscription.model.CmNotificationSubscriptionStatus;
+import org.onap.cps.ncmp.dmi.notifications.mapper.CloudEventMapper;
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent;
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.Data;
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class CmNotificationSubscriptionDmiInEventConsumer {
+
+
+ @Value("${app.dmi.avc.cm-subscription-dmi-out}")
+ private String cmNotificationSubscriptionDmiOutTopic;
+ @Value("${dmi.service.name}")
+ private String dmiName;
+ private final KafkaTemplate<String, CloudEvent> cloudEventKafkaTemplate;
+
+ /**
+ * Consume the cmNotificationSubscriptionDmiInCloudEvent event.
+ *
+ * @param cmNotificationSubscriptionDmiInCloudEvent the event to be consumed
+ */
+ @KafkaListener(topics = "${app.dmi.avc.cm-subscription-dmi-in}",
+ containerFactory = "cloudEventConcurrentKafkaListenerContainerFactory")
+ public void consumeCmNotificationSubscriptionDmiInEvent(
+ final ConsumerRecord<String, CloudEvent> cmNotificationSubscriptionDmiInCloudEvent) {
+ final CmNotificationSubscriptionDmiInEvent cmNotificationSubscriptionDmiInEvent =
+ CloudEventMapper.toTargetEvent(cmNotificationSubscriptionDmiInCloudEvent.value(),
+ CmNotificationSubscriptionDmiInEvent.class);
+ if (cmNotificationSubscriptionDmiInEvent != null) {
+ final String subscriptionId = cmNotificationSubscriptionDmiInCloudEvent.value().getId();
+ final String subscriptionType = cmNotificationSubscriptionDmiInCloudEvent.value().getType();
+ final String correlationId = String.valueOf(cmNotificationSubscriptionDmiInCloudEvent.value()
+ .getExtension("correlationid"));
+
+ if ("subscriptionCreateRequest".equals(subscriptionType)) {
+ createAndSendCmNotificationSubscriptionDmiOutEvent(subscriptionId, "subscriptionCreateResponse",
+ correlationId, CmNotificationSubscriptionStatus.ACCEPTED);
+ } else if ("subscriptionDeleteRequest".equals(subscriptionType)) {
+ createAndSendCmNotificationSubscriptionDmiOutEvent(subscriptionId, "subscriptionDeleteResponse",
+ correlationId, CmNotificationSubscriptionStatus.ACCEPTED);
+ }
+ }
+ }
+
+ /**
+ * Create Dmi out event object and send to response topic.
+ *
+ * @param eventKey the events key
+ * @param subscriptionType the subscriptions type
+ * @param correlationId the events correlation Id
+ * @param cmNotificationSubscriptionStatus subscriptions status accepted/rejected
+ */
+ public void createAndSendCmNotificationSubscriptionDmiOutEvent(
+ final String eventKey, final String subscriptionType, final String correlationId,
+ final CmNotificationSubscriptionStatus cmNotificationSubscriptionStatus) {
+
+ final CmNotificationSubscriptionDmiOutEvent cmNotificationSubscriptionDmiOutEvent =
+ new CmNotificationSubscriptionDmiOutEvent();
+ final Data cmNotificationSubscriptionDmiOutEventData = new Data();
+
+ if (cmNotificationSubscriptionStatus.equals(CmNotificationSubscriptionStatus.ACCEPTED)) {
+ cmNotificationSubscriptionDmiOutEventData.setStatusCode("1");
+ cmNotificationSubscriptionDmiOutEventData.setStatusMessage("ACCEPTED");
+ } else {
+ cmNotificationSubscriptionDmiOutEventData.setStatusCode("104");
+ cmNotificationSubscriptionDmiOutEventData.setStatusMessage("REJECTED");
+ }
+ cmNotificationSubscriptionDmiOutEvent.setData(cmNotificationSubscriptionDmiOutEventData);
+
+ cloudEventKafkaTemplate.send(cmNotificationSubscriptionDmiOutTopic, eventKey,
+ CmNotificationSubscriptionDmiOutEventToCloudEventMapper.toCloudEvent(cmNotificationSubscriptionDmiOutEvent,
+ subscriptionType, dmiName, correlationId));
+
+ }
+
+
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapper.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapper.java
new file mode 100644
index 00000000..51205da2
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapper.java
@@ -0,0 +1,62 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.cmsubscription;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+import java.net.URI;
+import java.util.UUID;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.onap.cps.ncmp.dmi.exception.CloudEventConstructionException;
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class CmNotificationSubscriptionDmiOutEventToCloudEventMapper {
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * Maps SubscriptionEventResponse to a CloudEvent.
+ *
+ * @param cmSubscriptionDmiOutEvent object.
+ * @param subscriptionType String of subscription type.
+ * @param dmiName String of dmiName.
+ * @param correlationId String of correlationId.
+ * @return CloudEvent built.
+ */
+ public static CloudEvent toCloudEvent(final CmNotificationSubscriptionDmiOutEvent cmSubscriptionDmiOutEvent,
+ final String subscriptionType, final String dmiName,
+ final String correlationId) {
+ try {
+ return CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withSource(URI.create(dmiName))
+ .withType(subscriptionType)
+ .withDataSchema(URI.create("urn:cps:org.onap.ncmp.dmi.cm.subscription:1.0.0"))
+ .withExtension("correlationid", correlationId)
+ .withData(objectMapper.writeValueAsBytes(cmSubscriptionDmiOutEvent)).build();
+ } catch (final Exception ex) {
+ throw new CloudEventConstructionException("The Cloud Event could not be constructed",
+ "Invalid object passed", ex);
+ }
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/model/CmNotificationSubscriptionStatus.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/model/CmNotificationSubscriptionStatus.java
new file mode 100644
index 00000000..40b1297f
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/cmsubscription/model/CmNotificationSubscriptionStatus.java
@@ -0,0 +1,32 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.cmsubscription.model;
+
+public enum CmNotificationSubscriptionStatus {
+
+ ACCEPTED("ACCEPTED"), REJECTED("REJECTED");
+
+ private final String cmNotificationSubscriptionStatusValue;
+
+ CmNotificationSubscriptionStatus(final String cmNotificationSubscriptionStatusValue) {
+ this.cmNotificationSubscriptionStatusValue = cmNotificationSubscriptionStatusValue;
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapper.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapper.java
new file mode 100644
index 00000000..8f196cfc
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapper.java
@@ -0,0 +1,62 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (c) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.mapper;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.CloudEventUtils;
+import io.cloudevents.core.data.PojoCloudEventData;
+import io.cloudevents.jackson.PojoCloudEventDataMapper;
+import io.cloudevents.rw.CloudEventRWException;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class CloudEventMapper {
+
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * Generic method to map cloud event data to target event class object.
+ *
+ * @param cloudEvent input cloud event
+ * @param targetEventClass target event class
+ * @param <T> target event class type
+ * @return mapped target event
+ */
+ public static <T> T toTargetEvent(final CloudEvent cloudEvent, final Class<T> targetEventClass) {
+ PojoCloudEventData<T> mappedCloudEvent = null;
+
+ try {
+ mappedCloudEvent =
+ CloudEventUtils.mapData(cloudEvent, PojoCloudEventDataMapper.from(objectMapper, targetEventClass));
+
+ } catch (final CloudEventRWException cloudEventRwException) {
+ log.error("Unable to map cloud event to target event class type : {} with cause : {}", targetEventClass,
+ cloudEventRwException.getMessage());
+ }
+
+ return mappedCloudEvent == null ? null : mappedCloudEvent.getValue();
+ }
+
+} \ No newline at end of file
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java
new file mode 100644
index 00000000..cad5e726
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java
@@ -0,0 +1,253 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2023 Nordix Foundation
+ * Modifications Copyright (C) 2022 Bell Canada
+ * ================================================================================
+ * 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.dmi.rest.controller;
+
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.dmi.model.CmHandles;
+import org.onap.cps.ncmp.dmi.model.DataAccessRequest;
+import org.onap.cps.ncmp.dmi.model.ModuleReferencesRequest;
+import org.onap.cps.ncmp.dmi.model.ModuleResourcesReadRequest;
+import org.onap.cps.ncmp.dmi.model.ModuleSet;
+import org.onap.cps.ncmp.dmi.model.ResourceDataOperationRequests;
+import org.onap.cps.ncmp.dmi.model.YangResources;
+import org.onap.cps.ncmp.dmi.notifications.async.AsyncTaskExecutor;
+import org.onap.cps.ncmp.dmi.rest.api.DmiPluginApi;
+import org.onap.cps.ncmp.dmi.rest.api.DmiPluginInternalApi;
+import org.onap.cps.ncmp.dmi.rest.controller.handlers.DatastoreType;
+import org.onap.cps.ncmp.dmi.service.DmiService;
+import org.onap.cps.ncmp.dmi.service.model.ModuleReference;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequestMapping("${rest.api.dmi-base-path}")
+@RestController
+@Slf4j
+@RequiredArgsConstructor
+public class DmiRestController implements DmiPluginApi, DmiPluginInternalApi {
+
+ private final DmiService dmiService;
+ private final ObjectMapper objectMapper;
+ private final AsyncTaskExecutor asyncTaskExecutor;
+ private static final Map<OperationEnum, HttpStatus> operationToHttpStatusMap = new HashMap<>(6);
+
+ @Value("${notification.async.executor.time-out-value-in-ms:2000}")
+ private int timeOutInMillis;
+
+ static {
+ operationToHttpStatusMap.put(null, HttpStatus.OK);
+ operationToHttpStatusMap.put(OperationEnum.READ, HttpStatus.OK);
+ operationToHttpStatusMap.put(OperationEnum.CREATE, HttpStatus.CREATED);
+ operationToHttpStatusMap.put(OperationEnum.PATCH, HttpStatus.OK);
+ operationToHttpStatusMap.put(OperationEnum.UPDATE, HttpStatus.OK);
+ operationToHttpStatusMap.put(OperationEnum.DELETE, HttpStatus.NO_CONTENT);
+ }
+
+ @Override
+ public ResponseEntity<ModuleSet> getModuleReferences(final String cmHandle,
+ final ModuleReferencesRequest body) {
+ // For onap-dmi-plugin we don't need cmHandleProperties, so DataAccessReadRequest is not used.
+ final ModuleSet moduleSet = dmiService.getModulesForCmHandle(cmHandle);
+ return ResponseEntity.ok(moduleSet);
+ }
+
+ @Override
+ public ResponseEntity<YangResources> retrieveModuleResources(
+ final String cmHandle,
+ final ModuleResourcesReadRequest moduleResourcesReadRequest) {
+ final List<ModuleReference> moduleReferences = convertRestObjectToJavaApiObject(moduleResourcesReadRequest);
+ final YangResources yangResources = dmiService.getModuleResources(cmHandle, moduleReferences);
+ log.info("Module set tag received: {}", moduleResourcesReadRequest.getModuleSetTag());
+ return new ResponseEntity<>(yangResources, HttpStatus.OK);
+ }
+
+ /**
+ * This method register given list of cm-handles to ncmp.
+ *
+ * @param cmHandles list of cm-handles
+ * @return (@ code ResponseEntity) response entity
+ */
+ public ResponseEntity<String> registerCmHandles(final CmHandles cmHandles) {
+ final List<String> cmHandlesList = cmHandles.getCmHandles();
+ if (cmHandlesList.isEmpty()) {
+ return new ResponseEntity<>("Need at least one cmHandle to process.", HttpStatus.BAD_REQUEST);
+ }
+ dmiService.registerCmHandles(cmHandlesList);
+ return new ResponseEntity<>("cm-handle registered successfully.", HttpStatus.CREATED);
+ }
+
+ /**
+ * This method is not implemented for ONAP DMI plugin.
+ *
+ * @param topic client given topic name
+ * @param requestId requestId generated by NCMP as an ack for client
+ * @param resourceDataOperationRequests list of operation details
+ * @return (@ code ResponseEntity) response entity
+ */
+ @Override
+ public ResponseEntity<Void> getResourceDataForCmHandleDataOperation(final String topic, final String requestId,
+ final ResourceDataOperationRequests resourceDataOperationRequests) {
+ log.info("Request Details (for testing purposes)");
+ log.info("Request Id: {}", requestId);
+ log.info("Topic: {}", topic);
+
+ log.info("Details of the first Operation");
+ log.info("Resource Identifier: {}", resourceDataOperationRequests.get(0).getResourceIdentifier());
+ log.info("Module Set Tag: {}", resourceDataOperationRequests.get(0).getCmHandles().get(0).getModuleSetTag());
+ log.info("Operation Id: {}", resourceDataOperationRequests.get(0).getOperationId());
+ log.info("Cm Handles: {}", resourceDataOperationRequests.get(0).getCmHandles());
+ log.info("Options: {}", resourceDataOperationRequests.get(0).getOptions());
+
+ return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+ }
+
+ /**
+ * This method fetches the resource for given cm handle using pass through operational or running datastore.
+ * It filters the response on the basis of options query parameters and returns response. Passthrough Running
+ * supports both read and write operation whereas passthrough operational does not support write operations.
+ *
+ * @param datastoreName name of the datastore
+ * @param cmHandle cm handle identifier
+ * @param resourceIdentifier resource identifier to fetch data
+ * @param optionsParamInQuery options query parameter
+ * @param topicParamInQuery topic name for (triggering) async responses
+ * @param dataAccessRequest data Access Request
+ * @return {@code ResponseEntity} response entity
+ */
+ @Override
+ public ResponseEntity<Object> dataAccessPassthrough(final String datastoreName,
+ final String cmHandle,
+ final String resourceIdentifier,
+ final String optionsParamInQuery,
+ final String topicParamInQuery,
+ final DataAccessRequest dataAccessRequest) {
+ log.info("Module set tag: {}", dataAccessRequest.getModuleSetTag());
+ if (DatastoreType.PASSTHROUGH_OPERATIONAL == DatastoreType.fromDatastoreName(datastoreName)) {
+ return dataAccessPassthroughOperational(resourceIdentifier, cmHandle, dataAccessRequest,
+ optionsParamInQuery, topicParamInQuery);
+ }
+ return dataAccessPassthroughRunning(resourceIdentifier, cmHandle, dataAccessRequest,
+ optionsParamInQuery, topicParamInQuery);
+ }
+
+ private ResponseEntity<Object> dataAccessPassthroughOperational(final String resourceIdentifier,
+ final String cmHandle,
+ final DataAccessRequest dataAccessRequest,
+ final String optionsParamInQuery,
+ final String topicParamInQuery) {
+ if (isReadOperation(dataAccessRequest)) {
+ if (hasTopic(topicParamInQuery)) {
+ return handleAsyncRequest(resourceIdentifier, cmHandle, dataAccessRequest, optionsParamInQuery,
+ topicParamInQuery);
+ }
+
+ final String resourceDataAsJson = dmiService.getResourceData(cmHandle, resourceIdentifier,
+ optionsParamInQuery, DmiService.RESTCONF_CONTENT_PASSTHROUGH_OPERATIONAL_QUERY_PARAM);
+ return ResponseEntity.ok(resourceDataAsJson);
+ }
+ return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
+ }
+
+ private ResponseEntity<Object> dataAccessPassthroughRunning(final String resourceIdentifier,
+ final String cmHandle,
+ final DataAccessRequest dataAccessRequest,
+ final String optionsParamInQuery,
+ final String topicParamInQuery) {
+ if (hasTopic(topicParamInQuery)) {
+ asyncTaskExecutor.executeAsyncTask(() ->
+ getSdncResponseForPassThroughRunning(
+ resourceIdentifier,
+ cmHandle,
+ dataAccessRequest,
+ optionsParamInQuery),
+ topicParamInQuery,
+ dataAccessRequest.getRequestId(),
+ dataAccessRequest.getOperation(),
+ timeOutInMillis
+ );
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+ }
+
+ final String sdncResponse =
+ getSdncResponseForPassThroughRunning(resourceIdentifier, cmHandle, dataAccessRequest, optionsParamInQuery);
+ return new ResponseEntity<>(sdncResponse, operationToHttpStatusMap.get(dataAccessRequest.getOperation()));
+ }
+
+ private String getSdncResponseForPassThroughRunning(final String resourceIdentifier,
+ final String cmHandle,
+ final DataAccessRequest dataAccessRequest,
+ final String optionsParamInQuery) {
+ if (isReadOperation(dataAccessRequest)) {
+ return dmiService.getResourceData(cmHandle, resourceIdentifier, optionsParamInQuery,
+ DmiService.RESTCONF_CONTENT_PASSTHROUGH_RUNNING_QUERY_PARAM);
+ }
+
+ return dmiService.writeData(dataAccessRequest.getOperation(), cmHandle, resourceIdentifier,
+ dataAccessRequest.getDataType(), dataAccessRequest.getData());
+ }
+
+ private boolean isReadOperation(final DataAccessRequest dataAccessRequest) {
+ return dataAccessRequest.getOperation() == null
+ || dataAccessRequest.getOperation().equals(DataAccessRequest.OperationEnum.READ);
+ }
+
+ private List<ModuleReference> convertRestObjectToJavaApiObject(
+ final ModuleResourcesReadRequest moduleResourcesReadRequest) {
+ return objectMapper
+ .convertValue(moduleResourcesReadRequest.getData().getModules(),
+ new TypeReference<List<ModuleReference>>() {});
+ }
+
+ private boolean hasTopic(final String topicParamInQuery) {
+ return !(topicParamInQuery == null || topicParamInQuery.isBlank());
+ }
+
+ private ResponseEntity<Object> handleAsyncRequest(final String resourceIdentifier,
+ final String cmHandle,
+ final DataAccessRequest dataAccessRequest,
+ final String optionsParamInQuery,
+ final String topicParamInQuery) {
+ asyncTaskExecutor.executeAsyncTask(() ->
+ dmiService.getResourceData(
+ cmHandle,
+ resourceIdentifier,
+ optionsParamInQuery,
+ DmiService.RESTCONF_CONTENT_PASSTHROUGH_OPERATIONAL_QUERY_PARAM),
+ topicParamInQuery,
+ dataAccessRequest.getRequestId(),
+ dataAccessRequest.getOperation(),
+ timeOutInMillis
+ );
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/handlers/DatastoreType.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/handlers/DatastoreType.java
new file mode 100644
index 00000000..3f280403
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/handlers/DatastoreType.java
@@ -0,0 +1,65 @@
+/*
+ * ============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.dmi.rest.controller.handlers;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+import org.onap.cps.ncmp.dmi.exception.InvalidDatastoreException;
+
+@Getter
+public enum DatastoreType {
+
+ PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"),
+ PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational");
+
+ DatastoreType(final String datastoreName) {
+ this.datastoreName = datastoreName;
+ }
+
+ private final String datastoreName;
+ private static final Map<String, DatastoreType> datastoreNameToDatastoreType = new HashMap<>();
+
+ static {
+ Arrays.stream(DatastoreType.values()).forEach(
+ type -> datastoreNameToDatastoreType.put(type.getDatastoreName(), type));
+ }
+
+ /**
+ * From datastore name get datastore type.
+ *
+ * @param datastoreName the datastore name
+ * @return the datastore type
+ */
+ public static DatastoreType fromDatastoreName(final String datastoreName) {
+
+ final DatastoreType datastoreType = datastoreNameToDatastoreType.get(datastoreName);
+
+ if (null == datastoreType) {
+ throw new InvalidDatastoreException(datastoreName + " is an invalid datastore name");
+ }
+
+ return datastoreType;
+ }
+
+}
+
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java
new file mode 100644
index 00000000..f0826a81
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java
@@ -0,0 +1,95 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2023 Nordix Foundation
+ * Modifications Copyright (C) 2022 Bell Canada
+ * ================================================================================
+ * 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.dmi.service;
+
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+import org.onap.cps.ncmp.dmi.exception.DmiException;
+import org.onap.cps.ncmp.dmi.model.DataAccessRequest;
+import org.onap.cps.ncmp.dmi.model.ModuleSet;
+import org.onap.cps.ncmp.dmi.model.YangResources;
+import org.onap.cps.ncmp.dmi.service.model.ModuleReference;
+
+
+
+/**
+ * Interface for handling Dmi plugin Data.
+ */
+public interface DmiService {
+
+ String RESTCONF_CONTENT_PASSTHROUGH_OPERATIONAL_QUERY_PARAM = "content=all";
+ String RESTCONF_CONTENT_PASSTHROUGH_RUNNING_QUERY_PARAM = "content=config";
+
+ /**
+ * This method fetches all modules for given Cm Handle.
+ *
+ * @param cmHandle cm-handle to fetch the modules information
+ * @return {@code String} returns all modules
+ * @throws DmiException can throw dmi exception
+ */
+ ModuleSet getModulesForCmHandle(String cmHandle) throws DmiException;
+
+ /**
+ * This method used to register the given {@code CmHandles} which contains list of {@code CmHandle} to cps
+ * repository.
+ *
+ * @param cmHandles list of cm-handles
+ */
+ void registerCmHandles(List<String> cmHandles);
+
+ /**
+ * Get module resources for the given cm handle and modules.
+ *
+ * @param cmHandle cmHandle
+ * @param modules a list of module data
+ * @return returns all yang resources
+ */
+ YangResources getModuleResources(String cmHandle, List<ModuleReference> modules);
+
+ /**
+ * This method use to fetch the resource data from cm handle for the given datastore and resource
+ * Identifier. Options query parameter are used to filter the response from network resource.
+ *
+ * @param cmHandle cm handle identifier
+ * @param resourceIdentifier resource identifier
+ * @param optionsParamInQuery options query parameter
+ * @param restconfContentQueryParam restconf content i.e. datastore to use
+ * @return {@code Object} response from network function
+ */
+ String getResourceData(@NotNull String cmHandle,
+ @NotNull String resourceIdentifier,
+ String optionsParamInQuery,
+ String restconfContentQueryParam);
+
+ /**
+ * Write resource data to sdnc (will default to 'content=config', does not need to be specified).
+ *
+ * @param cmHandle cmHandle
+ * @param resourceIdentifier resource identifier
+ * @param dataType accept header parameter
+ * @param data request data
+ * @return response from sdnc
+ */
+ String writeData(DataAccessRequest.OperationEnum operation, String cmHandle,
+ String resourceIdentifier, String dataType,
+ String data);
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java
new file mode 100644
index 00000000..6acbe09b
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java
@@ -0,0 +1,196 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2023 Nordix Foundation
+ * Modifications Copyright (C) 2021-2022 Bell Canada
+ * ================================================================================
+ * 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.dmi.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.dmi.config.DmiPluginConfig.DmiPluginProperties;
+import org.onap.cps.ncmp.dmi.exception.CmHandleRegistrationException;
+import org.onap.cps.ncmp.dmi.exception.DmiException;
+import org.onap.cps.ncmp.dmi.exception.HttpClientRequestException;
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException;
+import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException;
+import org.onap.cps.ncmp.dmi.model.DataAccessRequest;
+import org.onap.cps.ncmp.dmi.model.ModuleSet;
+import org.onap.cps.ncmp.dmi.model.ModuleSetSchemasInner;
+import org.onap.cps.ncmp.dmi.model.YangResource;
+import org.onap.cps.ncmp.dmi.model.YangResources;
+import org.onap.cps.ncmp.dmi.service.client.NcmpRestClient;
+import org.onap.cps.ncmp.dmi.service.model.CmHandleOperation;
+import org.onap.cps.ncmp.dmi.service.model.CreatedCmHandle;
+import org.onap.cps.ncmp.dmi.service.model.ModuleReference;
+import org.onap.cps.ncmp.dmi.service.model.ModuleSchema;
+import org.onap.cps.ncmp.dmi.service.operation.SdncOperations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class DmiServiceImpl implements DmiService {
+
+ private SdncOperations sdncOperations;
+ private NcmpRestClient ncmpRestClient;
+ private ObjectMapper objectMapper;
+ private DmiPluginProperties dmiPluginProperties;
+
+ /**
+ * Constructor.
+ *
+ * @param dmiPluginProperties dmiPluginProperties
+ * @param ncmpRestClient ncmpRestClient
+ * @param sdncOperations sdncOperations
+ * @param objectMapper objectMapper
+ */
+ public DmiServiceImpl(final DmiPluginProperties dmiPluginProperties,
+ final NcmpRestClient ncmpRestClient,
+ final SdncOperations sdncOperations, final ObjectMapper objectMapper) {
+ this.dmiPluginProperties = dmiPluginProperties;
+ this.ncmpRestClient = ncmpRestClient;
+ this.objectMapper = objectMapper;
+ this.sdncOperations = sdncOperations;
+ }
+
+ @Override
+ public ModuleSet getModulesForCmHandle(final String cmHandle) throws DmiException {
+ final Collection<ModuleSchema> moduleSchemas = sdncOperations.getModuleSchemasFromNode(cmHandle);
+ if (moduleSchemas.isEmpty()) {
+ throw new ModulesNotFoundException(cmHandle, "SDNC returned no modules for given cm-handle.");
+ } else {
+ final ModuleSet moduleSet = new ModuleSet();
+ moduleSchemas.forEach(moduleSchema ->
+ moduleSet.addSchemasItem(toModuleSetSchemas(moduleSchema)));
+ return moduleSet;
+ }
+ }
+
+ @Override
+ public YangResources getModuleResources(final String cmHandle, final List<ModuleReference> moduleReferences) {
+ final YangResources yangResources = new YangResources();
+ for (final ModuleReference moduleReference : moduleReferences) {
+ final String moduleRequest = createModuleRequest(moduleReference);
+ final ResponseEntity<String> responseEntity = sdncOperations.getModuleResource(cmHandle, moduleRequest);
+ if (responseEntity.getStatusCode() == HttpStatus.OK) {
+ final YangResource yangResource = YangResourceExtractor.toYangResource(moduleReference, responseEntity);
+ yangResources.add(yangResource);
+ } else if (responseEntity.getStatusCode() == HttpStatus.NOT_FOUND) {
+ log.error("SDNC did not return a module resource for the given cmHandle {}", cmHandle);
+ throw new ModuleResourceNotFoundException(cmHandle,
+ "SDNC did not return a module resource for the given cmHandle.");
+ } else {
+ log.error("Error occurred when getting module resources from SDNC for the given cmHandle {}", cmHandle);
+ throw new HttpClientRequestException(
+ cmHandle, responseEntity.getBody(), (HttpStatus) responseEntity.getStatusCode());
+ }
+ }
+ return yangResources;
+ }
+
+ @Override
+ public void registerCmHandles(final List<String> cmHandles) {
+ final CmHandleOperation cmHandleOperation = new CmHandleOperation();
+ cmHandleOperation.setDmiPlugin(dmiPluginProperties.getDmiServiceUrl());
+ final List<CreatedCmHandle> createdCmHandleList = new ArrayList<>();
+ for (final String cmHandle : cmHandles) {
+ final CreatedCmHandle createdCmHandle = new CreatedCmHandle();
+ createdCmHandle.setCmHandle(cmHandle);
+ createdCmHandleList.add(createdCmHandle);
+ }
+ cmHandleOperation.setCreatedCmHandles(createdCmHandleList);
+ final String cmHandlesJson;
+ try {
+ cmHandlesJson = objectMapper.writeValueAsString(cmHandleOperation);
+ } catch (final JsonProcessingException e) {
+ log.error("Parsing error occurred while converting cm-handles to JSON {}", cmHandles);
+ throw new DmiException("Internal Server Error.",
+ "Parsing error occurred while converting given cm-handles object list to JSON ");
+ }
+ final ResponseEntity<String> responseEntity = ncmpRestClient.registerCmHandlesWithNcmp(cmHandlesJson);
+ if (!responseEntity.getStatusCode().is2xxSuccessful()) {
+ throw new CmHandleRegistrationException(responseEntity.getBody());
+ }
+ }
+
+ private ModuleSetSchemasInner toModuleSetSchemas(final ModuleSchema moduleSchema) {
+ final ModuleSetSchemasInner moduleSetSchemas = new ModuleSetSchemasInner();
+ moduleSetSchemas.setModuleName(moduleSchema.getIdentifier());
+ moduleSetSchemas.setNamespace(moduleSchema.getNamespace());
+ moduleSetSchemas.setRevision(moduleSchema.getVersion());
+ return moduleSetSchemas;
+ }
+
+ @Override
+ public String getResourceData(final String cmHandle,
+ final String resourceIdentifier,
+ final String optionsParamInQuery,
+ final String restconfContentQueryParam) {
+ final ResponseEntity<String> responseEntity = sdncOperations.getResouceDataForOperationalAndRunning(cmHandle,
+ resourceIdentifier,
+ optionsParamInQuery,
+ restconfContentQueryParam);
+ return prepareAndSendResponse(responseEntity, cmHandle);
+ }
+
+ @Override
+ public String writeData(final DataAccessRequest.OperationEnum operation,
+ final String cmHandle,
+ final String resourceIdentifier,
+ final String dataType, final String data) {
+ final ResponseEntity<String> responseEntity =
+ sdncOperations.writeData(operation, cmHandle, resourceIdentifier, dataType, data);
+ return prepareAndSendResponse(responseEntity, cmHandle);
+ }
+
+ private String prepareAndSendResponse(final ResponseEntity<String> responseEntity, final String cmHandle) {
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
+ return responseEntity.getBody();
+ } else {
+ throw new HttpClientRequestException(cmHandle, responseEntity.getBody(),
+ (HttpStatus) responseEntity.getStatusCode());
+ }
+ }
+
+ private String createModuleRequest(final ModuleReference moduleReference) {
+ final Map<String, String> ietfNetconfModuleReferences = new LinkedHashMap<>();
+ ietfNetconfModuleReferences.put("ietf-netconf-monitoring:identifier", moduleReference.getName());
+ ietfNetconfModuleReferences.put("ietf-netconf-monitoring:version", moduleReference.getRevision());
+ final ObjectWriter objectWriter = objectMapper.writer().withRootName("ietf-netconf-monitoring:input");
+ final String moduleRequest;
+ try {
+ moduleRequest = objectWriter.writeValueAsString(ietfNetconfModuleReferences);
+ } catch (final JsonProcessingException e) {
+ log.error("JSON exception occurred when creating the module request for the given module reference {}",
+ moduleReference.getName());
+ throw new DmiException("Unable to process JSON.",
+ "JSON exception occurred when creating the module request.", e);
+ }
+ return moduleRequest;
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/YangResourceExtractor.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/YangResourceExtractor.java
new file mode 100644
index 00000000..d41240b9
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/YangResourceExtractor.java
@@ -0,0 +1,59 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.service;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException;
+import org.onap.cps.ncmp.dmi.model.YangResource;
+import org.onap.cps.ncmp.dmi.service.model.ModuleReference;
+import org.springframework.http.ResponseEntity;
+
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class YangResourceExtractor {
+
+ static YangResource toYangResource(final ModuleReference moduleReference,
+ final ResponseEntity<String> responseEntity) {
+ final YangResource yangResource = new YangResource();
+ yangResource.setModuleName(moduleReference.getName());
+ yangResource.setRevision(moduleReference.getRevision());
+ yangResource.setYangSource(extractYangSourceFromBody(responseEntity));
+ return yangResource;
+ }
+
+ private static String extractYangSourceFromBody(final ResponseEntity<String> responseEntity) {
+ final JsonObject responseBodyAsJsonObject = new Gson().fromJson(responseEntity.getBody(), JsonObject.class);
+ final JsonObject monitoringOutputAsJsonObject =
+ responseBodyAsJsonObject.getAsJsonObject("ietf-netconf-monitoring:output");
+ if (monitoringOutputAsJsonObject == null
+ || monitoringOutputAsJsonObject.getAsJsonPrimitive("data") == null) {
+ log.error("Error occurred when trying to parse the response body from sdnc {}", responseEntity.getBody());
+ throw new ModuleResourceNotFoundException(responseEntity.getBody(),
+ "Error occurred when trying to parse the response body from sdnc.");
+ }
+ return monitoringOutputAsJsonObject.getAsJsonPrimitive("data").getAsString();
+ }
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java
new file mode 100644
index 00000000..94783f3b
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/NcmpRestClient.java
@@ -0,0 +1,64 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.service.client;
+
+import org.onap.cps.ncmp.dmi.config.DmiConfiguration.CpsProperties;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@Component
+public class NcmpRestClient {
+
+ private CpsProperties cpsProperties;
+ private RestTemplate restTemplate;
+
+ public NcmpRestClient(final CpsProperties cpsProperties, final RestTemplate restTemplate) {
+ this.cpsProperties = cpsProperties;
+ this.restTemplate = restTemplate;
+ }
+
+ /**
+ * Register a cmHandle with NCMP using a HTTP call.
+ * @param jsonData json data
+ * @return the response entity
+ */
+ public ResponseEntity<String> registerCmHandlesWithNcmp(final String jsonData) {
+ final var ncmpRegistrationUrl = buildNcmpRegistrationUrl();
+ final var httpHeaders = new HttpHeaders();
+ httpHeaders.setBasicAuth(cpsProperties.getAuthUsername(), cpsProperties.getAuthPassword());
+ httpHeaders.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+ final var httpEntity = new HttpEntity<>(jsonData, httpHeaders);
+ return restTemplate.exchange(ncmpRegistrationUrl, HttpMethod.POST, httpEntity, String.class);
+ }
+
+ private String buildNcmpRegistrationUrl() {
+ return UriComponentsBuilder
+ .fromHttpUrl(cpsProperties.getBaseUrl())
+ .path(cpsProperties.getDmiRegistrationUrl())
+ .toUriString();
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java
new file mode 100644
index 00000000..179707ab
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java
@@ -0,0 +1,87 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.ncmp.dmi.service.client;
+
+import org.onap.cps.ncmp.dmi.config.DmiConfiguration.SdncProperties;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+@Component
+public class SdncRestconfClient {
+
+ private SdncProperties sdncProperties;
+ private RestTemplate restTemplate;
+
+ public SdncRestconfClient(final SdncProperties sdncProperties, final RestTemplate restTemplate) {
+ this.sdncProperties = sdncProperties;
+ this.restTemplate = restTemplate;
+ }
+
+ /**
+ * restconf get operation on sdnc.
+ *
+ * @param getResourceUrl sdnc get url
+ * @return the response entity
+ */
+ public ResponseEntity<String> getOperation(final String getResourceUrl) {
+ return getOperation(getResourceUrl, new HttpHeaders());
+ }
+
+ /**
+ * Overloaded restconf get operation on sdnc with http headers.
+ *
+ * @param getResourceUrl sdnc get url
+ * @param httpHeaders http headers
+ * @return the response entity
+ */
+ public ResponseEntity<String> getOperation(final String getResourceUrl, final HttpHeaders httpHeaders) {
+ return httpOperationWithJsonData(HttpMethod.GET, getResourceUrl, null, httpHeaders);
+ }
+
+ /**
+ * restconf http operations on sdnc.
+ *
+ * @param httpMethod HTTP Method
+ * @param resourceUrl sdnc resource url
+ * @param jsonData json data
+ * @param httpHeaders HTTP Headers
+ * @return response entity
+ */
+ public ResponseEntity<String> httpOperationWithJsonData(final HttpMethod httpMethod,
+ final String resourceUrl,
+ final String jsonData,
+ final HttpHeaders httpHeaders) {
+ final String sdncBaseUrl = sdncProperties.getBaseUrl();
+ final String sdncRestconfUrl = sdncBaseUrl.concat(resourceUrl);
+ httpHeaders.setBasicAuth(sdncProperties.getAuthUsername(), sdncProperties.getAuthPassword());
+ final HttpEntity<String> httpEntity;
+ if (jsonData == null) {
+ httpEntity = new HttpEntity<>(httpHeaders);
+ } else {
+ httpEntity = new HttpEntity<>(jsonData, httpHeaders);
+ }
+ return restTemplate.exchange(sdncRestconfUrl, httpMethod, httpEntity, String.class);
+ }
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CmHandleOperation.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CmHandleOperation.java
new file mode 100644
index 00000000..82eac92a
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CmHandleOperation.java
@@ -0,0 +1,35 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.service.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Setter
+public class CmHandleOperation {
+
+ private String dmiPlugin;
+ private List<CreatedCmHandle> createdCmHandles;
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CreatedCmHandle.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CreatedCmHandle.java
new file mode 100644
index 00000000..6ab6a01e
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/CreatedCmHandle.java
@@ -0,0 +1,36 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.service.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Setter
+public class CreatedCmHandle {
+
+ private String cmHandle;
+ private Map<String, String> cmHandleProperties;
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleReference.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleReference.java
new file mode 100644
index 00000000..75c37dff
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleReference.java
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.service.model;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Module Reference.
+ */
+@Getter
+@Setter
+@EqualsAndHashCode
+public class ModuleReference {
+
+ private String name;
+ private String revision;
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchema.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchema.java
new file mode 100644
index 00000000..c77e0e41
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchema.java
@@ -0,0 +1,36 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 Nordix Foundation
+ * Modifications Copyright (C) 2021 Bell Canada
+ * ================================================================================
+ * 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.dmi.service.model;
+
+import java.util.List;
+import lombok.Data;
+
+@Data
+public class ModuleSchema {
+
+ private String identifier;
+ private String version;
+ private String format;
+ private String namespace;
+ private List<String> location;
+
+}
diff --git a/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java
new file mode 100644
index 00000000..fd94e634
--- /dev/null
+++ b/dmi-service/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java
@@ -0,0 +1,270 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2023 Nordix Foundation
+ * Modifications Copyright (C) 2021-2022 Bell Canada
+ * ================================================================================
+ * 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.dmi.service.operation;
+
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum;
+
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.JsonPathException;
+import com.jayway.jsonpath.TypeRef;
+import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
+import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.onap.cps.ncmp.dmi.config.DmiConfiguration.SdncProperties;
+import org.onap.cps.ncmp.dmi.exception.SdncException;
+import org.onap.cps.ncmp.dmi.model.DataAccessRequest;
+import org.onap.cps.ncmp.dmi.service.client.SdncRestconfClient;
+import org.onap.cps.ncmp.dmi.service.model.ModuleSchema;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@Component
+public class SdncOperations {
+
+ private static final String TOPOLOGY_URL_TEMPLATE_DATA = "/rests/data/network-topology:network-topology/";
+ private static final String TOPOLOGY_URL_TEMPLATE_OPERATIONAL =
+ "/rests/operations/network-topology:network-topology/";
+ private static final String GET_SCHEMA_URL = "ietf-netconf-monitoring:netconf-state/schemas";
+ private static final String GET_SCHEMA_SOURCES_URL = "/ietf-netconf-monitoring:get-schema";
+ private static final String PATH_TO_MODULE_SCHEMAS = "$.ietf-netconf-monitoring:schemas.schema";
+ private static final int QUERY_PARAM_SPLIT_LIMIT = 2;
+ private static final int QUERY_PARAM_VALUE_INDEX = 1;
+ private static final int QUERY_PARAM_NAME_INDEX = 0;
+
+ private static EnumMap<OperationEnum, HttpMethod> operationToHttpMethodMap = new EnumMap<>(OperationEnum.class);
+
+ static {
+ operationToHttpMethodMap.put(OperationEnum.READ, HttpMethod.GET);
+ operationToHttpMethodMap.put(OperationEnum.CREATE, HttpMethod.POST);
+ operationToHttpMethodMap.put(OperationEnum.PATCH, HttpMethod.PATCH);
+ operationToHttpMethodMap.put(OperationEnum.UPDATE, HttpMethod.PUT);
+ operationToHttpMethodMap.put(OperationEnum.DELETE, HttpMethod.DELETE);
+ }
+
+ private final SdncProperties sdncProperties;
+ private final SdncRestconfClient sdncRestconfClient;
+ private final String topologyUrlData;
+ private final String topologyUrlOperational;
+
+ private Configuration jsonPathConfiguration = Configuration.builder()
+ .mappingProvider(new JacksonMappingProvider())
+ .jsonProvider(new JacksonJsonProvider())
+ .build();
+
+ /**
+ * Constructor for {@code SdncOperations}. This method also manipulates url properties.
+ *
+ * @param sdncProperties {@code SdncProperties}
+ * @param sdncRestconfClient {@code SdncRestconfClient}
+ */
+ public SdncOperations(final SdncProperties sdncProperties, final SdncRestconfClient sdncRestconfClient) {
+ this.sdncProperties = sdncProperties;
+ this.sdncRestconfClient = sdncRestconfClient;
+ topologyUrlOperational = getTopologyUrlOperational();
+ topologyUrlData = getTopologyUrlData();
+ }
+
+ /**
+ * This method fetches list of modules usind sdnc client.
+ *
+ * @param nodeId node id for node
+ * @return a collection of module schemas
+ */
+ public Collection<ModuleSchema> getModuleSchemasFromNode(final String nodeId) {
+ final String urlWithNodeId = prepareGetSchemaUrl(nodeId);
+ final ResponseEntity<String> modulesResponseEntity = sdncRestconfClient.getOperation(urlWithNodeId);
+ if (modulesResponseEntity.getStatusCode() == HttpStatus.OK) {
+ final String modulesResponseBody = modulesResponseEntity.getBody();
+ if (modulesResponseBody == null || modulesResponseBody.isBlank()) {
+ return Collections.emptyList();
+ }
+ return convertToModuleSchemas(modulesResponseBody);
+ }
+ throw new SdncException(
+ String.format("SDNC failed to get Modules Schema for node %s", nodeId),
+ (HttpStatus) modulesResponseEntity.getStatusCode(), modulesResponseEntity.getBody());
+ }
+
+ /**
+ * Get module schema.
+ *
+ * @param nodeId node ID
+ * @param moduleProperties module properties
+ * @return response entity
+ */
+ public ResponseEntity<String> getModuleResource(final String nodeId, final String moduleProperties) {
+ final String getYangResourceUrl = prepareGetOperationSchemaUrl(nodeId);
+ final HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.setContentType(MediaType.APPLICATION_JSON);
+ return sdncRestconfClient.httpOperationWithJsonData(
+ HttpMethod.POST, getYangResourceUrl, moduleProperties, httpHeaders);
+ }
+
+ /**
+ * This method fetches the resource data for given node identifier on given resource using sdnc client.
+ *
+ * @param nodeId network resource identifier
+ * @param resourceId resource identifier
+ * @param optionsParamInQuery fields query
+ * @param restConfContentQueryParam restConf content query param
+ * @return {@code ResponseEntity} response entity
+ */
+ public ResponseEntity<String> getResouceDataForOperationalAndRunning(final String nodeId,
+ final String resourceId,
+ final String optionsParamInQuery,
+ final String restConfContentQueryParam) {
+ final String getResourceDataUrl = prepareResourceDataUrl(nodeId,
+ resourceId, buildQueryParamMap(optionsParamInQuery, restConfContentQueryParam));
+ return sdncRestconfClient.getOperation(getResourceDataUrl);
+ }
+
+ /**
+ * Write resource data.
+ *
+ * @param nodeId network resource identifier
+ * @param resourceId resource identifier
+ * @param contentType http content type
+ * @param requestData request data
+ * @return {@code ResponseEntity} response entity
+ */
+ public ResponseEntity<String> writeData(final DataAccessRequest.OperationEnum operation,
+ final String nodeId,
+ final String resourceId,
+ final String contentType,
+ final String requestData) {
+ final String getResourceDataUrl = prepareWriteUrl(nodeId, resourceId);
+ final HttpHeaders httpHeaders = new HttpHeaders();
+ httpHeaders.setContentType(MediaType.parseMediaType(contentType));
+ final HttpMethod httpMethod = operationToHttpMethodMap.get(operation);
+ return sdncRestconfClient.httpOperationWithJsonData(httpMethod, getResourceDataUrl, requestData, httpHeaders);
+ }
+
+ private MultiValueMap<String, String> buildQueryParamMap(final String optionsParamInQuery,
+ final String restConfContentQueryParam) {
+ return getQueryParamsAsMap(optionsParamInQuery, restConfContentQueryParam);
+ }
+
+ private MultiValueMap<String, String> getQueryParamsAsMap(final String optionsParamInQuery,
+ final String restConfContentQueryParam) {
+ final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
+ if (optionsParamInQuery != null && !optionsParamInQuery.isBlank()) {
+ queryParams.setAll(extractQueryParams(optionsParamInQuery, restConfContentQueryParam));
+ }
+ return queryParams;
+ }
+
+ private String stripParenthesisFromOptionsQuery(final String optionsParamInQuery) {
+ return optionsParamInQuery.substring(1, optionsParamInQuery.length() - 1);
+ }
+
+ private String prepareGetSchemaUrl(final String nodeId) {
+ return addResource(addTopologyDataUrlwithNode(nodeId), GET_SCHEMA_URL);
+ }
+
+ private String prepareWriteUrl(final String nodeId, final String resourceId) {
+ return addResource(addTopologyDataUrlwithNode(nodeId), resourceId);
+ }
+
+ private String prepareGetOperationSchemaUrl(final String nodeId) {
+ return UriComponentsBuilder.fromUriString(topologyUrlOperational)
+ .pathSegment("node={nodeId}")
+ .pathSegment("yang-ext:mount")
+ .path(GET_SCHEMA_SOURCES_URL)
+ .buildAndExpand(nodeId).toUriString();
+ }
+
+ private String prepareResourceDataUrl(final String nodeId,
+ final String resourceId,
+ final MultiValueMap<String, String> queryMap) {
+ return addQuery(addResource(addTopologyDataUrlwithNode(nodeId), resourceId), queryMap);
+ }
+
+ private String addResource(final String url, final String resourceId) {
+ return UriComponentsBuilder.fromUriString(url)
+ .pathSegment(resourceId)
+ .buildAndExpand().toUriString();
+ }
+
+ private String addQuery(final String url, final MultiValueMap<String, String> queryMap) {
+ return UriComponentsBuilder.fromUriString(url)
+ .queryParams(queryMap)
+ .buildAndExpand().toUriString();
+ }
+
+ private String addTopologyDataUrlwithNode(final String nodeId) {
+ return UriComponentsBuilder.fromUriString(topologyUrlData)
+ .pathSegment("node={nodeId}")
+ .pathSegment("yang-ext:mount")
+ .buildAndExpand(nodeId).toUriString();
+ }
+
+ private List<ModuleSchema> convertToModuleSchemas(final String modulesListAsJson) {
+ try {
+ return JsonPath.using(jsonPathConfiguration).parse(modulesListAsJson).read(
+ PATH_TO_MODULE_SCHEMAS, new TypeRef<>() {
+ });
+ } catch (final JsonPathException jsonPathException) {
+ throw new SdncException("SDNC Response processing failed",
+ "SDNC response is not in the expected format.", jsonPathException);
+ }
+ }
+
+ private String getTopologyUrlData() {
+ return UriComponentsBuilder.fromUriString(TOPOLOGY_URL_TEMPLATE_DATA)
+ .path("topology={topologyId}")
+ .buildAndExpand(this.sdncProperties.getTopologyId()).toUriString();
+ }
+
+ private String getTopologyUrlOperational() {
+ return UriComponentsBuilder.fromUriString(
+ TOPOLOGY_URL_TEMPLATE_OPERATIONAL)
+ .path("topology={topologyId}")
+ .buildAndExpand(this.sdncProperties.getTopologyId()).toUriString();
+ }
+
+ private Map<String, String> extractQueryParams(final String optionsParamInQuery,
+ final String restConfContentQueryParam) {
+ final String QueryParamsAsString = stripParenthesisFromOptionsQuery(optionsParamInQuery)
+ + "," + restConfContentQueryParam;
+ final String[] splitTempQueryByComma = QueryParamsAsString.split(",");
+ return Arrays.stream(splitTempQueryByComma)
+ .map(queryParamPair -> queryParamPair.split("=", QUERY_PARAM_SPLIT_LIMIT))
+ .filter(queryParam -> queryParam.length > 1)
+ .collect(Collectors.toMap(
+ queryParam -> queryParam[QUERY_PARAM_NAME_INDEX],
+ queryParam -> queryParam[QUERY_PARAM_VALUE_INDEX]));
+ }
+}
diff --git a/dmi-service/src/main/resources/application.yml b/dmi-service/src/main/resources/application.yml
new file mode 100644
index 00000000..003aa191
--- /dev/null
+++ b/dmi-service/src/main/resources/application.yml
@@ -0,0 +1,128 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2021-2024 Nordix Foundation
+# Modifications Copyright (C) 2021 Bell Canada.
+# ================================================================================
+# 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=========================================================
+
+server:
+ port: 8080
+
+dmi:
+ service:
+ url: ${DMI_SERVICE_URL}
+ name: ${DMI_SERVICE_NAME:ncmp-dmi-plugin}
+
+rest:
+ api:
+ dmi-base-path: /dmi
+
+security:
+ permit-uri: /actuator/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/**,/v3/api-docs/**
+ auth:
+ username: ${DMI_USERNAME}
+ password: ${DMI_PASSWORD}
+
+# When updating to sprinboot 2.6.4 an exception would occur when starting the container
+# "Failed to start bean 'documentationPluginsBootstrapper'.
+# This is a known issue with springfox and springboot introduced in 2.6.x:
+# https://github.com/springfox/springfox/issues/3462
+spring:
+ application:
+ name: ncmp-dmi-plugin
+ mvc:
+ pathmatch:
+ matching-strategy: ANT_PATH_MATCHER
+ kafka:
+ bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVER:localhost:9092}
+ security:
+ protocol: PLAINTEXT
+ producer:
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ value-serializer: io.cloudevents.kafka.CloudEventSerializer
+ client-id: ncmp-dmi-plugin
+ consumer:
+ group-id: ${NCMP_CONSUMER_GROUP_ID:ncmp-group}
+ key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+ value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+ properties:
+ spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
+ spring.deserializer.value.delegate.class: io.cloudevents.kafka.CloudEventDeserializer
+ spring.json.use.type.headers: false
+
+ jackson:
+ serialization:
+ FAIL_ON_EMPTY_BEANS: false
+
+app:
+ ncmp:
+ async:
+ topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
+ dmi:
+ avc:
+ cm-subscription-dmi-in: ${CM_SUBSCRIPTION_DMI_IN_TOPIC:ncmp-dmi-cm-avc-subscription}
+ cm-subscription-dmi-out: ${CM_SUBSCRIPTION_DMI_OUT_TOPIC:dmi-ncmp-cm-avc-subscription}
+
+notification:
+ async:
+ executor:
+ time-out-value-in-ms: 2000
+
+# Actuator
+management:
+ endpoints:
+ web:
+ exposure:
+ include: info,health,loggers,prometheus
+ endpoint:
+ health:
+ show-details: always
+ # kubernetes probes: liveness and readiness
+ probes:
+ enabled: true
+ loggers:
+ enabled: true
+
+cps-core:
+ baseUrl: http://${CPS_CORE_HOST}:${CPS_CORE_PORT}
+ dmiRegistrationUrl : /ncmpInventory/v1/ch
+ auth:
+ username: ${CPS_CORE_USERNAME}
+ password: ${CPS_CORE_PASSWORD}
+
+sdnc:
+ baseUrl: http://${SDNC_HOST}:${SDNC_PORT}
+ topologyId: ${SDNC_TOPOLOGY_ID:topology-netconf}
+ auth:
+ username: ${SDNC_USERNAME}
+ password: ${SDNC_PASSWORD}
+
+logging:
+ format: json
+ level:
+ org.springframework: ERROR
+ org.onap.cps: DEBUG
+ pattern:
+ console: "%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"
+ file: "%d %p %c{1.} [%t] %m%n"
+ file: dmi.log
+
+springdoc:
+ swagger-ui:
+ disable-swagger-default-url: true
+ urlsPrimaryName: query
+ urls:
+ - name: query
+ url: /api-docs/openapi.yaml
diff --git a/dmi-service/src/main/resources/logback-spring.xml b/dmi-service/src/main/resources/logback-spring.xml
new file mode 100644
index 00000000..355209b4
--- /dev/null
+++ b/dmi-service/src/main/resources/logback-spring.xml
@@ -0,0 +1,75 @@
+<!--
+ ============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=========================================================
+-->
+
+<configuration scan="true" scanPeriod="30 seconds" debug="false">
+
+ <include resource="org/springframework/boot/logging/logback/defaults.xml" />
+ <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
+
+ <springProperty scope="context" name="springAppName" source="spring.application.name"/>
+ <springProperty scope="context" name="username" source="security.auth.username"/>
+ <springProperty scope="context" name="loggingFormat" source="logging.format"/>
+
+ <property name="currentTimeStamp" value="%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX,UTC}"/>
+
+ <appender name="jsonConsole"
+ class="ch.qos.logback.core.ConsoleAppender">
+ <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
+ <providers>
+ <pattern>
+ <omitEmptyFields>true</omitEmptyFields>
+ <pattern>
+ {
+ "logTimeStamp": "${currentTimeStamp:-}",
+ "logTypeName": "",
+ "logLevel": "%level",
+ "traceId": "%X{traceId:-}",
+ "statusCode": "",
+ "principalId": "${username:-}",
+ "serviceName": "${springAppName:-}",
+ "message": "%message",
+ "spanId": "%X{spanId:-}",
+ "processId": "${PID:-}",
+ "threadName": "%thread",
+ "class": "%logger{40}",
+ "exception": "%wEx"
+ }
+ </pattern>
+ </pattern>
+ </providers>
+ </encoder>
+ </appender>
+
+ <appender name="asyncConsole" class="ch.qos.logback.classic.AsyncAppender">
+ <if condition='property("loggingFormat").equalsIgnoreCase("json")'>
+ <then>
+ <appender-ref ref="jsonConsole"/>
+ </then>
+ <else>
+ <appender-ref ref="CONSOLE"/>
+ </else>
+ </if>
+ </appender>
+
+ <root level="INFO">
+ <appender-ref ref="asyncConsole"/>
+ </root>
+
+</configuration>
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/api/kafka/MessagingBaseSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/api/kafka/MessagingBaseSpec.groovy
new file mode 100644
index 00000000..13dd043d
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/api/kafka/MessagingBaseSpec.groovy
@@ -0,0 +1,79 @@
+/*
+ * ============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.dmi.api.kafka
+
+import io.cloudevents.CloudEvent
+import io.cloudevents.kafka.CloudEventDeserializer
+import io.cloudevents.kafka.CloudEventSerializer
+import org.apache.kafka.clients.consumer.KafkaConsumer
+import org.apache.kafka.common.serialization.StringDeserializer
+import org.apache.kafka.common.serialization.StringSerializer
+import org.springframework.kafka.core.DefaultKafkaProducerFactory
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.kafka.support.serializer.JsonSerializer
+import org.springframework.test.context.DynamicPropertyRegistry
+import org.springframework.test.context.DynamicPropertySource
+import org.testcontainers.containers.KafkaContainer
+import org.testcontainers.utility.DockerImageName
+import spock.lang.Specification
+
+class MessagingBaseSpec extends Specification {
+
+ def setupSpec() {
+ kafkaTestContainer.start()
+ }
+
+ def cleanupSpec() {
+ kafkaTestContainer.stop()
+ }
+
+ static kafkaTestContainer = new KafkaContainer(DockerImageName.parse('registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1').asCompatibleSubstituteFor('confluentinc/cp-kafka'))
+
+ def producerConfigProperties(valueSerializer) {
+ return [('bootstrap.servers'): kafkaTestContainer.getBootstrapServers().split(',')[0],
+ ('retries') : 0,
+ ('batch-size') : 16384,
+ ('linger.ms') : 1,
+ ('buffer.memory') : 33554432,
+ ('key.serializer') : StringSerializer,
+ ('value.serializer') : valueSerializer]
+ }
+
+ def consumerConfigProperties(consumerGroupId, valueDeserializer) {
+ return [('bootstrap.servers') : kafkaTestContainer.getBootstrapServers().split(',')[0],
+ ('key.deserializer') : StringDeserializer,
+ ('value.deserializer'): valueDeserializer,
+ ('auto.offset.reset') : 'earliest',
+ ('group.id') : consumerGroupId
+ ]
+ }
+
+ def kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory<Integer, String>(producerConfigProperties(JsonSerializer)))
+ def kafkaConsumer = new KafkaConsumer<>(consumerConfigProperties('ncmp-test-group', StringDeserializer))
+
+ def cloudEventKafkaTemplate = new KafkaTemplate(new DefaultKafkaProducerFactory<String, CloudEvent>(producerConfigProperties(CloudEventSerializer)))
+ def cloudEventKafkaConsumer = new KafkaConsumer<>(consumerConfigProperties('ncmp-test-group', CloudEventDeserializer))
+
+ @DynamicPropertySource
+ static void registerKafkaProperties(DynamicPropertyRegistry dynamicPropertyRegistry) {
+ dynamicPropertyRegistry.add('spring.kafka.bootstrap-servers', kafkaTestContainer::getBootstrapServers)
+ }
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy
new file mode 100644
index 00000000..9d80b71f
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiConfigurationSpec.groovy
@@ -0,0 +1,66 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.config
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.boot.web.client.RestTemplateBuilder
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [DmiConfiguration.CpsProperties, DmiConfiguration.SdncProperties])
+class DmiConfigurationSpec extends Specification {
+
+ @Autowired
+ DmiConfiguration.CpsProperties cpsProperties
+
+ @Autowired
+ DmiConfiguration.SdncProperties sdncProperties
+
+ def 'CPS properties configuration.'() {
+ expect: 'CPS properties are set to values in test configuration yaml file'
+ cpsProperties.baseUrl == 'some url for cps'
+ cpsProperties.dmiRegistrationUrl == 'some registration url'
+ cpsProperties.authUsername == 'some cps core user'
+ cpsProperties.authPassword == 'some cps core password'
+ }
+
+ def 'SDNC properties configuration.'() {
+ expect: 'SDNC properties are set to values in test configuration yaml file'
+ sdncProperties.authUsername == 'test'
+ sdncProperties.authPassword == 'test'
+ sdncProperties.baseUrl == 'http://test'
+ sdncProperties.topologyId == 'test-topology'
+ }
+
+ def 'Rest template building.'() {
+ given: 'a DMI configuration'
+ DmiConfiguration objectUnderTest = new DmiConfiguration()
+ and: 'a rest template builder'
+ RestTemplateBuilder mockRestTemplateBuilder = Spy(RestTemplateBuilder)
+ when: 'rest template method is invoked'
+ objectUnderTest.restTemplate(mockRestTemplateBuilder)
+ then: 'DMI configuration uses the build method on the template builder'
+ 1 * mockRestTemplateBuilder.build()
+ }
+
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiPluginConfigSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiPluginConfigSpec.groovy
new file mode 100644
index 00000000..c09403d7
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/DmiPluginConfigSpec.groovy
@@ -0,0 +1,52 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.dmi.config
+
+import org.springdoc.core.models.GroupedOpenApi
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+@SpringBootTest
+@ContextConfiguration(classes = [DmiPluginConfig.DmiPluginProperties])
+class DmiPluginConfigSpec extends Specification {
+
+ @Autowired
+ DmiPluginConfig.DmiPluginProperties dmiPluginProperties
+
+ def 'DMI plugin properties configuration.'() {
+ expect: 'DMI plugin properties are set to values in test configuration yaml file'
+ dmiPluginProperties.dmiServiceUrl == 'some url for the dmi service'
+ }
+
+ def 'DMI plugin api creation.'() {
+ given: 'a DMI plugin configuration'
+ DmiPluginConfig objectUnderTest = new DmiPluginConfig()
+ when: 'the api method is invoked'
+ def result = objectUnderTest.api()
+ then: 'a spring web plugin docket is returned'
+ result instanceof GroupedOpenApi
+ and: 'it is named "dmi-plugin-api"'
+ result.group == 'dmi-plugin-api'
+ }
+
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfigSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfigSpec.groovy
new file mode 100644
index 00000000..a3bf52b3
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/config/kafka/KafkaConfigSpec.groovy
@@ -0,0 +1,62 @@
+/*
+ * ============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.dmi.config.kafka
+
+import io.cloudevents.CloudEvent
+import io.cloudevents.kafka.CloudEventDeserializer
+import io.cloudevents.kafka.CloudEventSerializer
+import org.spockframework.spring.EnableSharedInjection
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.autoconfigure.kafka.KafkaProperties
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.kafka.support.serializer.JsonDeserializer
+import org.springframework.kafka.support.serializer.JsonSerializer
+import spock.lang.Shared
+import spock.lang.Specification
+
+@SpringBootTest(classes = [KafkaProperties, KafkaConfig])
+@EnableSharedInjection
+@EnableConfigurationProperties
+class KafkaConfigSpec extends Specification {
+
+ @Shared
+ @Autowired
+ KafkaTemplate<String, String> legacyEventKafkaTemplate
+
+ @Shared
+ @Autowired
+ KafkaTemplate<String, CloudEvent> cloudEventKafkaTemplate
+
+ def 'Verify kafka template serializer and deserializer configuration for #eventType.'() {
+ expect: 'kafka template is instantiated'
+ assert kafkaTemplateInstance.properties['beanName'] == beanName
+ and: 'verify event key and value serializer'
+ assert kafkaTemplateInstance.properties['producerFactory'].configs['value.serializer'].asType(String.class).contains(valueSerializer.getCanonicalName())
+ and: 'verify event key and value deserializer'
+ assert kafkaTemplateInstance.properties['consumerFactory'].configs['spring.deserializer.value.delegate.class'].asType(String.class).contains(delegateDeserializer.getCanonicalName())
+ where: 'the following event type is used'
+ eventType | kafkaTemplateInstance || beanName | valueSerializer | delegateDeserializer
+ 'legacy event' | legacyEventKafkaTemplate || 'legacyEventKafkaTemplate' | JsonSerializer | JsonDeserializer
+ 'cloud event' | cloudEventKafkaTemplate || 'cloudEventKafkaTemplate' | CloudEventSerializer | CloudEventDeserializer
+ }
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestControllerSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestControllerSpec.groovy
new file mode 100644
index 00000000..6c05f6f3
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/datajobs/rest/controller/DmiDatajobsRestControllerSpec.groovy
@@ -0,0 +1,94 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.datajobs.rest.controller
+
+import org.onap.cps.ncmp.dmi.config.WebSecurityConfig
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.context.annotation.Import
+import org.springframework.http.HttpStatus
+import org.springframework.security.test.context.support.WithMockUser
+import org.springframework.test.web.servlet.MockMvc
+import spock.lang.Specification
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+
+@Import(WebSecurityConfig)
+@WebMvcTest(DmiDatajobsRestController.class)
+@WithMockUser
+class DmiDatajobsRestControllerSpec extends Specification{
+
+ @Autowired
+ private MockMvc mvc
+
+ @Value('${rest.api.dmi-base-path}/v1')
+ def basePathV1
+
+ def 'write request should return 501 HTTP Status' () {
+ given: 'URL to write a data job'
+ def getModuleUrl = "${basePathV1}/writeJob/001"
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(getModuleUrl)
+ .contentType('application/3gpp-json-patch+json')
+ ).andReturn().response
+ then: 'response value is Not Implemented'
+ response.status == HttpStatus.NOT_IMPLEMENTED.value()
+ }
+
+ def 'read request should return 501 HTTP Status' () {
+ given: 'URL to write a data job'
+ def getModuleUrl = "${basePathV1}/readJob/001"
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(getModuleUrl)
+ .contentType('application/3gpp-json-patch+json')
+ ).andReturn().response
+ then: 'response value is Not Implemented'
+ response.status == HttpStatus.NOT_IMPLEMENTED.value()
+ }
+
+ def 'get status request should return 501 HTTP Status' () {
+ given: 'URL to get the status of a data job'
+ def getStatus = "${basePathV1}/dataJob/some-identifier/dataProducerJob/some-producer-job-identifier/status?dataProducerId=some-data-producer-identifier"
+ when: 'the request is performed'
+ def response = mvc.perform(
+ get(getStatus)
+ .contentType('application/json')
+ ).andReturn().response
+ then: 'response value is Not Implemented'
+ response.status == HttpStatus.NOT_IMPLEMENTED.value()
+ }
+
+ def 'get result request should return 501 HTTP Status' () {
+ given: 'URL to get the result of a data job'
+ def getStatus = "${basePathV1}/dataJob/some-identifier/dataProducerJob/some-producer-job-identifier/result?dataProducerId=some-data-producer-identifier&destination=some-destination"
+ when: 'the request is performed'
+ def response = mvc.perform(
+ get(getStatus)
+ .contentType('application/json')
+ ).andReturn().response
+ then: 'response value is Not Implemented'
+ response.status == HttpStatus.NOT_IMPLEMENTED.value()
+ }
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy
new file mode 100644
index 00000000..12ca05cf
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/async/AsyncTaskExecutorIntegrationSpec.groovy
@@ -0,0 +1,110 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022-2023 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.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
+import org.springframework.http.HttpStatus
+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
+@DirtiesContext
+class AsyncTaskExecutorIntegrationSpec extends MessagingBaseSpec {
+
+ @SpringBean
+ DmiAsyncRequestResponseEventProducer cpsAsyncRequestResponseEventProducer =
+ new DmiAsyncRequestResponseEventProducer(kafkaTemplate)
+
+ def spiedObjectMapper = Spy(ObjectMapper)
+ def mockSupplier = Mock(Supplier)
+
+ def objectUnderTest = new AsyncTaskExecutor(cpsAsyncRequestResponseEventProducer)
+
+ private static final String TEST_TOPIC = 'test-topic'
+
+ def setup() {
+ cpsAsyncRequestResponseEventProducer.dmiNcmpTopic = TEST_TOPIC
+ kafkaConsumer.subscribe([TEST_TOPIC] as List<String>)
+ }
+
+ def cleanup() {
+ kafkaConsumer.close()
+ }
+
+ def 'Publish and Subscribe message - success'() {
+ when: 'a successful event is published'
+ objectUnderTest.publishAsyncEvent(TEST_TOPIC, '12345','{}', 'OK', '200')
+ and: 'the topic is polled'
+ 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() == 'OK'
+ assert event.getEventContent().getResponseCode() == '200'
+ }
+
+ def 'Publish and Subscribe message - failure'() {
+ when: 'a failure event is published'
+ def exception = new HttpClientRequestException('some cm handle', 'Node not found', HttpStatus.INTERNAL_SERVER_ERROR)
+ objectUnderTest.publishAsyncFailureEvent(TEST_TOPIC, '67890', exception)
+ and: 'the topic is polled'
+ 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 '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/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/avc/AvcEventExecutorIntegrationSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/avc/AvcEventExecutorIntegrationSpec.groovy
new file mode 100644
index 00000000..a7557bb9
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/avc/AvcEventExecutorIntegrationSpec.groovy
@@ -0,0 +1,61 @@
+/*
+ * ============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.dmi.notifications.avc
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.cloudevents.core.CloudEventUtils
+import io.cloudevents.jackson.PojoCloudEventDataMapper
+import org.onap.cps.ncmp.dmi.api.kafka.MessagingBaseSpec
+import org.onap.cps.ncmp.events.avc1_0_0.AvcEvent
+import org.spockframework.spring.SpringBean
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.annotation.DirtiesContext
+import org.testcontainers.spock.Testcontainers
+
+import java.time.Duration
+
+@SpringBootTest(classes = [DmiDataAvcEventProducer])
+@Testcontainers
+@DirtiesContext
+class AvcEventExecutorIntegrationSpec extends MessagingBaseSpec {
+
+ @SpringBean
+ DmiDataAvcEventProducer dmiDataAvcEventProducer = new DmiDataAvcEventProducer(cloudEventKafkaTemplate)
+
+ def dmiService = new DmiDataAvcEventSimulationController(dmiDataAvcEventProducer)
+
+ def objectMapper = new ObjectMapper()
+
+ def 'Publish Avc Event'() {
+ given: 'a simulated event'
+ dmiService.simulateEvents(1)
+ and: 'a consumer subscribed to dmi-cm-events topic'
+ cloudEventKafkaConsumer.subscribe(['dmi-cm-events'])
+ when: 'the next event record is consumed'
+ def record = cloudEventKafkaConsumer.poll(Duration.ofMillis(1500)).iterator().next()
+ then: 'record has correct topic'
+ assert record.topic == 'dmi-cm-events'
+ and: 'the record value can be mapped to an avcEvent'
+ def dmiDataAvcEvent = record.value()
+ def convertedAvcEvent = CloudEventUtils.mapData(dmiDataAvcEvent, PojoCloudEventDataMapper.from(objectMapper, AvcEvent.class)).getValue()
+ assert convertedAvcEvent != null
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy
new file mode 100644
index 00000000..f1f476f6
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiInEventConsumerSpec.groovy
@@ -0,0 +1,149 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.cmsubscription
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.cloudevents.CloudEvent
+import io.cloudevents.core.builder.CloudEventBuilder
+import org.apache.kafka.clients.consumer.ConsumerRecord
+import org.onap.cps.ncmp.dmi.TestUtils
+import org.onap.cps.ncmp.dmi.api.kafka.MessagingBaseSpec
+import org.onap.cps.ncmp.dmi.notifications.cmsubscription.model.CmNotificationSubscriptionStatus
+import org.onap.cps.ncmp.dmi.notifications.mapper.CloudEventMapper
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.Data
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.ncmp_to_dmi.CmNotificationSubscriptionDmiInEvent
+import org.slf4j.LoggerFactory
+import org.spockframework.spring.SpringBean
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.annotation.DirtiesContext
+import org.testcontainers.spock.Testcontainers
+
+import java.sql.Timestamp
+import java.time.Duration
+import java.time.OffsetDateTime
+import java.time.ZoneId
+
+
+@Testcontainers
+@DirtiesContext
+class CmNotificationSubscriptionDmiInEventConsumerSpec extends MessagingBaseSpec {
+ def objectMapper = new ObjectMapper()
+ def testTopic = 'dmi-ncmp-cm-avc-subscription'
+ def testDmiName = 'test-ncmp-dmi'
+
+ @SpringBean
+ CmNotificationSubscriptionDmiInEventConsumer objectUnderTest = new CmNotificationSubscriptionDmiInEventConsumer(cloudEventKafkaTemplate)
+
+ def logger = Spy(ListAppender<ILoggingEvent>)
+
+ void setup() {
+ ((Logger) LoggerFactory.getLogger(CloudEventMapper.class)).addAppender(logger)
+ logger.start()
+ }
+
+ void cleanup() {
+ ((Logger) LoggerFactory.getLogger(CloudEventMapper.class)).detachAndStopAllAppenders()
+ }
+
+ def 'Sends subscription cloud event response successfully.'() {
+ given: 'an subscription event response'
+ objectUnderTest.dmiName = testDmiName
+ objectUnderTest.cmNotificationSubscriptionDmiOutTopic = testTopic
+ def correlationId = 'test-subscriptionId#test-ncmp-dmi'
+ 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, subscriptionAcceptanceType)
+ and: 'topic is polled'
+ def records = kafkaConsumer.poll(Duration.ofMillis(1500))
+ 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 | '104' | 'REJECTED'
+ }
+
+ def 'Consume valid message.'() {
+ given: 'an event'
+ objectUnderTest.dmiName = testDmiName
+ def eventKey = UUID.randomUUID().toString()
+ def timestamp = new Timestamp(1679521929511)
+ def jsonData = TestUtils.getResourceFileContent('cmNotificationSubscriptionCreationEvent.json')
+ def subscriptionEvent = objectMapper.readValue(jsonData, CmNotificationSubscriptionDmiInEvent.class)
+ objectUnderTest.cmNotificationSubscriptionDmiOutTopic = testTopic
+ def cloudEvent = CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withSource(URI.create('test-ncmp-dmi'))
+ .withType(subscriptionType)
+ .withDataSchema(URI.create("urn:cps:" + CmNotificationSubscriptionDmiInEvent.class.getName() + ":1.0.0"))
+ .withExtension("correlationid", eventKey)
+ .withTime(OffsetDateTime.ofInstant(timestamp.toInstant(), ZoneId.of("UTC")))
+ .withData(objectMapper.writeValueAsBytes(subscriptionEvent)).build()
+ def testEventSent = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, eventKey, cloudEvent)
+ when: 'the valid event is consumed'
+ objectUnderTest.consumeCmNotificationSubscriptionDmiInEvent(testEventSent)
+ then: 'no exception is thrown'
+ noExceptionThrown()
+ where: 'given #scenario'
+ scenario | subscriptionType
+ 'Subscription Create Event' | "subscriptionCreated"
+ 'Subscription Delete Event' | "subscriptionDeleted"
+ }
+
+ def 'Consume invalid message.'() {
+ given: 'an invalid event body'
+ objectUnderTest.dmiName = testDmiName
+ def eventKey = UUID.randomUUID().toString()
+ def timestamp = new Timestamp(1679521929511)
+ def invalidJsonBody = "/////"
+ objectUnderTest.cmNotificationSubscriptionDmiOutTopic = testTopic
+ def cloudEvent = CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withSource(URI.create('test-ncmp-dmi'))
+ .withType("subscriptionCreated")
+ .withDataSchema(URI.create("urn:cps:org.onap.ncmp.dmi.cm.subscription:1.0.0"))
+ .withTime(OffsetDateTime.ofInstant(timestamp.toInstant(), ZoneId.of("UTC")))
+ .withExtension("correlationid", eventKey).withData(objectMapper.writeValueAsBytes(invalidJsonBody)).build()
+ def testEventSent = new ConsumerRecord<String, CloudEvent>('topic-name', 0, 0, eventKey, cloudEvent)
+ when: 'the invalid event is consumed'
+ objectUnderTest.consumeCmNotificationSubscriptionDmiInEvent(testEventSent)
+ then: 'exception is thrown and event is logged'
+ def loggingEvent = getLoggingEvent()
+ assert loggingEvent.level == Level.ERROR
+ assert loggingEvent.formattedMessage.contains('Unable to map cloud event to target event class type')
+ }
+
+ def getLoggingEvent() {
+ return logger.list[0]
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapperSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapperSpec.groovy
new file mode 100644
index 00000000..8ca629f1
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/cmsubscription/CmNotificationSubscriptionDmiOutEventToCloudEventMapperSpec.groovy
@@ -0,0 +1,69 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.cmsubscription
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.cloudevents.core.builder.CloudEventBuilder
+import org.onap.cps.ncmp.dmi.exception.CloudEventConstructionException
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.CmNotificationSubscriptionDmiOutEvent
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.dmi_to_ncmp.Data
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import spock.lang.Specification
+
+@SpringBootTest(classes = [ObjectMapper])
+class CmNotificationSubscriptionDmiOutEventToCloudEventMapperSpec extends Specification {
+
+ @Autowired
+ def objectMapper = new ObjectMapper()
+
+ @SpringBean
+ CmNotificationSubscriptionDmiOutEventToCloudEventMapper objectUnderTest = new CmNotificationSubscriptionDmiOutEventToCloudEventMapper()
+
+ def 'Convert a Cm Subscription DMI Out Event to CloudEvent successfully.'() {
+ given: 'a Cm Subscription DMI Out Event and an event key'
+ def dmiName = 'test-ncmp-dmi'
+ def correlationId = 'subscription1#test-ncmp-dmi'
+ def cmSubscriptionDmiOutEventData = new Data(statusCode: "1", statusMessage: "accepted")
+ def cmSubscriptionDmiOutEvent =
+ new CmNotificationSubscriptionDmiOutEvent().withData(cmSubscriptionDmiOutEventData)
+ when: 'a Cm Subscription DMI Out Event is converted'
+ def result = objectUnderTest.toCloudEvent(cmSubscriptionDmiOutEvent, "subscriptionCreatedStatus", dmiName, correlationId)
+ then: 'Cm Subscription DMI Out Event is converted as expected'
+ def expectedCloudEvent = CloudEventBuilder.v1().withId(UUID.randomUUID().toString()).withSource(URI.create('test-ncmp-dmi'))
+ .withType("subscriptionCreated")
+ .withDataSchema(URI.create("urn:cps:" + CmNotificationSubscriptionDmiOutEvent.class.getName() + ":1.0.0"))
+ .withExtension("correlationid", correlationId)
+ .withData(objectMapper.writeValueAsBytes(cmSubscriptionDmiOutEvent)).build()
+ assert expectedCloudEvent.data == result.data
+ }
+
+ def 'Map the Cloud Event to data of the subscription event with null parameters causes an exception'() {
+ given: 'an empty subscription response event and event key'
+ def correlationId = 'subscription1#test-ncmp-dmi'
+ def cmSubscriptionDmiOutEvent = new CmNotificationSubscriptionDmiOutEvent()
+ when: 'the cm subscription dmi out Event map to data of cloud event'
+ objectUnderTest.toCloudEvent(cmSubscriptionDmiOutEvent, "subscriptionCreatedStatus", null , correlationId)
+ then: 'a run time exception is thrown'
+ thrown(CloudEventConstructionException)
+ }
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapperSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapperSpec.groovy
new file mode 100644
index 00000000..0b404776
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/mapper/CloudEventMapperSpec.groovy
@@ -0,0 +1,53 @@
+/*
+ * ============LICENSE_START========================================================
+ * Copyright (c) 2024 Nordix Foundation.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.mapper
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.cloudevents.core.builder.CloudEventBuilder
+import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import spock.lang.Specification
+
+@SpringBootTest(classes = [ObjectMapper])
+class CloudEventMapperSpec extends Specification {
+
+ @Autowired
+ ObjectMapper objectMapper
+
+ def 'Cloud event to Target event type when it is #scenario'() {
+ expect: 'Events mapped correctly'
+ assert mappedCloudEvent == (CloudEventMapper.toTargetEvent(testCloudEvent(), targetClass) != null)
+ where: 'below are the scenarios'
+ scenario | targetClass || mappedCloudEvent
+ 'valid concrete type' | CmNotificationSubscriptionNcmpInEvent.class || true
+ 'invalid concrete type' | ArrayList.class || false
+ }
+
+ def testCloudEvent() {
+ return CloudEventBuilder.v1().withData(objectMapper.writeValueAsBytes(new CmNotificationSubscriptionNcmpInEvent()))
+ .withId("cmhandle1")
+ .withSource(URI.create('test-source'))
+ .withDataSchema(URI.create('test'))
+ .withType('org.onap.cm.events.cm-subscription')
+ .build()
+ }
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/ControllerSecuritySpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/ControllerSecuritySpec.groovy
new file mode 100644
index 00000000..3f5d4a80
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/ControllerSecuritySpec.groovy
@@ -0,0 +1,76 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.dmi.rest.controller
+
+import org.onap.cps.ncmp.dmi.config.WebSecurityConfig
+import org.springframework.context.annotation.Import
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.http.HttpStatus
+import org.springframework.test.web.servlet.MockMvc
+import spock.lang.Specification
+
+@WebMvcTest(controllers = TestController.class)
+@Import(WebSecurityConfig)
+class ControllerSecuritySpec extends Specification {
+
+ @Autowired
+ MockMvc mvc
+
+ def testEndpoint = '/test'
+
+ def 'Get request with valid authentication'() {
+ when: 'request is sent with authentication'
+ def response = mvc.perform(
+ get(testEndpoint).header("Authorization", 'Basic Y3BzdXNlcjpjcHNyMGNrcyE=')
+ ).andReturn().response
+ then: 'HTTP OK status code is returned'
+ assert response.status == HttpStatus.OK.value()
+ }
+
+ def 'Get request without authentication'() {
+ when: 'request is sent without authentication'
+ def response = mvc.perform(get(testEndpoint)).andReturn().response
+ then: 'HTTP Unauthorized status code is returned'
+ assert response.status == HttpStatus.UNAUTHORIZED.value()
+ }
+
+ def 'Get request with invalid authentication'() {
+ when: 'request is sent with invalid authentication'
+ def response = mvc.perform(
+ get(testEndpoint).header("Authorization", 'Basic invalid auth')
+ ).andReturn().response
+ then: 'HTTP Unauthorized status code is returned'
+ assert response.status == HttpStatus.UNAUTHORIZED.value()
+ }
+
+ def 'Security Config #scenario permit URIs'() {
+ expect: 'can create a web security configuration'
+ new WebSecurityConfig(permitUris,'user','password')
+ where: 'the following string of permit URIs is provided'
+ scenario | permitUris
+ 'with' | 'a,b'
+ 'without' | ''
+ }
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy
new file mode 100644
index 00000000..a519de7b
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy
@@ -0,0 +1,406 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2023 Nordix Foundation
+ * Modifications Copyright (C) 2021-2022 Bell Canada
+ * ================================================================================
+ * 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.dmi.rest.controller
+
+
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
+import org.onap.cps.ncmp.dmi.TestUtils
+import org.onap.cps.ncmp.dmi.config.WebSecurityConfig
+import org.onap.cps.ncmp.dmi.exception.DmiException
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException
+import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException
+import org.onap.cps.ncmp.dmi.model.ModuleSet
+import org.onap.cps.ncmp.dmi.model.ModuleSetSchemasInner
+import org.onap.cps.ncmp.dmi.model.YangResource
+import org.onap.cps.ncmp.dmi.model.YangResources
+import org.onap.cps.ncmp.dmi.notifications.async.AsyncTaskExecutor
+import org.onap.cps.ncmp.dmi.notifications.async.DmiAsyncRequestResponseEventProducer
+import org.onap.cps.ncmp.dmi.service.DmiService
+import org.onap.cps.ncmp.dmi.service.model.ModuleReference
+import org.slf4j.LoggerFactory
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.context.annotation.Import
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.kafka.core.KafkaTemplate
+import org.springframework.security.test.context.support.WithMockUser
+import org.springframework.test.web.servlet.MockMvc
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.CREATE
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.DELETE
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.PATCH
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.READ
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.UPDATE
+import static org.springframework.http.HttpStatus.BAD_REQUEST
+import static org.springframework.http.HttpStatus.CREATED
+import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
+import static org.springframework.http.HttpStatus.NO_CONTENT
+import static org.springframework.http.HttpStatus.OK
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+
+@Import(WebSecurityConfig)
+@WebMvcTest(DmiRestController.class)
+@WithMockUser
+class DmiRestControllerSpec extends Specification {
+
+ @Autowired
+ private MockMvc mvc
+
+ @SpringBean
+ DmiService mockDmiService = Mock()
+
+ @SpringBean
+ DmiAsyncRequestResponseEventProducer cpsAsyncRequestResponseEventProducer = new DmiAsyncRequestResponseEventProducer(Mock(KafkaTemplate))
+
+ @SpringBean
+ AsyncTaskExecutor asyncTaskExecutor = new AsyncTaskExecutor(cpsAsyncRequestResponseEventProducer)
+
+ def logger = Spy(ListAppender<ILoggingEvent>)
+
+ void setup() {
+ ((Logger) LoggerFactory.getLogger(DmiRestController.class)).addAppender(logger)
+ logger.start()
+ }
+
+ void cleanup() {
+ ((Logger) LoggerFactory.getLogger(DmiRestController.class)).detachAndStopAllAppenders()
+ }
+
+ @Value('${rest.api.dmi-base-path}/v1')
+ def basePathV1
+
+ def 'Get all modules.'() {
+ given: 'URL for getting all modules and some request data'
+ def getModuleUrl = "$basePathV1/ch/node1/modules"
+ def someValidJson = '{}'
+ and: 'DMI service returns some module'
+ def moduleSetSchema = new ModuleSetSchemasInner(namespace:'some-namespace',
+ moduleName:'some-moduleName',
+ revision:'some-revision')
+ def moduleSetSchemasList = [moduleSetSchema] as List<ModuleSetSchemasInner>
+ def moduleSet = new ModuleSet()
+ moduleSet.schemas(moduleSetSchemasList)
+ mockDmiService.getModulesForCmHandle('node1') >> moduleSet
+ when: 'the request is posted'
+ def response = mvc.perform(post(getModuleUrl)
+ .contentType(MediaType.APPLICATION_JSON).content(someValidJson))
+ .andReturn().response
+ then: 'status is OK'
+ response.status == OK.value()
+ and: 'the response content matches the result from the DMI service'
+ response.getContentAsString() == '{"schemas":[{"moduleName":"some-moduleName","revision":"some-revision","namespace":"some-namespace"}]}'
+ }
+
+ def 'Get all modules with exception handling of #scenario.'() {
+ given: 'URL for getting all modules and some request data'
+ def getModuleUrl = "$basePathV1/ch/node1/modules"
+ def someValidJson = '{}'
+ and: 'a #exception is thrown during the process'
+ mockDmiService.getModulesForCmHandle('node1') >> { throw exception }
+ when: 'the request is posted'
+ def response = mvc.perform( post(getModuleUrl)
+ .contentType(MediaType.APPLICATION_JSON).content(someValidJson))
+ .andReturn().response
+ then: 'response status is #expectedResponse'
+ response.status == expectedResponse.value()
+ where: 'the scenario is #scenario'
+ scenario | exception || expectedResponse
+ 'dmi service exception' | new DmiException('','') || HttpStatus.INTERNAL_SERVER_ERROR
+ 'no modules found' | new ModulesNotFoundException('','') || HttpStatus.NOT_FOUND
+ 'any other runtime exception' | new RuntimeException() || HttpStatus.INTERNAL_SERVER_ERROR
+ 'runtime exception with cause' | new RuntimeException('', new RuntimeException()) || HttpStatus.INTERNAL_SERVER_ERROR
+ }
+
+ def 'Register given list.'() {
+ given: 'register cm handle url and cmHandles'
+ def registerCmhandlesPost = "${basePathV1}/inventory/cmHandles"
+ def cmHandleJson = '{"cmHandles":["node1", "node2"]}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(registerCmhandlesPost)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(cmHandleJson)
+ ).andReturn().response
+ then: 'register cm handles in dmi service is invoked with correct parameters'
+ 1 * mockDmiService.registerCmHandles(_ as List<String>)
+ and: 'response status is created'
+ response.status == CREATED.value()
+ }
+
+ def 'register cm handles called with empty content.'() {
+ given: 'register cm handle url and empty json'
+ def registerCmhandlesPost = "${basePathV1}/inventory/cmHandles"
+ def emptyJson = '{"cmHandles":[]}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(registerCmhandlesPost).contentType(MediaType.APPLICATION_JSON)
+ .content(emptyJson)
+ ).andReturn().response
+ then: 'response status is "bad request"'
+ response.status == BAD_REQUEST.value()
+ and: 'dmi service is not called'
+ 0 * mockDmiService.registerCmHandles(_)
+ }
+
+ def 'Retrieve module resources.'() {
+ given: 'URL to get module resources'
+ def getModulesEndpoint = "$basePathV1/ch/some-cm-handle/moduleResources"
+ and: 'request data to get some modules'
+ String jsonData = TestUtils.getResourceFileContent('moduleResources.json')
+ and: 'the DMI service returns the yang resources'
+ ModuleReference moduleReference1 = new ModuleReference(name: 'ietf-yang-library', revision: '2016-06-21')
+ ModuleReference moduleReference2 = new ModuleReference(name: 'nc-notifications', revision: '2008-07-14')
+ def moduleReferences = [moduleReference1, moduleReference2]
+ def yangResources = new YangResources()
+ def yangResource = new YangResource(yangSource: '"some-data"', moduleName: 'NAME', revision: 'REVISION')
+ yangResources.add(yangResource)
+ mockDmiService.getModuleResources('some-cm-handle', moduleReferences) >> yangResources
+ when: 'the request is posted'
+ def response = mvc.perform(post(getModulesEndpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonData)).andReturn().response
+ then: 'a OK status is returned'
+ response.status == OK.value()
+ and: 'the response content matches the result from the DMI service'
+ response.getContentAsString() == '[{"yangSource":"\\"some-data\\"","moduleName":"NAME","revision":"REVISION"}]'
+ }
+
+ def 'Retrieve module resources with exception handling.'() {
+ given: 'URL to get module resources'
+ def getModulesEndpoint = "$basePathV1/ch/some-cm-handle/moduleResources"
+ and: 'request data to get some modules'
+ String jsonData = TestUtils.getResourceFileContent('moduleResources.json')
+ and: 'the system throws a not-found exception (during the processing)'
+ mockDmiService.getModuleResources('some-cm-handle', _) >> { throw Mock(ModuleResourceNotFoundException.class) }
+ when: 'the request is posted'
+ def response = mvc.perform(post(getModulesEndpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonData)).andReturn().response
+ then: 'a not found status is returned'
+ response.status == HttpStatus.NOT_FOUND.value()
+ }
+
+ def 'Retrieve module resources and ensure module set tag is logged.'() {
+ given: 'URL to get module resources'
+ def getModulesEndpoint = "$basePathV1/ch/some-cm-handle/moduleResources"
+ and: 'request data to get some modules'
+ String jsonData = TestUtils.getResourceFileContent('moduleResources.json')
+ and: 'the DMI service returns the yang resources'
+ def moduleReferences = []
+ def yangResources = new YangResources()
+ def yangResource = new YangResource()
+ yangResources.add(yangResource)
+ mockDmiService.getModuleResources('some-cm-handle', moduleReferences) >> yangResources
+ when: 'the request is posted'
+ mvc.perform(post(getModulesEndpoint)
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(jsonData))
+ then: 'the module set tag is logged'
+ def loggingMessage = getLoggingMessage(0)
+ assert loggingMessage.contains('module-set-tag1')
+ }
+
+ def 'Get resource data for pass-through operational.'() {
+ given: 'Get resource data url and some request data'
+ def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-operational" +
+ "?resourceIdentifier=parent/child&options=(fields=myfields,depth=5)"
+ def someValidJson = '{}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(someValidJson)
+ ).andReturn().response
+ then: 'response status is ok'
+ response.status == OK.value()
+ and: 'dmi service method to get resource data is invoked once'
+ 1 * mockDmiService.getResourceData('some-cmHandle',
+ 'parent/child',
+ '(fields=myfields,depth=5)',
+ 'content=all')
+ }
+
+ def 'Get resource data for pass-through operational with write request (invalid).'() {
+ given: 'Get resource data url'
+ def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-operational" +
+ "?resourceIdentifier=parent/child&options=(fields=myfields,depth=5)"
+ and: 'an invalid write request data for "create" operation'
+ def jsonData = '{"operation":"create"}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(jsonData)
+ ).andReturn().response
+ then: 'response status is bad request'
+ response.status == BAD_REQUEST.value()
+ and: 'dmi service is not invoked'
+ 0 * mockDmiService.getResourceData(*_)
+ }
+
+ def 'Get resource data for invalid datastore'() {
+ given: 'Get resource data url'
+ def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/dummy-datastore" +
+ "?resourceIdentifier=parent/child&options=(fields=myfields,depth=5)"
+ and: 'an invalid write request data for "create" operation'
+ def jsonData = '{"operation":"create"}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(jsonData)
+ ).andReturn().response
+ then: 'response status is internal server error'
+ response.status == INTERNAL_SERVER_ERROR.value()
+ and: 'response contains expected error message'
+ response.contentAsString.contains('dummy-datastore is an invalid datastore name')
+ }
+
+ def 'data with #scenario operation using passthrough running.'() {
+ given: 'write data for passthrough running url'
+ def writeDataForPassthroughRunning = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" +
+ "?resourceIdentifier=some-resourceIdentifier"
+ and: 'request data for #scenario'
+ def jsonData = TestUtils.getResourceFileContent(requestBodyFile)
+ and: 'dmi service is called'
+ mockDmiService.writeData(operationEnum, 'some-cmHandle',
+ 'some-resourceIdentifier', dataType,
+ 'normal request body' ) >> '{some-json}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(writeDataForPassthroughRunning).contentType(MediaType.APPLICATION_JSON)
+ .content(jsonData)
+ ).andReturn().response
+ then: 'response status is #expectedResponseStatus'
+ response.status == expectedResponseStatus
+ and: 'the response content matches the result from the DMI service'
+ response.getContentAsString() == expectedJsonResponse
+ where: 'given request body and data'
+ scenario | requestBodyFile | operationEnum | dataType || expectedResponseStatus | expectedJsonResponse
+ 'Create' | 'createDataWithNormalChar.json' | CREATE | 'application/json' || CREATED.value() | '{some-json}'
+ 'Update' | 'updateData.json' | UPDATE | 'application/json' || OK.value() | '{some-json}'
+ 'Delete' | 'deleteData.json' | DELETE | 'application/json' || NO_CONTENT.value() | '{some-json}'
+ 'Read' | 'readData.json' | READ | 'application/json' || OK.value() | ''
+ 'Patch' | 'patchData.json' | PATCH | 'application/yang.patch+json' || OK.value() | '{some-json}'
+ }
+
+ def 'Create data using passthrough for special characters.'(){
+ given: 'create data for cmHandle url'
+ def writeDataForCmHandlePassthroughRunning = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" +
+ "?resourceIdentifier=some-resourceIdentifier"
+ and: 'request data with special characters'
+ def jsonData = TestUtils.getResourceFileContent('createDataWithSpecialChar.json')
+ and: 'dmi service returns data'
+ mockDmiService.writeData(CREATE, 'some-cmHandle', 'some-resourceIdentifier', 'application/json',
+ 'data with quote \" and new line \n') >> '{some-json}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(writeDataForCmHandlePassthroughRunning).contentType(MediaType.APPLICATION_JSON).content(jsonData)
+ ).andReturn().response
+ then: 'response status is CREATED'
+ response.status == CREATED.value()
+ and: 'the response content matches the result from the DMI service'
+ response.getContentAsString() == '{some-json}'
+ }
+
+ def 'PassThrough Returns OK when topic is used for async'(){
+ given: 'Passthrough read URL and request data with a topic (parameter)'
+ def readPassThroughUrl ="${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:" +
+ resourceIdentifier +
+ '?resourceIdentifier=some-resourceIdentifier&topic=test-topic'
+ def jsonData = TestUtils.getResourceFileContent('readData.json')
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(readPassThroughUrl).contentType(MediaType.APPLICATION_JSON).content(jsonData)
+ ).andReturn().response
+ then: 'response status is OK'
+ assert response.status == HttpStatus.NO_CONTENT.value()
+ where: 'the following values are used'
+ resourceIdentifier << ['passthrough-operational', 'passthrough-running']
+ }
+
+ def 'PassThrough logs module set tag'(){
+ given: 'Passthrough read URL and request data with a module set tag (parameter)'
+ def readPassThroughUrl ="${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:" +
+ 'passthrough-running?resourceIdentifier=some-resourceIdentifier'
+ def jsonData = TestUtils.getResourceFileContent('readData.json')
+ when: 'the request is posted'
+ mvc.perform(
+ post(readPassThroughUrl).contentType(MediaType.APPLICATION_JSON).content(jsonData))
+ then: 'response status is OK'
+ def loggingMessage = getLoggingMessage(0)
+ assert loggingMessage.contains('module-set-tag-example')
+ }
+
+ def 'Get resource data for pass-through running with #scenario value in resource identifier param.'() {
+ given: 'Get resource data url'
+ def getResourceDataForCmHandleUrl = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" +
+ "?resourceIdentifier="+resourceIdentifier+"&options=(fields=myfields,depth=5)"
+ and: 'some valid json data'
+ def json = '{"cmHandleProperties" : { "prop1" : "value1", "prop2" : "value2"}}'
+ when: 'the request is posted'
+ def response = mvc.perform(
+ post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON).content(json)
+ ).andReturn().response
+ then: 'response status is ok'
+ response.status == OK.value()
+ and: 'dmi service method to get resource data is invoked once with correct parameters'
+ 1 * mockDmiService.getResourceData('some-cmHandle',
+ resourceIdentifier,
+ '(fields=myfields,depth=5)',
+ 'content=config')
+ where: 'tokens are used in the resource identifier parameter'
+ scenario | resourceIdentifier
+ '/' | 'id/with/slashes'
+ '?' | 'idWith?'
+ ',' | 'idWith,'
+ '=' | 'idWith='
+ '[]' | 'idWith[]'
+ '? needs to be encoded as %3F' | 'idWith%3F'
+
+ }
+
+ def 'Execute a data operation for a list of operations.'() {
+ given: 'an endpoint for a data operation request with list of cmhandles in request body'
+ def resourceDataUrl = "$basePathV1/data?topic=client-topic-name&requestId=some-requestId"
+ and: 'list of operation details are received into request body'
+ def dataOperationRequestBody = '[{"operation": "read", "operationId": "14", "datastore": "ncmp-datastore:passthrough-operational", "options": "some options", "resourceIdentifier": "some resourceIdentifier",' +
+ '"cmHandles": [ {"id": "cmHandle123", "moduleSetTag": "module-set-tag1", "cmHandleProperties": { "myProp`": "some value", "otherProp": "other value"}}]}]'
+ when: 'the dmi resource data for dataOperation api is called.'
+ def response = mvc.perform(
+ post(resourceDataUrl).contentType(MediaType.APPLICATION_JSON).content(dataOperationRequestBody)
+ ).andReturn().response
+ then: 'the resource data operation endpoint returns the not implemented response'
+ assert response.status == 501
+ and: 'the job details are correctly received (logged)'
+ assert getLoggingMessage(1).contains('some-requestId')
+ assert getLoggingMessage(2).contains('client-topic-name')
+ assert getLoggingMessage(4).contains('some resourceIdentifier')
+ assert getLoggingMessage(5).contains('module-set-tag1')
+ and: 'the operation Id is correctly received (logged)'
+ assert getLoggingMessage(6).contains('14')
+ }
+
+ def getLoggingMessage(int index) {
+ return logger.list[index].formattedMessage
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy
new file mode 100644
index 00000000..8531d35f
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy
@@ -0,0 +1,273 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2022 Nordix Foundation
+ * Modifications Copyright (C) 2021-2022 Bell Canada
+ * ================================================================================
+ * 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.dmi.service
+
+import com.fasterxml.jackson.core.JsonProcessingException
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.ObjectWriter
+import org.onap.cps.ncmp.dmi.config.DmiPluginConfig
+import org.onap.cps.ncmp.dmi.exception.CmHandleRegistrationException
+import org.onap.cps.ncmp.dmi.exception.DmiException
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException
+import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException
+import org.onap.cps.ncmp.dmi.exception.HttpClientRequestException
+import org.onap.cps.ncmp.dmi.service.model.ModuleReference
+import org.onap.cps.ncmp.dmi.model.YangResource
+import org.onap.cps.ncmp.dmi.model.YangResources
+import org.onap.cps.ncmp.dmi.service.client.NcmpRestClient
+import org.onap.cps.ncmp.dmi.service.model.ModuleSchema
+import org.onap.cps.ncmp.dmi.service.operation.SdncOperations
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.CREATE
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.UPDATE
+
+class DmiServiceImplSpec extends Specification {
+
+
+ def mockNcmpRestClient = Mock(NcmpRestClient)
+ def mockDmiPluginProperties = Mock(DmiPluginConfig.DmiPluginProperties)
+ def spyObjectMapper = Spy(ObjectMapper)
+ def mockObjectMapper = Mock(ObjectMapper)
+ def mockSdncOperations = Mock(SdncOperations)
+ def objectUnderTest = new DmiServiceImpl(mockDmiPluginProperties, mockNcmpRestClient, mockSdncOperations, spyObjectMapper)
+
+ def 'Register cm handles with ncmp.'() {
+ given: 'some cm-handle ids'
+ def givenCmHandlesList = ['node1', 'node2']
+ and: 'json payload'
+ def expectedJson = '{"dmiPlugin":"test-dmi-service","createdCmHandles":[{"cmHandle":"node1"},{"cmHandle":"node2"}]}'
+ and: 'process returns "test-dmi-service" for service name'
+ mockDmiPluginProperties.getDmiServiceUrl() >> 'test-dmi-service'
+ when: 'the cm handles are registered'
+ objectUnderTest.registerCmHandles(givenCmHandlesList)
+ then: 'register cm handle with ncmp is called with the expected json and return "created" status'
+ 1 * mockNcmpRestClient.registerCmHandlesWithNcmp(expectedJson) >> new ResponseEntity<>(HttpStatus.CREATED)
+ }
+
+ def 'Register cm handles with ncmp called with exception #scenario.'() {
+ given: 'some cm-handle ids'
+ def cmHandlesList = ['node1', 'node2']
+ and: 'process returns "test-dmi-service" for service name'
+ mockDmiPluginProperties.getDmiServiceUrl() >> 'test-dmi-service'
+ and: 'returns #responseEntity'
+ mockNcmpRestClient.registerCmHandlesWithNcmp(_ as String) >> responseEntity
+ when: 'the cm handles are registered'
+ objectUnderTest.registerCmHandles(cmHandlesList)
+ then: 'a registration exception is thrown'
+ thrown(CmHandleRegistrationException.class)
+ where: 'given #scenario'
+ scenario | responseEntity
+ 'ncmp rest client returns bad request' | new ResponseEntity<>(HttpStatus.BAD_REQUEST)
+ 'ncmp rest client returns internal server error' | new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)
+ }
+
+ def 'Register cm handles with ncmp with wrong data.'() {
+ given: 'some cm-handle ids'
+ def cmHandlesList = ['node1', 'node2']
+ and: ' "JsonProcessingException" occurs during parsing'
+ objectUnderTest.objectMapper = mockObjectMapper
+ mockObjectMapper.writeValueAsString(_) >> { throw new JsonProcessingException('some error.') }
+ when: 'the cmHandles are registered'
+ objectUnderTest.registerCmHandles(cmHandlesList)
+ then: 'a dmi exception is thrown'
+ thrown(DmiException.class)
+ }
+
+ def ' Get modules for a cm-handle.'() {
+ given: 'a cm handle'
+ def cmHandle = 'node1'
+ and: 'process returns one module schema for the cmhandle'
+ def moduleSchema = new ModuleSchema(
+ identifier: "example-identifier",
+ namespace: "example:namespace",
+ version: "example-version")
+ mockSdncOperations.getModuleSchemasFromNode(cmHandle) >> List.of(moduleSchema)
+ when: 'modules for cmHandle is requested'
+ def result = objectUnderTest.getModulesForCmHandle(cmHandle)
+ then: 'one module is returned'
+ result.schemas.size() == 1
+ and: 'module has expected values'
+ with(result.schemas[0]) {
+ it.getRevision() == moduleSchema.getVersion()
+ it.getModuleName() == moduleSchema.getIdentifier()
+ it.getNamespace() == moduleSchema.getNamespace();
+ }
+ }
+
+ def 'no modules found for the cmhandle.'() {
+ given: 'cm handle id'
+ def cmHandle = 'node1'
+ and: 'process returns no modules'
+ mockSdncOperations.getModuleSchemasFromNode(cmHandle) >> Collections.emptyList();
+ when: 'modules for cm-handle is requested'
+ objectUnderTest.getModulesForCmHandle(cmHandle)
+ then: 'module not found exception is thrown'
+ thrown(ModulesNotFoundException)
+ }
+
+ def 'Get multiple module resources.'() {
+ given: 'a cmHandle'
+ def cmHandle = 'some-cmHandle'
+ and: 'multiple module references'
+ def moduleReference1 = new ModuleReference(name: 'name-1', revision: 'revision-1')
+ def moduleReference2 = new ModuleReference(name: 'name-2', revision: 'revision-2')
+ def moduleList = [moduleReference1, moduleReference2]
+ when: 'module resources is requested'
+ def result = objectUnderTest.getModuleResources(cmHandle, moduleList)
+ then: 'SDNC operation service is called same number of module references given'
+ 2 * mockSdncOperations.getModuleResource(cmHandle, _) >>> [new ResponseEntity<String>('{"ietf-netconf-monitoring:output": {"data": "some-data1"}}', HttpStatus.OK),
+ new ResponseEntity<String>('{"ietf-netconf-monitoring:output": {"data": "some-data2"}}', HttpStatus.OK)]
+ and: 'the result contains the expected properties'
+ def yangResources = new YangResources()
+ def yangResource1 = new YangResource(yangSource: 'some-data1', moduleName: 'name-1', revision: 'revision-1')
+ def yangResource2 = new YangResource(yangSource: 'some-data2', moduleName: 'name-2', revision: 'revision-2')
+ yangResources.add(yangResource1)
+ yangResources.add(yangResource2)
+ assert result == yangResources
+ }
+
+ def 'Get a module resource with module resource not found exception for #scenario.'() {
+ given: 'a cmHandle and module reference list'
+ def cmHandle = 'some-cmHandle'
+ def moduleReference = new ModuleReference(name: 'NAME', revision: 'REVISION')
+ def moduleList = [moduleReference]
+ when: 'module resources is requested'
+ objectUnderTest.getModuleResources(cmHandle, moduleList)
+ then: 'SDNC operation service is called once with a response body that contains no data'
+ 1 * mockSdncOperations.getModuleResource(cmHandle, _) >> new ResponseEntity<String>(responseBody, HttpStatus.OK)
+ and: 'an exception is thrown'
+ thrown(ModuleResourceNotFoundException)
+ where: 'the following values are returned'
+ scenario | responseBody
+ 'a response body containing no data object' | '{"ietf-netconf-monitoring:output": {"null": "some-data"}}'
+ 'a response body containing no ietf-netconf-monitoring:output object' | '{"null": {"data": "some-data"}}'
+ }
+
+ def 'Get module resources when sdnc returns #scenario response.'() {
+ given: 'sdnc returns a #scenario response'
+ mockSdncOperations.getModuleResource(_ as String, _ as String) >> new ResponseEntity<String>('some-response-body', httpStatus)
+ when: 'module resources is requested'
+ objectUnderTest.getModuleResources('some-cmHandle', [new ModuleReference()] as LinkedList<ModuleReference>)
+ then: '#expectedException is thrown'
+ thrown(expectedException)
+ where: 'the following values are returned'
+ scenario | httpStatus || expectedException
+ 'not found' | HttpStatus.NOT_FOUND || ModuleResourceNotFoundException
+ 'internal server error' | HttpStatus.INTERNAL_SERVER_ERROR || DmiException
+ }
+
+ def 'Get module resources with JSON processing exception.'() {
+ given: 'a json processing exception during process'
+ def mockObjectWriter = Mock(ObjectWriter)
+ spyObjectMapper.writer() >> mockObjectWriter
+ mockObjectWriter.withRootName(_) >> mockObjectWriter
+ def jsonProcessingException = new JsonProcessingException('')
+ mockObjectWriter.writeValueAsString(_) >> { throw jsonProcessingException }
+ when: 'module resources is requested'
+ objectUnderTest.getModuleResources('some-cmHandle', [new ModuleReference()] as LinkedList<ModuleReference>)
+ then: 'an exception is thrown'
+ def thrownException = thrown(DmiException.class)
+ and: 'the exception has the expected message and details'
+ thrownException.message == 'Unable to process JSON.'
+ thrownException.details == 'JSON exception occurred when creating the module request.'
+ and: 'the cause is the original json processing exception'
+ thrownException.cause == jsonProcessingException
+ }
+
+ def 'Get resource data for passthrough operational.'() {
+ given: 'sdnc operation returns OK response'
+ mockSdncOperations.getResouceDataForOperationalAndRunning(
+ 'someCmHandle',
+ 'someResourceId',
+ '(fields=x/y/z,depth=10,test=abc)',
+ 'content=all') >> new ResponseEntity<>('response json', HttpStatus.OK)
+ when: 'resource data is requested'
+ def response = objectUnderTest.getResourceData(
+ 'someCmHandle',
+ 'someResourceId',
+ '(fields=x/y/z,depth=10,test=abc)',
+ 'content=all')
+ then: 'response matches the response returned from the SDNC service'
+ response == 'response json'
+ }
+
+ def 'Get resource data with not found exception.'() {
+ given: 'sdnc operation returns "NOT_FOUND" response'
+ mockSdncOperations.getResouceDataForOperationalAndRunning(*_) >> new ResponseEntity<>(HttpStatus.NOT_FOUND)
+ when: 'resource data is requested'
+ objectUnderTest.getResourceData('someCmHandle', 'someResourceId',
+ '(fields=x/y/z,depth=10,test=abc)', 'content=config')
+ then: 'http client request exception'
+ thrown(HttpClientRequestException.class)
+ }
+
+ def 'Get resource data for passthrough running.'() {
+ given: 'sdnc operation returns OK response'
+ mockSdncOperations.getResouceDataForOperationalAndRunning(*_) >> new ResponseEntity<>('response json', HttpStatus.OK)
+ when: 'resource data is requested'
+ def response = objectUnderTest.getResourceData(
+ 'someCmHandle',
+ 'someResourceId',
+ '(fields=x/y/z,depth=10,test=abc)',
+ 'content=config')
+ then: 'response have expected json'
+ response == 'response json'
+ }
+
+ def 'Write resource data for passthrough running with a #scenario from sdnc.'() {
+ given: 'sdnc returns a response with #scenario'
+ mockSdncOperations.writeData(operationEnum, _, _, _, _) >> new ResponseEntity<String>('response json', httpResponse)
+ when: 'resource data is written to sdnc'
+ def response = objectUnderTest.writeData(operationEnum, 'some-cmHandle',
+ 'some-resourceIdentifier', 'some-dataType', '{some-data}')
+ then: 'the response matches the expected data'
+ response == 'response json'
+ where: 'the following values are used'
+ scenario | httpResponse | operationEnum
+ '200 OK with an update operation' | HttpStatus.OK | UPDATE
+ '201 CREATED with a create operation' | HttpStatus.CREATED | CREATE
+ }
+
+ def 'Write resource data with special characters.'() {
+ given: 'sdnc returns a created response'
+ mockSdncOperations.writeData(CREATE, 'some-cmHandle',
+ 'some-resourceIdentifier', 'some-dataType', 'data with quote " and \n new line') >> new ResponseEntity<String>('response json', HttpStatus.CREATED)
+ when: 'resource data is written to sdnc'
+ def response = objectUnderTest.writeData(CREATE, 'some-cmHandle',
+ 'some-resourceIdentifier', 'some-dataType', 'data with quote " and \n new line')
+ then: 'the response matches the expected data'
+ response == 'response json'
+ }
+
+ def 'Write resource data for passthrough running with a 500 response from sdnc.'() {
+ given: 'sdnc returns internal server error response'
+ mockSdncOperations.writeData(CREATE, _, _, _, _) >> new ResponseEntity<String>('response json', HttpStatus.INTERNAL_SERVER_ERROR)
+ when: 'resource data is written to sdnc'
+ objectUnderTest.writeData(CREATE, 'some-cmHandle',
+ 'some-resourceIdentifier', 'some-dataType', _ as String)
+ then: 'a dmi exception is thrown'
+ thrown(DmiException.class)
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/YangResourceExtractorSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/YangResourceExtractorSpec.groovy
new file mode 100644
index 00000000..656cfcb5
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/YangResourceExtractorSpec.groovy
@@ -0,0 +1,98 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.service
+
+import com.google.gson.JsonSyntaxException
+import org.onap.cps.ncmp.dmi.exception.ModuleResourceNotFoundException
+import org.onap.cps.ncmp.dmi.service.model.ModuleReference
+import org.springframework.http.ResponseEntity
+import spock.lang.Specification
+
+class YangResourceExtractorSpec extends Specification {
+
+ static def BACK_SLASH = '\\';
+ static def NEW_LINE = '\n';
+ static def QUOTE = '"';
+ static def TAB = '\t';
+
+ static def YANG_ESCAPED_NEW_LINE = '\\n'
+ static def YANG_ESCAPED_BACK_SLASH = '\\\\'
+ static def YANG_ESCAPED_QUOTE = '\\"'
+ static def YANG_ESCAPED_TAB = '\\t'
+
+ static def SDNC_OUTPUT_JSON_NAME = '"ietf-netconf-monitoring:output"'
+
+ def moduleReference = new ModuleReference(name: 'test', revision: 'rev')
+ def responseEntity = Mock(ResponseEntity)
+
+ def 'Extract yang resource with escaped characters in the source.'() {
+ given: 'a response entity with a data field of value #jsonValue'
+ responseEntity.getBody() >> '{' + SDNC_OUTPUT_JSON_NAME + ': { "data": "' + jsonValue + '" }}'
+ when: 'the yang resource is extracted'
+ def result = YangResourceExtractor.toYangResource(moduleReference, responseEntity)
+ then: 'the yang source string is as expected'
+ result.getYangSource() == expectedString
+ where: 'the following data is used'
+ jsonValue || expectedString
+ 'line1' + YANG_ESCAPED_NEW_LINE + 'line2' || 'line1' + NEW_LINE + 'line2'
+ 'a' + YANG_ESCAPED_BACK_SLASH+'b' || 'a'+BACK_SLASH +'b'
+ 'a' + YANG_ESCAPED_QUOTE + 'b' || 'a'+QUOTE+'b'
+ 'a' + YANG_ESCAPED_TAB + 'b' || 'a'+TAB+'b'
+ }
+
+ def 'Extract yang resource with escaped characters in the source inside escaped double quotes.'() {
+ given: 'a response entity with a data field of value #jsonValue wrapped in escaped double quotes'
+ responseEntity.getBody() >> '{' + SDNC_OUTPUT_JSON_NAME + ': { "data": "' + YANG_ESCAPED_QUOTE + jsonValue + YANG_ESCAPED_QUOTE + '" }}'
+ when: 'the yang resource is extracted'
+ def result = YangResourceExtractor.toYangResource(moduleReference, responseEntity)
+ then: 'the yang source string is as expected'
+ result.getYangSource() == expectedString
+ where: 'the following data is used'
+ jsonValue || expectedString
+ 'line1' + YANG_ESCAPED_NEW_LINE + 'line2' || '"line1' + NEW_LINE + 'line2"'
+ 'a' + YANG_ESCAPED_BACK_SLASH+'b' || '"a'+BACK_SLASH +'b"'
+ 'a' + YANG_ESCAPED_QUOTE + 'b' || '"a'+QUOTE+'b"'
+ 'a' + YANG_ESCAPED_TAB + 'b' || '"a'+TAB+'b"'
+ }
+
+ def 'Attempt to extract yang resource with un-escaped double quotes in the source.'() {
+ given: 'a response entity with a data field with unescaped double quotes'
+ responseEntity.getBody() >> '{' + SDNC_OUTPUT_JSON_NAME + ': { "data": "' + QUOTE + 'some data' + QUOTE + '" }}'
+ when: 'Json is converted to String'
+ YangResourceExtractor.toYangResource(moduleReference, responseEntity)
+ then: 'the output of the method is equal to the output from the test template'
+ thrown(JsonSyntaxException)
+ }
+
+ def 'Attempt to extract yang resource without #without.'() {
+ given: 'a response entity with a body without #without'
+ responseEntity.getBody() >> jsonBody
+ when: 'Json is converted to String'
+ YangResourceExtractor.toYangResource(moduleReference, responseEntity)
+ then: 'the output of the method is equal to the output from the test template'
+ thrown(ModuleResourceNotFoundException)
+ where:
+ without | jsonBody
+ 'data' | '{' + SDNC_OUTPUT_JSON_NAME + ': { "something": "else" }}'
+ SDNC_OUTPUT_JSON_NAME | '{"something:else": { "data": "data" }}'
+ }
+
+}
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy
new file mode 100644
index 00000000..4d7e27e2
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/NcmpRestClientSpec.groovy
@@ -0,0 +1,57 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.ncmp.dmi.service.client
+
+import org.onap.cps.ncmp.dmi.config.DmiConfiguration
+import org.springframework.http.HttpMethod
+import org.springframework.http.ResponseEntity
+import org.springframework.web.client.RestTemplate
+import spock.lang.Specification
+
+class NcmpRestClientSpec extends Specification {
+ def objectUnderTest = new NcmpRestClient(mockCpsProperties, mockRestTemplate)
+ def mockCpsProperties = Mock(DmiConfiguration.CpsProperties)
+ def mockRestTemplate = Mock(RestTemplate)
+
+ def setup() {
+ objectUnderTest.cpsProperties = mockCpsProperties
+ objectUnderTest.restTemplate = mockRestTemplate
+ }
+
+ def 'Register a cm handle.'() {
+ given: 'some request data'
+ def someRequestData = 'some request data'
+ and: 'configuration data'
+ mockCpsProperties.baseUrl >> 'http://some-uri'
+ mockCpsProperties.dmiRegistrationUrl >> 'some-url'
+ mockCpsProperties.authUsername >> 'some-username'
+ mockCpsProperties.authPassword >> 'some-password'
+ and: 'the rest template returns a valid response entity'
+ def mockResponseEntity = Mock(ResponseEntity)
+ when: 'registering a cm handle'
+ def result = objectUnderTest.registerCmHandlesWithNcmp(someRequestData)
+ then: 'the rest template is called with the correct uri and original request data in the body'
+ 1 * mockRestTemplate.exchange({ it.toString() == 'http://some-uri/some-url' },
+ HttpMethod.POST, { it.body.contains(someRequestData) }, String.class) >> mockResponseEntity
+ and: 'the output of the method is equal to the output from the rest template service'
+ result == mockResponseEntity
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy
new file mode 100644
index 00000000..f334f780
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy
@@ -0,0 +1,102 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-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.ncmp.dmi.service.client
+
+import org.onap.cps.ncmp.dmi.config.DmiConfiguration
+import org.springframework.http.HttpEntity
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpMethod
+import org.springframework.http.ResponseEntity
+import org.springframework.web.client.RestTemplate
+import spock.lang.Specification
+
+import static org.springframework.http.HttpMethod.GET
+import static org.springframework.http.HttpMethod.POST
+import static org.springframework.http.HttpMethod.DELETE
+import static org.springframework.http.HttpMethod.PUT
+
+class SdncRestconfClientSpec extends Specification {
+
+ def mockSdncProperties = Mock(DmiConfiguration.SdncProperties)
+ def mockRestTemplate = Mock(RestTemplate)
+ def objectUnderTest = new SdncRestconfClient(mockSdncProperties, mockRestTemplate)
+
+ def 'SDNC GET operation.'() {
+ given: 'a get resource url'
+ def getResourceUrl = '/getResourceUrl'
+ and: 'test configuration data'
+ setupTestConfigurationData()
+ and: 'the process returns a valid response entity'
+ def mockResponseEntity = Mock(ResponseEntity)
+ mockRestTemplate.exchange({ it.toString() == 'http://some-uri/getResourceUrl' },
+ HttpMethod.GET, _ as HttpEntity, String.class) >> mockResponseEntity
+ when: 'the resource is fetched'
+ def result = objectUnderTest.getOperation(getResourceUrl)
+ then: 'the output of the method is equal to the output from the rest template service'
+ result == mockResponseEntity
+ }
+
+ def 'SDNC #scenario operation called.'() {
+ given: 'some request data'
+ def someRequestData = 'some request data'
+ and: 'a url for get module resources'
+ def getModuleResourceUrl = '/getModuleResourceUrl'
+ and: 'test configuration data'
+ setupTestConfigurationData()
+ and: 'the process returns a valid response entity'
+ def mockResponseEntity = Mock(ResponseEntity)
+ when: 'the resource is fetched'
+ def result = objectUnderTest.httpOperationWithJsonData(expectedHttpMethod, getModuleResourceUrl, someRequestData, new HttpHeaders())
+ then: 'the rest template is called with the correct uri and json in the body'
+ 1 * mockRestTemplate.exchange({ it.toString() == 'http://some-uri/getModuleResourceUrl' },
+ expectedHttpMethod, { it.body.contains(someRequestData) }, String.class) >> mockResponseEntity
+ and: 'the output of the method is the same as the output from the rest template service'
+ result == mockResponseEntity
+ where: 'the following values are used'
+ scenario || expectedHttpMethod
+ 'POST' || POST
+ 'PUT' || PUT
+ 'GET' || GET
+ 'DELETE' || DELETE
+ }
+
+ def 'SDNC GET operation with headers.'() {
+ given: 'a get url'
+ def getResourceUrl = '/getResourceUrl'
+ and: 'test configuration data'
+ setupTestConfigurationData()
+ and: 'the process returns a valid response entity'
+ def mockResponseEntity = Mock(ResponseEntity)
+ mockRestTemplate.exchange({ it.toString() == 'http://some-uri/getResourceUrl' },
+ HttpMethod.GET, _ as HttpEntity, String.class) >> mockResponseEntity
+ when: 'the resource is fetched with headers'
+ def result = objectUnderTest.getOperation(getResourceUrl, new HttpHeaders())
+ then: 'the output of the method is equal to the output from the rest template service'
+ result == mockResponseEntity
+ }
+
+ def setupTestConfigurationData() {
+ mockSdncProperties.baseUrl >> 'http://some-uri'
+ mockSdncProperties.authUsername >> 'some-username'
+ mockSdncProperties.authPassword >> 'some-password'
+ mockSdncProperties.topologyId >> 'some-topology-id'
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy
new file mode 100644
index 00000000..9dcb72e6
--- /dev/null
+++ b/dmi-service/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy
@@ -0,0 +1,176 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021-2022 Nordix Foundation
+ * Modifications Copyright (C) 2021-2022 Bell Canada
+ * ================================================================================
+ * 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.dmi.service.operation
+
+import org.onap.cps.ncmp.dmi.TestUtils
+import org.onap.cps.ncmp.dmi.config.DmiConfiguration
+import org.onap.cps.ncmp.dmi.exception.SdncException
+import org.onap.cps.ncmp.dmi.service.client.SdncRestconfClient
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.HttpHeaders
+import org.springframework.http.HttpMethod
+import org.springframework.http.HttpStatus
+import org.springframework.http.ResponseEntity
+import org.springframework.test.context.ContextConfiguration
+import spock.lang.Specification
+
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.CREATE
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.DELETE
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.PATCH
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.UPDATE
+import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.READ
+
+@SpringBootTest
+@ContextConfiguration(classes = [DmiConfiguration.SdncProperties, SdncOperations])
+class SdncOperationsSpec extends Specification {
+
+ @SpringBean
+ SdncRestconfClient mockSdncRestClient = Mock()
+
+ @Autowired
+ SdncOperations objectUnderTest
+
+ def 'get modules from node.'() {
+ given: 'a node id and url'
+ def nodeId = 'node1'
+ def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/ietf-netconf-monitoring:netconf-state/schemas'
+ and: 'sdnc returns one module during process'
+ mockSdncRestClient.getOperation(expectedUrl) >>
+ ResponseEntity.ok(TestUtils.getResourceFileContent('ModuleSchema.json'))
+ when: 'module schemas from node are fetched'
+ def moduleSchemas = objectUnderTest.getModuleSchemasFromNode(nodeId)
+ then: 'one module is found'
+ moduleSchemas.size() == 1
+ and: 'module schema has expected values'
+ with(moduleSchemas[0]) {
+ it.getIdentifier() == "example-identifier"
+ it.getNamespace() == "example:namespace"
+ it.getVersion() == "example-version"
+ it.getFormat() == "example-format"
+ it.getLocation() == ["example-location"]
+ }
+ }
+
+ def 'No modules from Node: SDNC Response - #scenario .'() {
+ given: 'node id and url'
+ def nodeId = 'node1'
+ def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/ietf-netconf-monitoring:netconf-state/schemas'
+ and: 'sdnc operation returns #scenario'
+ mockSdncRestClient.getOperation(expectedUrl) >> ResponseEntity.ok(responseBody)
+ when: 'the module schemas are requested'
+ def moduleSchemas = objectUnderTest.getModuleSchemasFromNode(nodeId)
+ then: 'no module schemas are returned'
+ moduleSchemas.size() == 0
+ where:
+ scenario | responseBody
+ 'null response body' | null
+ 'empty response body ' | ''
+ 'no module schema' | '{ "ietf-netconf-monitoring:schemas" : { "schema" : [] } } '
+ }
+
+ def 'Error handling - modules from node: #scenario'() {
+ given: 'node id and url'
+ def nodeId = 'node1'
+ def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/ietf-netconf-monitoring:netconf-state/schemas'
+ and: '#scenario is returned during process'
+ mockSdncRestClient.getOperation(expectedUrl) >> new ResponseEntity<>(sdncResponseBody, sdncHttpStatus)
+ when: 'module schemas from node are fetched'
+ objectUnderTest.getModuleSchemasFromNode(nodeId)
+ then: 'SDNCException is thrown'
+ def thrownException = thrown(SdncException)
+ thrownException.getDetails().contains(expectedExceptionDetails)
+ where:
+ scenario | sdncHttpStatus | sdncResponseBody || expectedExceptionDetails
+ 'failed response from SDNC' | HttpStatus.BAD_REQUEST | '{ "errorMessage" : "incorrect input"}' || '{ "errorMessage" : "incorrect input"}'
+ 'invalid json response' | HttpStatus.OK | 'invalid-json' || 'SDNC response is not in the expected format'
+ 'response in unexpected json schema' | HttpStatus.OK | '{ "format" : "incorrect" }' || 'SDNC response is not in the expected format'
+ }
+
+ def 'Get module resources from SDNC.'() {
+ given: 'node id and url'
+ def nodeId = 'some-node'
+ def expectedUrl = '/rests/operations/network-topology:network-topology/topology=test-topology/node=some-node/yang-ext:mount/ietf-netconf-monitoring:get-schema'
+ when: 'module resource is fetched with the expected parameters'
+ objectUnderTest.getModuleResource(nodeId, 'some-json-data')
+ then: 'the SDNC Rest client is invoked with the correct parameters'
+ 1 * mockSdncRestClient.httpOperationWithJsonData(HttpMethod.POST, expectedUrl, 'some-json-data', _ as HttpHeaders)
+ }
+
+ def 'Get resource data from node to SDNC.'() {
+ given: 'expected url'
+ def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/testResourceId?a=1&b=2&content=testContent'
+ when: 'resource data is fetched for given node ID'
+ objectUnderTest.getResouceDataForOperationalAndRunning('node1', 'testResourceId',
+ '(a=1,b=2)', 'content=testContent')
+ then: 'the SDNC get operation is executed with the correct URL'
+ 1 * mockSdncRestClient.getOperation(expectedUrl)
+ }
+
+ def 'Write resource data with #scenario operation to SDNC.'() {
+ given: 'expected url'
+ def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/testResourceId'
+ when: 'write resource data for passthrough running is called'
+ objectUnderTest.writeData(operationEnum, 'node1', 'testResourceId', 'application/json', 'requestData')
+ then: 'the #expectedHttpMethod operation is executed with the correct parameters'
+ 1 * mockSdncRestClient.httpOperationWithJsonData(expectedHttpMethod, expectedUrl, 'requestData', _ as HttpHeaders)
+ where: 'the following values are used'
+ scenario | operationEnum || expectedHttpMethod
+ 'Create' | CREATE || HttpMethod.POST
+ 'Update' | UPDATE || HttpMethod.PUT
+ 'Read' | READ || HttpMethod.GET
+ 'Delete' | DELETE || HttpMethod.DELETE
+ 'Patch' | PATCH || HttpMethod.PATCH
+ }
+
+ def 'build query param list for SDNC where options #scenario'() {
+ when: 'query param list is built'
+ def result = objectUnderTest.buildQueryParamMap(optionsParamInQuery, 'd=4')
+ .toSingleValueMap().toString()
+ then: 'result matches the expected result'
+ result == expectedResult
+ where: 'following parameters are used'
+ scenario | optionsParamInQuery || expectedResult
+ 'is single key-value pair' | '(a=x)' || '[a:x, d:4]'
+ 'is multiple key-value pairs' | '(a=x,b=y,c=z)' || '[a:x, b:y, c:z, d:4]'
+ 'has / as special char' | '(a=x,b=y,c=t/z)' || '[a:x, b:y, c:t/z, d:4]'
+ 'has " as special char' | '(a=x,b=y,c="z")' || '[a:x, b:y, c:"z", d:4]'
+ 'has [] as special char' | '(a=x,b=y,c=[z])' || '[a:x, b:y, c:[z], d:4]'
+ 'has = in value' | '(a=(x=y),b=x=y)' || '[a:(x=y), b:x=y, d:4]'
+ 'is empty' | '' || '[:]'
+ 'is null' | null || '[:]'
+ }
+
+ def 'options parameters contains a comma #scenario'() {
+ when: 'query param list is built with #scenario'
+ def result = objectUnderTest.buildQueryParamMap(optionsParamInQuery, 'd=4').toSingleValueMap()
+ then: 'expect 2 elements from options where we are ignoring empty query param value, +1 from content query param (2+1) = 3 elements'
+ def expectedNoOfElements = 3
+ and: 'results contains equal elements as expected'
+ result.size() == expectedNoOfElements
+ where: 'following parameters are used'
+ scenario | optionsParamInQuery
+ '"," in value' | '(a=(x,y),b=y)'
+ '"," in string value' | '(a="x,y",b=y)'
+ }
+}
diff --git a/dmi-service/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java b/dmi-service/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java
new file mode 100644
index 00000000..c10d91a5
--- /dev/null
+++ b/dmi-service/src/test/java/org/onap/cps/ncmp/dmi/TestUtils.java
@@ -0,0 +1,54 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * Common convenience methods for testing.
+ */
+public class TestUtils {
+
+ /**
+ * Convert a file in the test resource folder to file.
+ *
+ * @param filename to name of the file in test/resources
+ * @return the file
+ * @throws IOException when there is an IO issue
+ */
+ public static File readFile(final String filename) {
+ return new File(ClassLoader.getSystemClassLoader().getResource(filename).getFile());
+ }
+
+ /**
+ * Convert a file in the test resource folder to a string.
+ *
+ * @param filename to name of the file in test/resources
+ * @return the content of the file as a String
+ * @throws IOException when there is an IO issue
+ */
+ public static String getResourceFileContent(final String filename) throws IOException {
+ final File file = readFile(filename);
+ return new String(Files.readAllBytes(file.toPath()));
+ }
+}
diff --git a/dmi-service/src/test/java/org/onap/cps/ncmp/dmi/rest/controller/TestController.java b/dmi-service/src/test/java/org/onap/cps/ncmp/dmi/rest/controller/TestController.java
new file mode 100644
index 00000000..5240e239
--- /dev/null
+++ b/dmi-service/src/test/java/org/onap/cps/ncmp/dmi/rest/controller/TestController.java
@@ -0,0 +1,35 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2021 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.dmi.rest.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class TestController {
+
+ @GetMapping("/test")
+ ResponseEntity<String> test() {
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+}
diff --git a/dmi-service/src/test/resources/ModuleSchema.json b/dmi-service/src/test/resources/ModuleSchema.json
new file mode 100644
index 00000000..50c67154
--- /dev/null
+++ b/dmi-service/src/test/resources/ModuleSchema.json
@@ -0,0 +1,15 @@
+{
+ "ietf-netconf-monitoring:schemas": {
+ "schema": [
+ {
+ "identifier": "example-identifier",
+ "version": "example-version",
+ "format": "example-format",
+ "namespace": "example:namespace",
+ "location": [
+ "example-location"
+ ]
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/resources/application.yml b/dmi-service/src/test/resources/application.yml
new file mode 100644
index 00000000..ddc2b45f
--- /dev/null
+++ b/dmi-service/src/test/resources/application.yml
@@ -0,0 +1,79 @@
+# ============LICENSE_START=======================================================
+# Copyright (C) 2021-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=========================================================
+
+rest:
+ api:
+ dmi-base-path: /dmi
+
+security:
+ permit-uri: /actuator/**,/swagger-ui/**,/swagger-resources/**,/v3/api-docs
+ auth:
+ username: cpsuser
+ password: cpsr0cks!
+
+sdnc:
+ baseUrl: http://test
+ topologyId: test-topology
+ auth:
+ username: test
+ password: test
+
+cps-core:
+ baseUrl: some url for cps
+ dmiRegistrationUrl: some registration url
+ auth:
+ username: some cps core user
+ password: some cps core password
+
+dmi:
+ service:
+ url: some url for the dmi service
+ avc:
+ subscription-topic: ncmp-dmi-cm-avc-subscription
+ subscription-response-topic: dmi-ncmp-cm-avc-subscription
+
+spring:
+ application:
+ name: ncmp-dmi-plugin
+ mvc:
+ pathmatch:
+ matching-strategy: ANT_PATH_MATCHER
+ kafka:
+ bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVER}
+ security:
+ protocol: PLAINTEXT
+ producer:
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ value-serializer: io.cloudevents.kafka.CloudEventSerializer
+ client-id: ncmp-dmi-plugin
+ consumer:
+ group-id: ${NCMP_CONSUMER_GROUP_ID:ncmp-group}
+ key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+ value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+ properties:
+ spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
+ spring.deserializer.value.delegate.class: io.cloudevents.kafka.CloudEventDeserializer
+ spring.json.use.type.headers: false
+
+app:
+ ncmp:
+ async:
+ topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
+
+logging:
+ format: json
diff --git a/dmi-service/src/test/resources/cmNotificationSubscriptionCreationEvent.json b/dmi-service/src/test/resources/cmNotificationSubscriptionCreationEvent.json
new file mode 100644
index 00000000..3b780976
--- /dev/null
+++ b/dmi-service/src/test/resources/cmNotificationSubscriptionCreationEvent.json
@@ -0,0 +1,43 @@
+{
+ "data": {
+ "cmhandles": [
+ {
+ "cmhandleId": "CMHandle1",
+ "private-properties": {
+ "prop1": "prop-value"
+ }
+ },
+ {
+ "cmhandleId": "CMHandle2",
+ "private-properties": {
+ "prop-x": "prop-valuex",
+ "prop-p": "prop-valuep"
+ }
+ },
+ {
+ "cmhandleId": "CMHandle3",
+ "private-properties": {
+ "prop-y": "prop-valuey"
+ }
+ }
+ ],
+ "predicates": [
+ {
+ "targetFilter": [
+ "CMHandle1",
+ "CMHandle2",
+ "CMHandle3"
+ ],
+ "scopeFilter": {
+ "datastore": "ncmp-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/dmi-service/src/test/resources/createDataWithNormalChar.json b/dmi-service/src/test/resources/createDataWithNormalChar.json
new file mode 100644
index 00000000..31cdf1c5
--- /dev/null
+++ b/dmi-service/src/test/resources/createDataWithNormalChar.json
@@ -0,0 +1,8 @@
+{
+ "operation": "create",
+ "dataType": "application/json",
+ "data": "normal request body",
+ "cmHandleProperties": {
+ "some-property": "some-property-value"
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/resources/createDataWithSpecialChar.json b/dmi-service/src/test/resources/createDataWithSpecialChar.json
new file mode 100644
index 00000000..1e7622ee
--- /dev/null
+++ b/dmi-service/src/test/resources/createDataWithSpecialChar.json
@@ -0,0 +1,8 @@
+{
+ "operation": "create",
+ "dataType": "application/json",
+ "data": "data with quote \" and new line \n",
+ "cmHandleProperties": {
+ "some-property": "some-property-value"
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/resources/deleteData.json b/dmi-service/src/test/resources/deleteData.json
new file mode 100644
index 00000000..2233fa01
--- /dev/null
+++ b/dmi-service/src/test/resources/deleteData.json
@@ -0,0 +1,8 @@
+{
+ "operation": "delete",
+ "dataType": "application/json",
+ "data": "normal request body",
+ "cmHandleProperties": {
+ "some-property": "some-property-value"
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/resources/moduleResources.json b/dmi-service/src/test/resources/moduleResources.json
new file mode 100644
index 00000000..23adfcba
--- /dev/null
+++ b/dmi-service/src/test/resources/moduleResources.json
@@ -0,0 +1,18 @@
+{
+ "data": {
+ "modules": [
+ {
+ "name": "ietf-yang-library",
+ "revision": "2016-06-21"
+ },
+ {
+ "name": "nc-notifications",
+ "revision": "2008-07-14"
+ }
+ ]
+ },
+ "cmHandleProperties": {
+ "subsystemId": "system-001"
+ },
+ "moduleSetTag": "module-set-tag1"
+}
diff --git a/dmi-service/src/test/resources/patchData.json b/dmi-service/src/test/resources/patchData.json
new file mode 100644
index 00000000..e5466eaf
--- /dev/null
+++ b/dmi-service/src/test/resources/patchData.json
@@ -0,0 +1,8 @@
+{
+ "operation": "patch",
+ "dataType": "application/yang.patch+json",
+ "data": "normal request body",
+ "cmHandleProperties": {
+ "some-property": "some-property-value"
+ }
+} \ No newline at end of file
diff --git a/dmi-service/src/test/resources/readData.json b/dmi-service/src/test/resources/readData.json
new file mode 100644
index 00000000..53f6d2ed
--- /dev/null
+++ b/dmi-service/src/test/resources/readData.json
@@ -0,0 +1,9 @@
+{
+ "operation": "read",
+ "dataType": "application/json",
+ "data": "normal request body",
+ "cmHandleProperties": {
+ "some-property": "some-property-value"
+ },
+ "moduleSetTag": "module-set-tag-example"
+} \ No newline at end of file
diff --git a/dmi-service/src/test/resources/updateData.json b/dmi-service/src/test/resources/updateData.json
new file mode 100644
index 00000000..7cbf4f0c
--- /dev/null
+++ b/dmi-service/src/test/resources/updateData.json
@@ -0,0 +1,8 @@
+{
+ "operation": "update",
+ "dataType": "application/json",
+ "data": "normal request body",
+ "cmHandleProperties": {
+ "some-property": "some-property-value"
+ }
+} \ No newline at end of file