diff options
35 files changed, 1018 insertions, 557 deletions
@@ -4,4 +4,3 @@ port=29418 project=cps/ncmp-dmi-plugin defaultbranch=master -
\ No newline at end of file diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index a9229e98..25974baf 100755 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -22,13 +22,13 @@ version: "3.7" services: ncmp-dmi-plugin: container_name: ncmp-dmi-plugin - image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/ncmp-dmi-plugin:${VERSION:-latest} + image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/ncmp-dmi-plugin:${DMI_VERSION:-latest} ports: - - "8783:8080" - - "8787:8081" + - ${DMI_PORT:-8783}:8080 + - ${DMI_MANAGEMENT_PORT:-8787}:8081 environment: - CPS_USERNAME: ${CPS_USERNAME:-cpsuser} - CPS_PASSWORD: ${CPS_PASSWORD:-cpsr0cks!} + CPS_USERNAME: ${CPS_CORE_USERNAME:-cpsuser} + CPS_PASSWORD: ${CPS_CORE_PASSWORD:-cpsr0cks!} CPS_CORE_HOST: ${CPS_CORE_HOST:-cps} CPS_CORE_PORT: ${CPS_CORE_PORT:-8080} CPS_CORE_USERNAME: ${CPS_CORE_USERNAME:-cpsuser} @@ -37,4 +37,7 @@ services: SDNC_PORT: ${SDNC_PORT:-8181} SDNC_USERNAME: ${SDNC_USERNAME:-admin} SDNC_PASSWORD: ${SDNC_PASSWORD:-Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U} + DMI_SERVICE_URL: ${DMI_SERVICE_URL:-http://ncmp-dmi-plugin:8783} + DMI_USERNAME: ${DMI_USERNAME:-cpsuser} + DMI_PASSWORD: ${DMI_PASSWORD:-cpsr0cks!} restart: unless-stopped diff --git a/docs/_static/dmi-plugin-r9-arch-diagram.PNG b/docs/_static/dmi-plugin-r9-arch-diagram.PNG Binary files differdeleted file mode 100644 index a7910819..00000000 --- a/docs/_static/dmi-plugin-r9-arch-diagram.PNG +++ /dev/null diff --git a/docs/admin-guide.rst b/docs/admin-guide.rst index 787e931b..a1af64aa 100644 --- a/docs/admin-guide.rst +++ b/docs/admin-guide.rst @@ -9,4 +9,4 @@ DMI-Plugin Admin Guide ###################### -For DMI-Plugin Admin Guide, please refer to the `CPS Admin Guide. <https://docs.onap.org/projects/onap-cps/en/latest/admin-guide.html>`_
\ No newline at end of file +Please refer to the common :ref:`CPS Admin Guide<onap-cps:adminGuide>` diff --git a/docs/api/swagger/openapi.yaml b/docs/api/swagger/openapi.yaml new file mode 100644 index 00000000..54676fe5 --- /dev/null +++ b/docs/api/swagger/openapi.yaml @@ -0,0 +1,409 @@ +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: + - name: cmHandle + in: path + description: The cm handle to fetch all the modules + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: Operational body + content: + application/json: + schema: + $ref: '#/components/schemas/ModuleReferencesRequest' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ModuleSet' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + /v1/ch/{cmHandle}/moduleResources: + post: + tags: + - dmi-plugin + summary: Retrieve module resources + description: Retrieve module resources for one or more modules + operationId: retrieveModuleResources + parameters: + - 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 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ModuleResourcesReadRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/YangResources' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + /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) + operationId: registerCmHandles + requestBody: + description: list of cm handles + content: + application/json: + schema: + $ref: '#/components/schemas/CmHandles' + required: true + responses: + "201": + description: Created + content: + text/plain: + schema: + type: string + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + x-api-audience: component-internal + /v1/ch/{cmHandle}/data/ds/ncmp-datastore:passthrough-operational: + post: + tags: + - dmi-plugin + summary: Get resource data from passthrough-operational for cm handle + description: Get resource data from passthrough-operational for cm handle. Will + support read operations only. + operationId: dataAccessPassthroughOperational + parameters: + - 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 + - name: resourceIdentifier + in: query + description: Resource identifier to get/set the resource data + required: true + allowReserved: true + schema: + type: string + - name: accept + in: header + description: Accept parameter for response, if accept parameter is null, that + means client can accept any format. + schema: + type: string + enum: + - application/json + - application/yang-data+json + - name: options + in: query + description: options parameter in query, it is mandatory to wrap key(s)=value(s) + in parenthesis'()'. + required: false + allowReserved: true + 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]) + requestBody: + description: Operational body + content: + application/json: + schema: + $ref: '#/components/schemas/DataAccessRequest' + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + /v1/ch/{cmHandle}/data/ds/ncmp-datastore:passthrough-running: + post: + tags: + - dmi-plugin + summary: Get, Create or Update request for data passthrough-running for a cm-handle + description: Post request to Get, Create or to Update resource data for a cm-handle. + Since all requests need to include additional information in a request body + HTTP Post is used for all use cases and the actual operation is defined in + the request body instead. + operationId: dataAccessPassthroughRunning + parameters: + - 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 + - name: resourceIdentifier + in: query + description: Resource identifier to get/set the resource data + required: true + allowReserved: true + schema: + type: string + - name: accept + in: header + description: Accept parameter for response, if accept parameter is null, that + means client can accept any format. + schema: + type: string + enum: + - application/json + - application/yang-data+json + - name: options + in: query + description: options parameter in query, it is mandatory to wrap key(s)=value(s) + in parenthesis'()'. + required: false + allowReserved: true + 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]) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DataAccessRequest' + responses: + "201": + description: Created + content: + text/plain: + schema: + type: string + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' +components: + schemas: + ModuleReferencesRequest: + type: object + properties: + cmHandleProperties: + $ref: '#/components/schemas/cmHandleProperties' + cmHandleProperties: + type: object + additionalProperties: + type: string + example: '{"prop1":"value1","prop2":"value2"}' + ModuleSet: + type: object + properties: + schemas: + type: array + items: + $ref: '#/components/schemas/ModuleSet_schemas' + revision: + type: string + example: someRevision + ErrorMessage: + title: Error + type: object + properties: + status: + type: string + message: + type: string + details: + type: string + ModuleResourcesReadRequest: + type: object + properties: + data: + $ref: '#/components/schemas/ModuleResourcesReadRequest_data' + cmHandleProperties: + $ref: '#/components/schemas/cmHandleProperties' + name: + type: string + example: someName + YangResources: + type: array + items: + $ref: '#/components/schemas/YangResource' + YangResource: + properties: + yangSource: + type: string + moduleName: + type: string + revision: + $ref: '#/components/schemas/revision' + CmHandles: + type: object + properties: + cmHandles: + type: array + items: + type: string + DataAccessRequest: + type: object + properties: + operation: + type: string + enum: + - read + - create + - update + - delete + dataType: + type: string + data: + type: string + cmHandleProperties: + $ref: '#/components/schemas/cmHandleProperties' + ModuleSet_schemas: + type: object + properties: + moduleName: + type: string + revision: + $ref: '#/components/schemas/revision' + namespace: + type: string + ModuleResourcesReadRequest_data_modules: + type: object + properties: + name: + $ref: '#/components/schemas/name' + revision: + $ref: '#/components/schemas/revision' + ModuleResourcesReadRequest_data: + type: object + properties: + modules: + type: array + items: + $ref: '#/components/schemas/ModuleResourcesReadRequest_data_modules' diff --git a/docs/architecture.rst b/docs/architecture.rst index 069caa6f..a7bc974d 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -52,4 +52,4 @@ The DMI-Plugin provides following interfaces. - Query module references - REST -More details on the CPS interface CPS-E-05 which is responsible for the DMI-Plugin can be found on the `CPS Architecture Page <https://docs.onap.org/projects/onap-cps/en/latest/architecture.html>`_.
\ No newline at end of file +More details on the CPS interface CPS-E-05 which is responsible for the DMI-Plugin can be found on the :ref:`CPS Architecture page<onap-cps:architecture>` diff --git a/docs/conf.py b/docs/conf.py index 3aaddd19..723fb21f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,14 +20,15 @@ from docutils.parsers.rst import directives from docs_conf.conf import * +#change 'latest' to relevant branch-name once branch has been created branch = 'latest' doc_url = 'https://docs.onap.org/projects' master_doc = 'index' intersphinx_mapping = {} -# Latest (change to branch) intersphinx_mapping['onap-cps'] = ('{}/onap-cps/en/%s'.format(doc_url) % branch, None) +intersphinx_mapping['onap-cps-cps-temporal'] = ('{}/onap-cps-cps-temporal/en/%s'.format(doc_url) % branch, None) linkcheck_ignore = [ 'http://localhost', diff --git a/docs/deployment.rst b/docs/deployment.rst index 9c3da025..0d7b1031 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -15,7 +15,7 @@ DMI-Plugin Deployment Deployment ========== -Refer to `CPS Deployment <https://docs.onap.org/projects/onap-cps/en/latest/deployment.html>`_ +Refer to :ref:`CPS-Deployment<onap-cps:deployment>` page for deployment documentation related to DMI-Plugin and all CPS components. Additional DMI-Plugin Core Customisations @@ -34,7 +34,7 @@ values to configure for the application being deployed. This list is not exhaust +---------------------------------------+-------------------------------------------------------------------------------------------------------------+-------------------------------------------------+ | config.appUserPassword | Password used by the DMI-Plugin to authenticate users for the REST APIs that it exposes. | Not defined | | | If not defined, the password is generated when deploying the application. | | -| | See also `Credentials Retrieval <https://docs.onap.org/projects/onap-cps/en/latest/deployment.html>`_ | | +| | See also :ref:`CPS Credentials Retrieval<onap-cps:cps_common_credentials_retrieval>` | | +---------------------------------------+-------------------------------------------------------------------------------------------------------------+-------------------------------------------------+ | config.dmiServiceName | DMI-Plugin hostname and port. | Not defined | +---------------------------------------+-------------------------------------------------------------------------------------------------------------+-------------------------------------------------+ @@ -56,4 +56,4 @@ DMI-Plugin Docker Installation DMI-Plugin can also be installed in a docker environment. Latest `docker-compose <https://github.com/onap/cps-ncmp-dmi-plugin/blob/master/docker-compose/docker-compose.yml>`_ is included in the repo to start all the relevant services. -Latest instructions are covered in the `README <https://github.com/onap/cps-ncmp-dmi-plugin/blob/master/docker-compose/README.md>`_
\ No newline at end of file +Latest instructions are covered in the `README <https://github.com/onap/cps-ncmp-dmi-plugin/blob/master/docker-compose/README.md>`_ diff --git a/docs/design.rst b/docs/design.rst index d0d4b87f..9326fd3d 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -17,7 +17,7 @@ Offered APIs The DMI-Plugin supports the public APIs listed in the link below: -:download:`DMI Rest OpenApi Specification <openapi/openapi.yaml>` +:download:`DMI Rest OpenApi Specification <api/swagger/openapi.yaml>` View Offered APIs ----------------- diff --git a/docs/index.rst b/docs/index.rst index 7d1fcf37..236b1c8e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,8 +8,8 @@ .. THIS IS USED INTERNALLY IN CPS ONLY .. _dmi-framework-doc: -DMI-Plugin Documentation for the Istanbul-R9 Release ----------------------------------------------------- +DMI-Plugin Documentation +------------------------ .. toctree:: :maxdepth: 1 @@ -23,12 +23,12 @@ DMI-Plugin Documentation for the Istanbul-R9 Release release-notes.rst -CPS-NCMP Documentation for the Istanbul-R9 Release ---------------------------------------------------------- +CPS-NCMP Documentation +---------------------- -* `CPS-NCMP <https://docs.onap.org/projects/onap-cps/en/latest/index.html>`_ +* :ref:`CPS-NCMP<onap-cps:master_index>` -CPS-Temporal Documentation for the Istanbul-R9 Release ------------------------------------------------------- +CPS-Temporal Documentation +-------------------------- -* `CPS-Temporal <https://docs.onap.org/projects/onap-cps-cps-temporal/en/latest/index.html>`_ +* :ref:`CPS-Temporal<onap-cps-cps-temporal:master_index>` diff --git a/docs/modeling.rst b/docs/modeling.rst index d4f6bbed..a69eec52 100644 --- a/docs/modeling.rst +++ b/docs/modeling.rst @@ -10,5 +10,4 @@ DMI-Plugin Modeling :maxdepth: 1 The DMI-Plugin acts as a proxy and as such does not have its own data model. For more information on the data which -is passed through the DMI-Plugin, please refer to the `NCMP Modeling Section <https://docs.onap.org/projects/onap-cps/en/latest/modeling.html#ncmp-modeling>`_ -of the `CPS Modeling Page <https://docs.onap.org/projects/onap-cps/en/latest/modeling.html>`_. +is passed through the DMI-Plugin, please refer to :ref:`NCMP Modeling<onap-cps:cps_ncmp_modelling>`. diff --git a/docs/openapi/components.yml b/openapi/components.yml index 30e5987f..cdf4c1c0 100644 --- a/docs/openapi/components.yml +++ b/openapi/components.yml @@ -73,21 +73,12 @@ components: revision: $ref: '#/components/schemas/revision' - DataAccessReadRequest: + DataAccessRequest: type: object properties: operation: type: string - enum: [ read ] - cmHandleProperties: - $ref: '#/components/schemas/cmHandleProperties' - - DataAccessWriteRequest: - type: object - properties: - operation: - type: string - enum: [ create ] + enum: [ read, create, update, delete ] dataType: type: string data: diff --git a/docs/openapi/openapi.yml b/openapi/openapi.yml index 83c05abb..5c095a7a 100644 --- a/docs/openapi/openapi.yml +++ b/openapi/openapi.yml @@ -22,7 +22,7 @@ info: description: Adds Data Model Inventory Registry capability for ONAP version: "1.0.0" servers: - - url: //localhost:8088/ + - url: /dmi tags: - name: dmi-plugin-internal description: DMI plugin internal rest apis @@ -74,31 +74,6 @@ paths: '403': $ref: 'components.yml#/components/responses/Forbidden' - /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': - $ref: 'components.yml#/components/responses/Created' - '400': - $ref: 'components.yml#/components/responses/BadRequest' - '401': - $ref: 'components.yml#/components/responses/Unauthorized' - '403': - $ref: 'components.yml#/components/responses/Forbidden' - /v1/ch/{cmHandle}/moduleResources: post: description: Retrieve module resources for one or more modules @@ -128,27 +103,24 @@ paths: '403': $ref: 'components.yml#/components/responses/Forbidden' - /v1/ch/{cmHandle}/data/ds/ncmp-datastore:passthrough-operational: - put: + /v1/inventory/cmHandles: + post: tags: - - dmi-plugin - summary: Get resource data from passthrough-operational for cm handle - description: Get resource data from passthrough-operational for cm handle - operationId: getResourceDataOperationalForCmHandle - parameters: - - $ref: 'components.yml#/components/parameters/cmHandleInPath' - - $ref: 'components.yml#/components/parameters/resourceIdentifierInQuery' - - $ref: 'components.yml#/components/parameters/acceptParamInHeader' - - $ref: 'components.yml#/components/parameters/optionsParamInQuery' + - 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: Operational body + description: list of cm handles content: application/json: schema: - $ref: 'components.yml#/components/schemas/DataAccessReadRequest' + $ref: 'components.yml#/components/schemas/CmHandles' + required: true responses: - '200': - $ref: 'components.yml#/components/responses/Ok' + '201': + $ref: 'components.yml#/components/responses/Created' '400': $ref: 'components.yml#/components/responses/BadRequest' '401': @@ -156,13 +128,13 @@ paths: '403': $ref: 'components.yml#/components/responses/Forbidden' - /v1/ch/{cmHandle}/data/ds/ncmp-datastore:passthrough-running: - put: + /v1/ch/{cmHandle}/data/ds/ncmp-datastore:passthrough-operational: + post: tags: - dmi-plugin - summary: Get resource data from passthrough-running for cm handle - description: Get resource data from passthrough-running for cm handle - operationId: getResourceDataPassthroughRunningForCmHandle + summary: Get resource data from passthrough-operational for cm handle + description: Get resource data from passthrough-operational for cm handle. Will support read operations only. + operationId: dataAccessPassthroughOperational parameters: - $ref: 'components.yml#/components/parameters/cmHandleInPath' - $ref: 'components.yml#/components/parameters/resourceIdentifierInQuery' @@ -173,7 +145,7 @@ paths: content: application/json: schema: - $ref: 'components.yml#/components/schemas/DataAccessReadRequest' + $ref: 'components.yml#/components/schemas/DataAccessRequest' responses: '200': $ref: 'components.yml#/components/responses/Ok' @@ -184,21 +156,23 @@ paths: '403': $ref: 'components.yml#/components/responses/Forbidden' + /v1/ch/{cmHandle}/data/ds/ncmp-datastore:passthrough-running: post: - description: Write data for a cmHandle using passthrough-running tags: - dmi-plugin - summary: Write data for a cmHandle - operationId: writeDataByPassthroughRunningForCmHandle + summary: Get, Create or Update request for data passthrough-running for a cm-handle + description: Post request to Get, Create or to Update resource data for a cm-handle. Since all requests need to include additional information in a request body HTTP Post is used for all use cases and the actual operation is defined in the request body instead. + operationId: dataAccessPassthroughRunning parameters: - $ref: 'components.yml#/components/parameters/cmHandleInPath' - $ref: 'components.yml#/components/parameters/resourceIdentifierInQuery' + - $ref: 'components.yml#/components/parameters/acceptParamInHeader' + - $ref: 'components.yml#/components/parameters/optionsParamInQuery' requestBody: - required: true content: application/json: schema: - $ref: 'components.yml#/components/schemas/DataAccessWriteRequest' + $ref: 'components.yml#/components/schemas/DataAccessRequest' responses: '201': $ref: 'components.yml#/components/responses/Created' @@ -208,4 +182,3 @@ paths: $ref: 'components.yml#/components/responses/Unauthorized' '403': $ref: 'components.yml#/components/responses/Forbidden' - @@ -34,7 +34,7 @@ </organization> <groupId>org.onap.cps</groupId> <artifactId>ncmp-dmi-plugin</artifactId> - <version>1.0.1-SNAPSHOT</version> + <version>1.1.0-SNAPSHOT</version> <name>ncmp-dmi-plugin</name> <description>DMI Plugin Service</description> <properties> @@ -51,7 +51,7 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> - <version>2.5.0</version> + <version>2.5.5</version> <type>pom</type> <scope>import</scope> </dependency> @@ -78,6 +78,10 @@ <artifactId>groovy</artifactId> </dependency> <dependency> + <groupId>com.jayway.jsonpath</groupId> + <artifactId>json-path</artifactId> + </dependency> + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> @@ -99,6 +103,11 @@ <scope>test</scope> </dependency> <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.6.2</version> @@ -164,7 +173,7 @@ <goal>generate</goal> </goals> <configuration> - <inputSpec>${project.basedir}/docs/openapi/openapi.yml</inputSpec> + <inputSpec>${project.basedir}/openapi/openapi.yml</inputSpec> <language>spring</language> <generateSupportingFiles>false</generateSupportingFiles> <apiPackage>org.onap.cps.ncmp.dmi.rest.api</apiPackage> @@ -184,7 +193,7 @@ </goals> <phase>compile</phase> <configuration> - <inputSpec>${project.basedir}/docs/openapi/openapi.yml</inputSpec> + <inputSpec>${project.basedir}/openapi/openapi.yml</inputSpec> <language>openapi-yaml</language> </configuration> </execution> diff --git a/releases/1.0.1-container.yaml b/releases/1.0.1-container.yaml new file mode 100644 index 00000000..681a8261 --- /dev/null +++ b/releases/1.0.1-container.yaml @@ -0,0 +1,9 @@ +distribution_type: container +container_release_tag: 1.0.1 +project: cps/ncmp-dmi-plugin +log_dir: cps-ncmp-dmi-plugin-maven-docker-stage-master/145 +ref: 2852caad1d57acdc0bfc816598dac3864cf09d64 +tag_release: true +containers: + - name: 'ncmp-dmi-plugin' + version: '1.0.1-20211013T114550Z'
\ No newline at end of file diff --git a/releases/1.0.1.yaml b/releases/1.0.1.yaml new file mode 100644 index 00000000..b24e7125 --- /dev/null +++ b/releases/1.0.1.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-ncmp-dmi-plugin-maven-stage-master/145/ +project: cps/ncmp-dmi-plugin +version: 1.0.1
\ No newline at end of file diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchemaList.java b/src/main/java/org/onap/cps/ncmp/dmi/exception/SdncException.java index a4af1761..a83485a9 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchemaList.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/exception/SdncException.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation + * 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. @@ -18,19 +18,32 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.dmi.service.model; +package org.onap.cps.ncmp.dmi.exception; -import java.util.List; -import lombok.Getter; -import lombok.Setter; +import org.springframework.http.HttpStatus; -/** - * ModuleSchemaList. +/* +Use this exception when SDNC contract fails */ -@Getter -@Setter -public class ModuleSchemaList { +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)); + } - private List<ModuleSchemaProperties> schema; + public SdncException(final String message, final String details, final Throwable cause) { + super(message, details, cause); + } } diff --git a/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java b/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java index b17280ae..97848c3e 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/rest/controller/DmiRestController.java @@ -26,8 +26,7 @@ import java.util.List; import javax.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.dmi.model.CmHandles; -import org.onap.cps.ncmp.dmi.model.DataAccessReadRequest; -import org.onap.cps.ncmp.dmi.model.DataAccessWriteRequest; +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; @@ -74,25 +73,6 @@ public class DmiRestController implements DmiPluginApi, DmiPluginInternalApi { } /** - * Write data using passthrough for the given cmHandle. - * - * @param dataAccessWriteRequest pass through request - * @param resourceIdentifier resource identifier - * @param cmHandle cmHandle - * @return (@ code ResponseEntity) response entity - */ - @Override - public ResponseEntity<String> writeDataByPassthroughRunningForCmHandle( - final DataAccessWriteRequest dataAccessWriteRequest, - final String resourceIdentifier, final String cmHandle) { - final String response = dmiService.writeResourceDataPassthroughForCmHandle(cmHandle, - resourceIdentifier, - MediaType.APPLICATION_JSON_VALUE, - dataAccessWriteRequest.getData()); - return new ResponseEntity<>(response, HttpStatus.CREATED); - } - - /** * This method register given list of cm-handles to ncmp. * * @param cmHandles list of cm-handles @@ -109,52 +89,84 @@ public class DmiRestController implements DmiPluginApi, DmiPluginInternalApi { /** * This method fetches the resource for given cm handle using pass through operational. It filters the response on - * the basis of options query parameters and returns response. + * the basis of options query parameters and returns response. Does not support write operations. * * @param resourceIdentifier resource identifier to fetch data * @param cmHandle cm handle identifier - * @param dataAccessReadRequest data Access Read Request + * @param dataAccessRequest data Access Request * @param acceptParamInHeader accept header parameter * @param optionsParamInQuery options query parameter * @return {@code ResponseEntity} response entity */ @Override - public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String resourceIdentifier, - final String cmHandle, - final @Valid DataAccessReadRequest dataAccessReadRequest, - final String acceptParamInHeader, - final @Valid String optionsParamInQuery) { - final var modulesListAsJson = dmiService.getResourceDataOperationalForCmHandle(cmHandle, - resourceIdentifier, - acceptParamInHeader, - optionsParamInQuery, - dataAccessReadRequest.getCmHandleProperties()); - return ResponseEntity.ok(modulesListAsJson); + public ResponseEntity<Object> dataAccessPassthroughOperational(final String resourceIdentifier, + final String cmHandle, + final @Valid DataAccessRequest + dataAccessRequest, + final String acceptParamInHeader, + final @Valid String optionsParamInQuery) { + if (isReadOperation(dataAccessRequest)) { + final String resourceDataAsJson = dmiService.getResourceData(cmHandle, + resourceIdentifier, + acceptParamInHeader, + optionsParamInQuery, + DmiService.RESTCONF_CONTENT_PASSTHROUGH_OPERATIONAL_QUERY_PARAM); + return ResponseEntity.ok(resourceDataAsJson); + } + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } - /** - * This method fetches the resource for given cm handle using pass through running. It filters the response on the - * basis of options query parameters and returns response. - * - * @param resourceIdentifier resource identifier to fetch data - * @param cmHandle cm handle identifier - * @param dataAccessReadRequest data Access Read Request - * @param acceptParamInHeader accept header parameter - * @param optionsParamInQuery options query parameter - * @return {@code ResponseEntity} response entity - */ @Override - public ResponseEntity<Object> getResourceDataPassthroughRunningForCmHandle(final String resourceIdentifier, - final String cmHandle, - final @Valid DataAccessReadRequest dataAccessReadRequest, - final String acceptParamInHeader, - final @Valid String optionsParamInQuery) { - final var modulesListAsJson = dmiService.getResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceIdentifier, - acceptParamInHeader, - optionsParamInQuery, - dataAccessReadRequest.getCmHandleProperties()); - return ResponseEntity.ok(modulesListAsJson); + public ResponseEntity<String> dataAccessPassthroughRunning(final String resourceIdentifier, + final String cmHandle, + final @Valid DataAccessRequest + dataAccessRequest, + final String acceptParamInHeader, + final @Valid String optionsParamInQuery) { + final String sdncResponse; + if (isReadOperation(dataAccessRequest)) { + sdncResponse = dmiService.getResourceData(cmHandle, + resourceIdentifier, + acceptParamInHeader, + optionsParamInQuery, + DmiService.RESTCONF_CONTENT_PASSTHROUGH_RUNNING_QUERY_PARAM); + } else { + sdncResponse = dmiService.writeData( + dataAccessRequest.getOperation(), + cmHandle, + resourceIdentifier, + MediaType.APPLICATION_JSON_VALUE, + dataAccessRequest.getData()); + } + return new ResponseEntity<>(sdncResponse, getHttpStatus(dataAccessRequest)); + } + + private boolean isReadOperation(final @Valid DataAccessRequest dataAccessRequest) { + return dataAccessRequest.getOperation() == null + || dataAccessRequest.getOperation().equals(DataAccessRequest.OperationEnum.READ); + } + + private HttpStatus getHttpStatus(final DataAccessRequest dataAccessRequest) { + final HttpStatus httpStatus; + if (dataAccessRequest.getOperation() == null) { + httpStatus = HttpStatus.OK; + } else { + switch (dataAccessRequest.getOperation()) { + case CREATE: + httpStatus = HttpStatus.CREATED; + break; + case READ: + case UPDATE: + httpStatus = HttpStatus.OK; + break; + case DELETE: + httpStatus = HttpStatus.NO_CONTENT; + break; + default: + httpStatus = HttpStatus.BAD_REQUEST; + } + } + return httpStatus; } private List<ModuleReference> convertRestObjectToJavaApiObject( diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java index c6910399..0f3fcc0c 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiService.java @@ -21,18 +21,23 @@ package org.onap.cps.ncmp.dmi.service; import java.util.List; -import java.util.Map; import javax.validation.constraints.NotNull; 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. * @@ -60,41 +65,24 @@ public interface DmiService { YangResources getModuleResources(String cmHandle, List<ModuleReference> modules); /** - * This method use to fetch the resource data from cm handle for datastore pass-through operational 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 acceptParamInHeader accept header parameter - * @param optionsParamInQuery options query parameter - * @param cmHandlePropertyMap cm handle properties - * @return {@code Object} response from network function - */ - Object getResourceDataOperationalForCmHandle(@NotNull String cmHandle, - @NotNull String resourceIdentifier, - String acceptParamInHeader, - String optionsParamInQuery, - Map<String, String> cmHandlePropertyMap); - - /** - * This method use to fetch the resource data from cm handle for datastore pass-through running and resource + * 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 acceptParamInHeader accept header parameter - * @param optionsParamInQuery options query parameter - * @param cmHandlePropertyMap cm handle properties + * @param cmHandle cm handle identifier + * @param resourceIdentifier resource identifier + * @param acceptParamInHeader accept header parameter + * @param optionsParamInQuery options query parameter + * @param restconfContentQueryParam restconf content i.e. datastore to use * @return {@code Object} response from network function */ - Object getResourceDataPassThroughRunningForCmHandle(@NotNull String cmHandle, + String getResourceData(@NotNull String cmHandle, @NotNull String resourceIdentifier, String acceptParamInHeader, String optionsParamInQuery, - Map<String, String> cmHandlePropertyMap); + String restconfContentQueryParam); /** - * Write resource data to sdnc using passthrough running. + * Write resource data to sdnc (will default to 'content=config', does not need to be specified). * * @param cmHandle cmHandle * @param resourceIdentifier resource identifier @@ -102,6 +90,7 @@ public interface DmiService { * @param data request data * @return response from sdnc */ - String writeResourceDataPassthroughForCmHandle(String cmHandle, String resourceIdentifier, String dataType, - String data); + String writeData(DataAccessRequest.OperationEnum operation, String cmHandle, + String resourceIdentifier, String dataType, + String data); } diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java index b66e5c1b..99127e66 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/DmiServiceImpl.java @@ -1,6 +1,7 @@ /* * ============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. @@ -25,18 +26,17 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.JsonObject; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import javax.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; -import org.apache.groovy.parser.antlr4.util.StringUtils; 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.ModuleResourceNotFoundException; import org.onap.cps.ncmp.dmi.exception.ModulesNotFoundException; import org.onap.cps.ncmp.dmi.exception.ResourceDataNotFound; +import org.onap.cps.ncmp.dmi.model.DataAccessRequest; import org.onap.cps.ncmp.dmi.model.ModuleSet; import org.onap.cps.ncmp.dmi.model.ModuleSetSchemas; import org.onap.cps.ncmp.dmi.model.YangResource; @@ -45,8 +45,7 @@ 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.ModuleSchemaProperties; -import org.onap.cps.ncmp.dmi.service.model.ModuleSchemas; +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; @@ -60,8 +59,6 @@ public class DmiServiceImpl implements DmiService { private NcmpRestClient ncmpRestClient; private ObjectMapper objectMapper; private DmiPluginProperties dmiPluginProperties; - private static final String RESTCONF_CONTENT_PASSTHROUGH_OPERATIONAL_QUERY_PARAM = "content=all"; - private static final String REST_CONF_CONTENT_PASSTHROUGH_RUNNING_QUERY_PARAM = "content=config"; private static final String RESPONSE_CODE = "response code : "; private static final String MESSAGE = " message : "; private static final String IETF_NETCONF_MONITORING_OUTPUT = "ietf-netconf-monitoring:output"; @@ -75,8 +72,8 @@ public class DmiServiceImpl implements DmiService { * @param objectMapper objectMapper */ public DmiServiceImpl(final DmiPluginProperties dmiPluginProperties, - final NcmpRestClient ncmpRestClient, - final SdncOperations sdncOperations, final ObjectMapper objectMapper) { + final NcmpRestClient ncmpRestClient, + final SdncOperations sdncOperations, final ObjectMapper objectMapper) { this.dmiPluginProperties = dmiPluginProperties; this.ncmpRestClient = ncmpRestClient; this.objectMapper = objectMapper; @@ -85,16 +82,14 @@ public class DmiServiceImpl implements DmiService { @Override public ModuleSet getModulesForCmHandle(final String cmHandle) throws DmiException { - final ResponseEntity<String> responseEntity = sdncOperations.getModulesFromNode(cmHandle); - if (responseEntity.getStatusCode() == HttpStatus.OK) { - final String responseBody = responseEntity.getBody(); - if (StringUtils.isEmpty(responseBody)) { - throw new ModulesNotFoundException(cmHandle, "SDNC returned no modules for given cm-handle."); - } - return createModuleSchema(responseBody); + final Collection<ModuleSchema> moduleSchemas = sdncOperations.getModuleSchemasFromNode(cmHandle); + if (moduleSchemas.isEmpty()) { + throw new ModulesNotFoundException(cmHandle, "SDNC returned no modules for given cm-handle."); } else { - throw new DmiException("SDNC is not able to process request.", - RESPONSE_CODE + responseEntity.getStatusCode() + MESSAGE + responseEntity.getBody()); + final ModuleSet moduleSet = new ModuleSet(); + moduleSchemas.forEach(moduleSchema -> + moduleSet.addSchemasItem(toModuleSetSchemas(moduleSchema))); + return moduleSet; } } @@ -109,11 +104,11 @@ public class DmiServiceImpl implements DmiService { } 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."); + "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 DmiException(cmHandle, - RESPONSE_CODE + responseEntity.getStatusCode() + MESSAGE + responseEntity.getBody()); + RESPONSE_CODE + responseEntity.getStatusCode() + MESSAGE + responseEntity.getBody()); } } return yangResources; @@ -136,7 +131,7 @@ public class DmiServiceImpl implements DmiService { } 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 "); + "Parsing error occurred while converting given cm-handles object list to JSON "); } final ResponseEntity<String> responseEntity = ncmpRestClient.registerCmHandlesWithNcmp(cmHandlesJson); if ((responseEntity.getStatusCode() != HttpStatus.CREATED)) { @@ -144,76 +139,40 @@ public class DmiServiceImpl implements DmiService { } } - private ModuleSet createModuleSchema(final String responseBody) { - final var moduleSchemas = convertModulesToNodeSchema(responseBody); - final List<ModuleSetSchemas> moduleSetSchemas = new ArrayList<>(); - for (final ModuleSchemaProperties schemaProperties : moduleSchemas.getSchemas().getSchema()) { - moduleSetSchemas.add(convertModulesToModuleSchemas(schemaProperties)); - } - final var moduleSet = new ModuleSet(); - moduleSet.setSchemas(moduleSetSchemas); - return moduleSet; - } - - private ModuleSetSchemas convertModulesToModuleSchemas(final ModuleSchemaProperties moduleSchemaProperties) { + private ModuleSetSchemas toModuleSetSchemas(final ModuleSchema moduleSchema) { final var moduleSetSchemas = new ModuleSetSchemas(); - moduleSetSchemas.setModuleName(moduleSchemaProperties.getIdentifier()); - moduleSetSchemas.setNamespace(moduleSchemaProperties.getNamespace()); - moduleSetSchemas.setRevision(moduleSchemaProperties.getVersion()); + moduleSetSchemas.setModuleName(moduleSchema.getIdentifier()); + moduleSetSchemas.setNamespace(moduleSchema.getNamespace()); + moduleSetSchemas.setRevision(moduleSchema.getVersion()); return moduleSetSchemas; } - private ModuleSchemas convertModulesToNodeSchema(final String modulesListAsJson) { - try { - return objectMapper.readValue(modulesListAsJson, ModuleSchemas.class); - } catch (final JsonProcessingException e) { - log.error("JSON exception occurred when converting the following modules to node schema " - + "{}", modulesListAsJson); - throw new DmiException("Unable to process JSON.", - "JSON exception occurred when mapping modules.", e); - } - } - - @Override - public Object getResourceDataOperationalForCmHandle(final @NotNull String cmHandle, - final @NotNull String resourceIdentifier, - final String acceptParamInHeader, - final String optionsParamInQuery, - final Map<String, String> cmHandlePropertyMap) { - // not using cmHandlePropertyMap of onap dmi impl , other dmi impl might use this. - final ResponseEntity<String> responseEntity = sdncOperations.getResouceDataForOperationalAndRunning(cmHandle, - resourceIdentifier, - optionsParamInQuery, - acceptParamInHeader, - RESTCONF_CONTENT_PASSTHROUGH_OPERATIONAL_QUERY_PARAM); - return prepareAndSendResponse(responseEntity, cmHandle); - } - @Override - public Object getResourceDataPassThroughRunningForCmHandle(final @NotNull String cmHandle, - final @NotNull String resourceIdentifier, - final String acceptParamInHeader, - final String optionsParamInQuery, - final Map<String, String> cmHandlePropertyMap) { - // not using cmHandlePropertyMap of onap dmi impl , other dmi impl might use this. + public String getResourceData(final String cmHandle, + final String resourceIdentifier, + final String acceptParamInHeader, + final String optionsParamInQuery, + final String restconfContentQueryParam) { final ResponseEntity<String> responseEntity = sdncOperations.getResouceDataForOperationalAndRunning(cmHandle, - resourceIdentifier, - optionsParamInQuery, - acceptParamInHeader, - REST_CONF_CONTENT_PASSTHROUGH_RUNNING_QUERY_PARAM); + resourceIdentifier, + optionsParamInQuery, + acceptParamInHeader, + restconfContentQueryParam); return prepareAndSendResponse(responseEntity, cmHandle); } @Override - public String writeResourceDataPassthroughForCmHandle(final String cmHandle, final String resourceIdentifier, - final String dataType, final String data) { + public String writeData(final DataAccessRequest.OperationEnum operation, + final String cmHandle, + final String resourceIdentifier, + final String dataType, final String data) { final ResponseEntity<String> responseEntity = - sdncOperations.writeResourceDataPassthroughRunning(cmHandle, resourceIdentifier, dataType, data); + sdncOperations.writeData(operation, cmHandle, resourceIdentifier, dataType, data); if (responseEntity.getStatusCode().is2xxSuccessful()) { return responseEntity.getBody(); } else { throw new DmiException(cmHandle, - RESPONSE_CODE + responseEntity.getStatusCode() + MESSAGE + responseEntity.getBody()); + RESPONSE_CODE + responseEntity.getStatusCode() + MESSAGE + responseEntity.getBody()); } } @@ -222,7 +181,7 @@ public class DmiServiceImpl implements DmiService { return responseEntity.getBody(); } else { throw new ResourceDataNotFound(cmHandle, - RESPONSE_CODE + responseEntity.getStatusCode() + MESSAGE + responseEntity.getBody()); + RESPONSE_CODE + responseEntity.getStatusCode() + MESSAGE + responseEntity.getBody()); } } @@ -236,15 +195,15 @@ public class DmiServiceImpl implements DmiService { moduleRequest = writer.writeValueAsString(ietfNetconfModuleReferences); } catch (final JsonProcessingException e) { log.error("JSON exception occurred when creating the module request for the given module reference {}", - moduleReference.getName()); + moduleReference.getName()); throw new DmiException("Unable to process JSON.", - "JSON exception occurred when creating the module request.", e); + "JSON exception occurred when creating the module request.", e); } return moduleRequest; } private YangResource toYangResource(final ModuleReference moduleReference, - final ResponseEntity<String> response) { + final ResponseEntity<String> response) { final YangResource yangResource = new YangResource(); yangResource.setModuleName(moduleReference.getName()); yangResource.setRevision(moduleReference.getRevision()); @@ -255,13 +214,13 @@ public class DmiServiceImpl implements DmiService { private String extractYangSourceFromBody(final ResponseEntity<String> responseEntity) { final var responseBodyAsJsonObject = new Gson().fromJson(responseEntity.getBody(), JsonObject.class); if (responseBodyAsJsonObject.getAsJsonObject(IETF_NETCONF_MONITORING_OUTPUT) == null - || responseBodyAsJsonObject.getAsJsonObject(IETF_NETCONF_MONITORING_OUTPUT) - .getAsJsonPrimitive("data") == null) { + || responseBodyAsJsonObject.getAsJsonObject(IETF_NETCONF_MONITORING_OUTPUT) + .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."); + "Error occurred when trying to parse the response body from sdnc."); } return responseBodyAsJsonObject.getAsJsonObject(IETF_NETCONF_MONITORING_OUTPUT).getAsJsonPrimitive("data") - .toString(); + .toString(); } } diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java b/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java index b8e33dfc..cf7b4599 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClient.java @@ -57,28 +57,30 @@ public class SdncRestconfClient { * @return the response entity */ public ResponseEntity<String> getOperation(final String getResourceUrl, final HttpHeaders httpHeaders) { - final String sdncBaseUrl = sdncProperties.getBaseUrl(); - final String sdncRestconfUrl = sdncBaseUrl.concat(getResourceUrl); - httpHeaders.setBasicAuth(sdncProperties.getAuthUsername(), sdncProperties.getAuthPassword()); - final var httpEntity = new HttpEntity<>(httpHeaders); - return restTemplate.exchange(sdncRestconfUrl, - HttpMethod.GET, httpEntity, String.class); + return httpOperationWithJsonData(HttpMethod.GET, getResourceUrl, null, httpHeaders); } /** - * restconf post operation on sdnc. + * restconf http operations on sdnc. * - * @param postResourceUrl sdnc post resource url - * @param jsonData json data - * @param httpHeaders HTTP headers - * @return the response entity + * @param httpMethod HTTP Method + * @param resourceUrl sdnc resource url + * @param jsonData json data + * @param httpHeaders HTTP Headers + * @return response entity */ - public ResponseEntity<String> postOperationWithJsonData(final String postResourceUrl, - final String jsonData, final HttpHeaders httpHeaders) { - final var sdncBaseUrl = sdncProperties.getBaseUrl(); - final var sdncRestconfUrl = sdncBaseUrl.concat(postResourceUrl); + 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 var httpEntity = new HttpEntity<>(jsonData, httpHeaders); - return restTemplate.exchange(sdncRestconfUrl, HttpMethod.POST, httpEntity, String.class); + 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); } -}
\ No newline at end of file +} diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchemaProperties.java b/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchema.java index 6de7d131..c77e0e41 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchemaProperties.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchema.java @@ -1,6 +1,7 @@ /* * ============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. @@ -21,15 +22,10 @@ package org.onap.cps.ncmp.dmi.service.model; import java.util.List; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; -/** - * ModuleSchemaProperties. - */ -@Getter -@Setter -public class ModuleSchemaProperties { +@Data +public class ModuleSchema { private String identifier; private String version; diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchemas.java b/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchemas.java deleted file mode 100644 index a7b2430d..00000000 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/model/ModuleSchemas.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * ============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.JsonProperty; -import lombok.Getter; -import lombok.Setter; - -/** - * ModuleSchemas. - */ -@Getter -@Setter -public class ModuleSchemas { - - @JsonProperty("ietf-netconf-monitoring:schemas") - private ModuleSchemaList schemas; -} diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java b/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java index 98371bf9..ced9b061 100644 --- a/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java +++ b/src/main/java/org/onap/cps/ncmp/dmi/service/operation/SdncOperations.java @@ -1,6 +1,7 @@ /* * ============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. @@ -20,14 +21,26 @@ package org.onap.cps.ncmp.dmi.service.operation; +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.LinkedList; import java.util.List; import org.apache.groovy.parser.antlr4.util.StringUtils; -import org.jetbrains.annotations.NotNull; 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; @@ -42,12 +55,18 @@ public class SdncOperations { private static final String MOUNT_URL_TEMPLATE = "/node={nodeId}/yang-ext:mount"; 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 SdncProperties sdncProperties; private 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. * @@ -66,11 +85,20 @@ public class SdncOperations { * This method fetches list of modules usind sdnc client. * * @param nodeId node id for node - * @return returns {@code ResponseEntity} which contains list of modules + * @return a collection of module schemas */ - public ResponseEntity<String> getModulesFromNode(final String nodeId) { + public Collection<ModuleSchema> getModuleSchemasFromNode(final String nodeId) { final String urlWithNodeId = prepareGetSchemaUrl(nodeId); - return sdncRestconfClient.getOperation(urlWithNodeId); + final ResponseEntity<String> modulesResponseEntity = sdncRestconfClient.getOperation(urlWithNodeId); + if (modulesResponseEntity.getStatusCode() == HttpStatus.OK) { + final String modulesResponseBody = modulesResponseEntity.getBody(); + return (StringUtils.isEmpty(modulesResponseBody)) ? Collections.emptyList() + : convertToModuleSchemas(modulesResponseBody); + } else { + throw new SdncException( + String.format("SDNC failed to get Modules Schema for node %s", nodeId), + modulesResponseEntity.getStatusCode(), modulesResponseEntity.getBody()); + } } /** @@ -84,17 +112,17 @@ public class SdncOperations { final var getYangResourceUrl = prepareGetOperationSchemaUrl(nodeId); final var httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_JSON); - return sdncRestconfClient - .postOperationWithJsonData(getYangResourceUrl, moduleProperties, httpHeaders); + 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 acceptParamInHeader accept parameter + * @param nodeId network resource identifier + * @param resourceId resource identifier + * @param optionsParamInQuery fields query + * @param acceptParamInHeader accept parameter * @param restconfContentQueryParam restconf content query param * @return {@code ResponseEntity} response entity */ @@ -114,7 +142,7 @@ public class SdncOperations { } /** - * Write resource data using passthrough running. + * Write resource data. * * @param nodeId network resource identifier * @param resourceId resource identifier @@ -122,15 +150,18 @@ public class SdncOperations { * @param requestData request data * @return {@code ResponseEntity} response entity */ - public ResponseEntity<String> writeResourceDataPassthroughRunning(final String nodeId, - final String resourceId, final String contentType, final String requestData) { - final var getResourceDataUrl = preparePassthroughRunningUrl(nodeId, resourceId); + public ResponseEntity<String> writeData(final DataAccessRequest.OperationEnum operation, + final String nodeId, + final String resourceId, + final String contentType, + final String requestData) { + final var getResourceDataUrl = prepareWriteUrl(nodeId, resourceId); final var httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.parseMediaType(contentType)); - return sdncRestconfClient.postOperationWithJsonData(getResourceDataUrl, requestData, httpHeaders); + final HttpMethod httpMethod = getHttpMethod(operation); + return sdncRestconfClient.httpOperationWithJsonData(httpMethod, getResourceDataUrl, requestData, httpHeaders); } - @NotNull private List<String> buildQueryParamList(final String optionsParamInQuery, final String restconfContentQueryParam) { final List<String> queryParamAsList = getOptionsParamAsList(optionsParamInQuery); queryParamAsList.add(restconfContentQueryParam); @@ -146,17 +177,15 @@ public class SdncOperations { return queryParamAsList; } - @NotNull private String stripParenthesisFromOptionsQuery(final String optionsParamInQuery) { return optionsParamInQuery.substring(1, optionsParamInQuery.length() - 1); } - @NotNull private String prepareGetSchemaUrl(final String nodeId) { return addResource(addTopologyDataUrlwithNode(nodeId), GET_SCHEMA_URL); } - private String preparePassthroughRunningUrl(final String nodeId, final String resourceId) { + private String prepareWriteUrl(final String nodeId, final String resourceId) { return addResource(addTopologyDataUrlwithNode(nodeId), "/" + resourceId); } @@ -166,14 +195,12 @@ public class SdncOperations { return topologyMountUrlWithNodeId.concat(GET_SCHEMA_SOURCES_URL); } - @NotNull private String prepareResourceDataUrl(final String nodeId, final String resourceId, final List<String> queryList) { return addQuery(addResource(addTopologyDataUrlwithNode(nodeId), resourceId), queryList); } - @NotNull private String addResource(final String url, final String resourceId) { if (resourceId.startsWith("/")) { return url.concat(resourceId); @@ -182,7 +209,6 @@ public class SdncOperations { } } - @NotNull private String addQuery(final String url, final List<String> queryList) { if (queryList.isEmpty()) { return url; @@ -197,9 +223,41 @@ public class SdncOperations { return urlBuilder.toString(); } - @NotNull private String addTopologyDataUrlwithNode(final String nodeId) { final String topologyMountUrl = topologyUrlData + MOUNT_URL_TEMPLATE; return topologyMountUrl.replace("{nodeId}", nodeId); } -}
\ No newline at end of file + + 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 HttpMethod getHttpMethod(final DataAccessRequest.OperationEnum operation) { + HttpMethod httpMethod = null; + switch (operation) { + case READ: + httpMethod = HttpMethod.GET; + break; + case CREATE: + httpMethod = HttpMethod.POST; + break; + case UPDATE: + httpMethod = HttpMethod.PUT; + break; + case DELETE: + httpMethod = HttpMethod.DELETE; + break; + default: + //unreachable code but checkstyle made me do this! + } + return httpMethod; + } + +} diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/model/ModuleSchemaPropertiesSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/model/ModuleSchemaPropertiesSpec.groovy deleted file mode 100644 index 51dddc7d..00000000 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/model/ModuleSchemaPropertiesSpec.groovy +++ /dev/null @@ -1,41 +0,0 @@ -/* - * ============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.model - -import org.onap.cps.ncmp.dmi.service.model.ModuleSchemaProperties -import spock.lang.Specification - -class ModuleSchemaPropertiesSpec extends Specification { - def objectUnderTest = new ModuleSchemaProperties(identifier:'some id', - version:'some version', - format:'some format', - namespace:'some namespace', - location: ['some','locations']) - - def 'Reading all properties.'() { - expect: 'all properties return the expected values' - objectUnderTest.identifier == 'some id' - objectUnderTest.version == 'some version' - objectUnderTest.format == 'some format' - objectUnderTest.namespace == 'some namespace' - objectUnderTest.location == ['some','locations'] - } -} diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy index ac78928d..221603c2 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/rest/controller/DmiRestControllerSpec.groovy @@ -1,6 +1,7 @@ /* * ============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. @@ -26,7 +27,6 @@ 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.service.model.ModuleReference -import org.onap.cps.ncmp.dmi.service.model.ModuleSchemaList import org.onap.cps.ncmp.dmi.model.ModuleSet import org.onap.cps.ncmp.dmi.model.ModuleSetSchemas import org.onap.cps.ncmp.dmi.model.YangResource @@ -35,33 +35,39 @@ import org.onap.cps.ncmp.dmi.service.DmiService 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.AutoConfigureMockMvc 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.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.DELETE +import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.READ +import static org.springframework.http.HttpStatus.BAD_REQUEST +import static org.springframework.http.HttpStatus.NO_CONTENT import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.CREATE +import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.UPDATE +import static org.springframework.http.HttpStatus.CREATED +import static org.springframework.http.HttpStatus.OK -@WebMvcTest -@AutoConfigureMockMvc(addFilters = false) +@WebMvcTest(DmiRestController) +@WithMockUser +@Import(ObjectMapper) class DmiRestControllerSpec extends Specification { @SpringBean DmiService mockDmiService = Mock() - @SpringBean - ObjectMapper mockObjectMapper = Spy() - @Autowired private MockMvc mvc @Value('${rest.api.dmi-base-path}/v1') def basePathV1 - def 'Get all modules for given cm handle.'() { + def 'Get all modules.'() { given: 'REST endpoint for getting all modules' def getModuleUrl = "$basePathV1/ch/node1/modules" and: 'get modules for cm-handle returns a json' @@ -78,26 +84,12 @@ class DmiRestControllerSpec extends Specification { .contentType(MediaType.APPLICATION_JSON).content(json)) .andReturn().response then: 'status is OK' - response.status == HttpStatus.OK.value() + 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 for given cm handle with invalid json.'() { - given: 'REST endpoint for getting all modules' - def getModuleUrl = "$basePathV1/ch/node1/modules" - and: 'get modules for cmHandle throws an exception' - mockObjectMapper.readValue(_ as String, _ as ModuleSchemaList) >> { throw Mock(DmiException.class) } - mockDmiService.getModulesForCmHandle('node1') >> 'some-value' - when: 'post is being called' - def response = mvc.perform(post(getModuleUrl) - .contentType(MediaType.APPLICATION_JSON)) - .andReturn().response - then: 'the status is as expected' - response.status == HttpStatus.INTERNAL_SERVER_ERROR.value() - } - - def 'Get all modules for given cm handle with exception handling of #scenario.'() { + def 'Get all modules with exception handling of #scenario.'() { given: 'REST endpoint for getting all modules' def getModuleUrl = "$basePathV1/ch/node1/modules" and: 'given request body and get modules for cm-handle throws #exceptionClass' @@ -116,20 +108,20 @@ class DmiRestControllerSpec extends Specification { 'any other runtime exception' | RuntimeException.class || HttpStatus.INTERNAL_SERVER_ERROR.value() } - def 'Register given list of cm handles.'() { + def 'Register given list.'() { given: 'register cm handle url and cm handles json' def registerCmhandlesPost = "${basePathV1}/inventory/cmHandles" def cmHandleJson = '{"cmHandles":["node1", "node2"]}' - when: 'post register cm handles api is invoked' + when: 'register cm handles api is invoked with POST' def response = mvc.perform( post(registerCmhandlesPost) .contentType(MediaType.APPLICATION_JSON) .content(cmHandleJson) ).andReturn().response - then: 'register cm handles in dmi service is called once' + 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 == HttpStatus.CREATED.value() + response.status == CREATED.value() } def 'register cm handles called with empty content.'() { @@ -142,7 +134,7 @@ class DmiRestControllerSpec extends Specification { .content(emptyJson) ).andReturn().response then: 'response status is "bad request"' - response.status == HttpStatus.BAD_REQUEST.value() + response.status == BAD_REQUEST.value() and: 'dmi service is not called' 0 * mockDmiService.registerCmHandles(_) } @@ -164,7 +156,7 @@ class DmiRestControllerSpec extends Specification { .contentType(MediaType.APPLICATION_JSON) .content(jsonData)).andReturn().response then: 'a OK status is returned' - response.status == HttpStatus.OK.value() + response.status == OK.value() and: 'the expected response is returned' response.getContentAsString() == '[{"yangSource":"\\"some-data\\"","moduleName":"NAME","revision":"REVISION"}]' } @@ -183,68 +175,105 @@ class DmiRestControllerSpec extends Specification { response.status == HttpStatus.NOT_FOUND.value() } - def 'Get resource data for pass-through operational from cm handle.'() { + def 'Get resource data for pass-through operational.'() { 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)" def json = '{"cmHandleProperties" : { "prop1" : "value1", "prop2" : "value2"}}' - when: 'get resource data PUT api is invoked' + when: 'get resource data POST api is invoked' def response = mvc.perform( - put(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON) + post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON).content(json) ).andReturn().response then: 'response status is ok' - response.status == HttpStatus.OK.value() - and: 'dmi service called with get resource data for cm handle' - 1 * mockDmiService.getResourceDataOperationalForCmHandle('some-cmHandle', + response.status == OK.value() + and: 'dmi service called with get resource data' + 1 * mockDmiService.getResourceData('some-cmHandle', 'parent/child', 'application/json', '(fields=myfields,depth=5)', - ['prop1': 'value1', 'prop2': 'value2']) + 'content=all') + } + + def 'Get resource data for pass-through operational with bad request.'() { + 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)" + def jsonData = TestUtils.getResourceFileContent('createDataWithNormalChar.json') + when: 'get resource data POST api is invoked' + def response = mvc.perform( + post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON) + .accept(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 'Write data using passthrough running for a cm handle using #scenario.'() { - given: 'write data for cmHandle url and jsonData' - def writeDataforCmHandlePassthroughRunning = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" + + def 'write data with #scenario operation using passthrough running.'() { + given: 'write data for passthrough running url and jsonData' + def writeDataForPassthroughRunning = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" + "?resourceIdentifier=some-resourceIdentifier" def jsonData = TestUtils.getResourceFileContent(requestBodyFile) and: 'dmi service is called' - mockDmiService.writeResourceDataPassthroughForCmHandle('some-cmHandle', + mockDmiService.writeData(operationEnum, 'some-cmHandle', 'some-resourceIdentifier', 'application/json', - expectedRequestData) >> '{some-json}' - when: 'write cmHandle passthrough running post api is invoked with json data' + 'normal request body' ) >> '{some-json}' + when: 'write data for passthrough running post api is invoked with json data' def response = mvc.perform( - post(writeDataforCmHandlePassthroughRunning).contentType(MediaType.APPLICATION_JSON) + post(writeDataForPassthroughRunning).contentType(MediaType.APPLICATION_JSON) .content(jsonData) ).andReturn().response - then: 'response status is 201 CREATED' - response.status == HttpStatus.CREATED.value() + then: 'response status is #expectedResponseStatus' + response.status == expectedResponseStatus and: 'the data in the request body is as expected' - response.getContentAsString() == '{some-json}' + response.getContentAsString() == expectedJsonResponse where: 'given request body and data' - scenario | requestBodyFile || expectedRequestData - 'data with normal chars' | 'dataWithNormalChar.json' || 'normal request body' - 'data with special chars' | 'dataWithSpecialChar.json'|| 'data with quote \" and new line \n' + scenario | requestBodyFile | operationEnum || expectedResponseStatus | expectedJsonResponse + 'Create' | 'createDataWithNormalChar.json' | CREATE || CREATED.value() | '{some-json}' + 'Update' | 'updateData.json' | UPDATE || OK.value() | '{some-json}' + 'Delete' | 'deleteData.json' | DELETE || NO_CONTENT.value() | '{some-json}' + 'Read' | 'readData.json' | READ || OK.value() | '' } - def 'Get resource data for pass-through running from cm handle with #scenario value in resource identifier param.'() { + def 'Create data using passthrough for special characters.'(){ + given: 'create data for cmHandle url and JsonData' + def writeDataForCmHandlePassthroughRunning = "${basePathV1}/ch/some-cmHandle/data/ds/ncmp-datastore:passthrough-running" + + "?resourceIdentifier=some-resourceIdentifier" + def jsonData = TestUtils.getResourceFileContent('createDataWithSpecialChar.json') + and: 'dmi service is called' + mockDmiService.writeData(CREATE, 'some-cmHandle', 'some-resourceIdentifier', 'application/json', + 'data with quote \" and new line \n') >> '{some-json}' + when: 'create cmHandle passthrough running post api is invoked with json data with special chars' + 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 data in the request body is as expected' + response.getContentAsString() == '{some-json}' + } + + + 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)" def json = '{"cmHandleProperties" : { "prop1" : "value1", "prop2" : "value2"}}' - when: 'get resource data PUT api is invoked' + when: 'get resource data POST api is invoked' def response = mvc.perform( - put(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON) + post(getResourceDataForCmHandleUrl).contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON).content(json) ).andReturn().response then: 'response status is ok' - response.status == HttpStatus.OK.value() - and: 'dmi service called with get resource data for cm handle' - 1 * mockDmiService.getResourceDataPassThroughRunningForCmHandle('some-cmHandle', + response.status == OK.value() + and: 'dmi service called with get resource data for a cm handle' + 1 * mockDmiService.getResourceData('some-cmHandle', resourceIdentifier, 'application/json', '(fields=myfields,depth=5)', - ['prop1':'value1', 'prop2':'value2']) + 'content=config') where: 'tokens are used in the resource identifier parameter' scenario | resourceIdentifier '/' | 'id/with/slashes' diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy index 9325d59b..a463a344 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/service/DmiServiceImplSpec.groovy @@ -1,6 +1,7 @@ /* * ============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. @@ -23,7 +24,6 @@ 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.TestUtils import org.onap.cps.ncmp.dmi.config.DmiPluginConfig import org.onap.cps.ncmp.dmi.exception.CmHandleRegistrationException import org.onap.cps.ncmp.dmi.exception.DmiException @@ -34,11 +34,15 @@ 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 { @@ -49,50 +53,35 @@ class DmiServiceImplSpec extends Specification { def mockSdncOperations = Mock(SdncOperations) def objectUnderTest = new DmiServiceImpl(mockDmiPluginProperties, mockNcmpRestClient, mockSdncOperations, spyObjectMapper) - def 'Call get modules for cm-handle on dmi Service.'() { - given: 'cm handle id' - def cmHandle = 'node1' - and: 'request operation returns OK' - def body = TestUtils.getResourceFileContent('ModuleSchema.json') - mockSdncOperations.getModulesFromNode(cmHandle) >> new ResponseEntity<String>(body, HttpStatus.OK) - when: 'get modules for cm-handle is called' - def result = objectUnderTest.getModulesForCmHandle(cmHandle) - then: 'result is equal to the response from the SDNC service' - result.toString().contains('moduleName: example-identifier') - result.toString().contains('revision: example-version') - } - - def 'Call get modules for cm-handle with invalid json.'() { - given: 'cm handle id' + def ' Get modules for a cm-handle.'() { + given: 'a cm handle' def cmHandle = 'node1' - and: 'request operation returns invalid json' - def body = TestUtils.getResourceFileContent('ModuleSchema.json') - mockSdncOperations.getModulesFromNode(cmHandle) >> new ResponseEntity<String>('invalid json', HttpStatus.OK) + and: 'sdnc operations 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: 'get modules for cm-handle is called' def result = objectUnderTest.getModulesForCmHandle(cmHandle) - then: 'a dmi exception is thrown' - thrown(DmiException) - } - - def 'Call get modules for cm-handle and SDNC returns "bad request" status.'() { - given: 'cm handle id' - def cmHandle = 'node1' - and: 'get modules from node returns "bad request" status' - mockSdncOperations.getModulesFromNode(cmHandle) >> new ResponseEntity<String>('body', HttpStatus.BAD_REQUEST) - when: 'get modules for cm-handle is called' - objectUnderTest.getModulesForCmHandle(cmHandle) - then: 'dmi exception is thrown' - thrown(DmiException) + 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 'Call get modules for cm-handle and SDNC returns OK with empty body.'() { + def 'no modules found for the cmhandle.'() { given: 'cm handle id' def cmHandle = 'node1' - and: 'get modules for cm-handle returns OK with empty body' - mockSdncOperations.getModulesFromNode(cmHandle) >> new ResponseEntity<String>('', HttpStatus.OK) + and: 'sdnc operations returns no modules' + mockSdncOperations.getModuleSchemasFromNode(cmHandle) >> Collections.emptyList(); when: 'get modules for cm-handle is called' objectUnderTest.getModulesForCmHandle(cmHandle) - then: 'ModulesNotFoundException is thrown' + then: 'module not found exception is thrown' thrown(ModulesNotFoundException) } @@ -140,14 +129,14 @@ class DmiServiceImplSpec extends Specification { def 'Get multiple module resources.'() { given: 'a cmHandle and module reference list' def cmHandle = 'some-cmHandle' - def moduleReference1 = new ModuleReference(name: 'name-1',revision: 'revision-1') - def moduleReference2 = new ModuleReference(name: 'name-2',revision: 'revision-2') + 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: 'get module resources is invoked with the given cm handle and a module list' def result = objectUnderTest.getModuleResources(cmHandle, moduleList) then: 'get modules resources is called twice' 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)] + new ResponseEntity<String>('{"ietf-netconf-monitoring:output": {"data": "some-data2"}}', HttpStatus.OK)] and: 'the result is a yang resources object with the expected names, revisions and yang-sources' def yangResources = new YangResources() def yangResource1 = new YangResource(yangSource: '"some-data1"', moduleName: 'name-1', revision: 'revision-1') @@ -160,7 +149,7 @@ class DmiServiceImplSpec extends Specification { 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 moduleReference = new ModuleReference(name: 'NAME', revision: 'REVISION') def moduleList = [moduleReference] when: 'get module resources is invoked with the given cm handle and a module list' objectUnderTest.getModuleResources(cmHandle, moduleList) @@ -205,8 +194,8 @@ class DmiServiceImplSpec extends Specification { thrownException.cause == jsonProcessingException } - def 'Get resource data for pass through operational from cm handle.'() { - given: 'cm-handle, pass through parameter, resourceId, accept header, fields, depth' + def 'Get resource data for passthrough operational.'() { + given: 'cm-handle, passthrough parameter, resourceId, accept header, fields, depth' def cmHandle = 'testCmHandle' def resourceId = 'testResourceId' def acceptHeaderParam = 'testAcceptParam' @@ -215,31 +204,32 @@ class DmiServiceImplSpec extends Specification { and: 'sdnc operation returns OK response' mockSdncOperations.getResouceDataForOperationalAndRunning(cmHandle, resourceId, optionsParam, acceptHeaderParam, contentQuery) >> new ResponseEntity<>('response json', HttpStatus.OK) when: 'get resource data from cm handles service method invoked' - def response = objectUnderTest.getResourceDataOperationalForCmHandle(cmHandle, - resourceId, acceptHeaderParam, - optionsParam, null) + def response = objectUnderTest.getResourceData(cmHandle, + resourceId, acceptHeaderParam, + optionsParam, contentQuery) then: 'response have expected json' response == 'response json' } - def 'Get resource data from cm handle with exception.'() { - given: 'cm-handle, pass through parameter, resourceId, accept header, fields, depth' + def 'Get resource data with not found exception.'() { + given: 'cm-handle, passthrough parameter, resourceId, accept header, fields, depth, query param' def cmHandle = 'testCmHandle' def resourceId = 'testResourceId' def acceptHeaderParam = 'testAcceptParam' def optionsParam = '(fields=x/y/z,depth=10,test=abc)' + def restConfQueryParam = 'content=config' and: 'sdnc operation returns "NOT_FOUND" response' mockSdncOperations.getResouceDataForOperationalAndRunning(cmHandle, resourceId, optionsParam, acceptHeaderParam, _ as String) >> new ResponseEntity<>(HttpStatus.NOT_FOUND) when: 'get resource data from cm handles service method invoked' - objectUnderTest.getResourceDataOperationalForCmHandle(cmHandle, - resourceId, acceptHeaderParam, - optionsParam, null) + objectUnderTest.getResourceData(cmHandle, + resourceId, acceptHeaderParam, + optionsParam, restConfQueryParam) then: 'resource data not found' thrown(ResourceDataNotFound.class) } - def 'Get resource data for pass through running from cm handle.'() { - given: 'cm-handle, pass through parameter, resourceId, accept header, fields, depth' + def 'Get resource data for passthrough running.'() { + given: 'cm-handle, passthrough parameter, resourceId, accept header, fields, depth' def cmHandle = 'testCmHandle' def resourceId = 'testResourceId' def acceptHeaderParam = 'testAcceptParam' @@ -247,50 +237,46 @@ class DmiServiceImplSpec extends Specification { def contentQuery = 'content=config' and: 'sdnc operation returns OK response' mockSdncOperations.getResouceDataForOperationalAndRunning(cmHandle, resourceId, optionsParam, - acceptHeaderParam, contentQuery) >> new ResponseEntity<>('response json', HttpStatus.OK) + acceptHeaderParam, contentQuery) >> new ResponseEntity<>('response json', HttpStatus.OK) when: 'get resource data from cm handles service method invoked' - def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle(cmHandle, - resourceId, acceptHeaderParam, - optionsParam, null) + def response = objectUnderTest.getResourceData(cmHandle, + resourceId, acceptHeaderParam, + optionsParam, contentQuery) then: 'response have expected json' response == 'response json' } - def 'Write resource data for passthrough running for the given cm handle with a #scenario from sdnc.'() { + def 'Write resource data for passthrough running with a #scenario from sdnc.'() { given: 'sdnc returns a response with #scenario' - mockSdncOperations.writeResourceDataPassthroughRunning(_, _, _, _) >> new ResponseEntity<String>('response json', httpResponse) + mockSdncOperations.writeData(operationEnum, _, _, _, _) >> new ResponseEntity<String>('response json', httpResponse) when: 'write resource data for cm handle method invoked' - def response = objectUnderTest.writeResourceDataPassthroughForCmHandle('some-cmHandle', - 'some-resourceIdentifier', 'some-dataType', '{some-data}') + def response = objectUnderTest.writeData(operationEnum,'some-cmHandle', + 'some-resourceIdentifier', 'some-dataType', '{some-data}') then: 'the response contains the expected json data from sdnc' response == 'response json' where: 'the following values are used' - scenario | httpResponse - '200 OK response' | HttpStatus.OK - '201 CREATED response' | HttpStatus.CREATED + 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 using for passthrough running for the given cm handle with #scenario.'() { + def 'Write resource data with special characters.'() { given: 'sdnc returns a created response' - mockSdncOperations.writeResourceDataPassthroughRunning('some-cmHandle', - 'some-resourceIdentifier', 'some-dataType', requestBody) >> new ResponseEntity<String>('response json', HttpStatus.CREATED) + mockSdncOperations.writeData(CREATE, 'some-cmHandle', + 'some-resourceIdentifier', 'some-dataType', 'data with quote " and \n new line') >> new ResponseEntity<String>('response json', HttpStatus.CREATED) when: 'write resource data from cm handles service method invoked' - def response = objectUnderTest.writeResourceDataPassthroughForCmHandle('some-cmHandle', - 'some-resourceIdentifier', 'some-dataType', requestBody) + def response = objectUnderTest.writeData(CREATE, 'some-cmHandle', + 'some-resourceIdentifier', 'some-dataType', 'data with quote " and \n new line') then: 'response have expected json' response == 'response json' - where: 'given request body' - scenario | requestBody - 'data contains normal char' | 'normal char string' - 'data contains quote and new line' | 'data with quote " and \n new line' } def 'Write resource data for passthrough running with a 500 response from sdnc.'() { given: 'sdnc returns a 500 response for the write operation' - mockSdncOperations.writeResourceDataPassthroughRunning(_, _, _, _) >> new ResponseEntity<String>('response json', HttpStatus.INTERNAL_SERVER_ERROR) - when: 'write resource data for pass through method is invoked' - objectUnderTest.writeResourceDataPassthroughForCmHandle('some-cmHandle', - 'some-resourceIdentifier', 'some-dataType', _ as String) + mockSdncOperations.writeData(CREATE, _, _, _, _) >> new ResponseEntity<String>('response json', HttpStatus.INTERNAL_SERVER_ERROR) + when: 'write resource data for passthrough method is invoked' + objectUnderTest.writeData(CREATE, 'some-cmHandle', + 'some-resourceIdentifier', 'some-dataType', _ as String) then: 'a dmi exception is thrown' thrown(DmiException.class) } diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy index 2184c7e7..8a3170b4 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/service/client/SdncRestconfClientSpec.groovy @@ -28,6 +28,11 @@ 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) @@ -49,7 +54,7 @@ class SdncRestconfClientSpec extends Specification { result == mockResponseEntity } - def 'SDNC POST operation called.'() { + def 'SDNC #scenario operation called.'() { given: 'json data' def jsonData = 'some-json' and: 'a url for get module resources' @@ -59,12 +64,18 @@ class SdncRestconfClientSpec extends Specification { and: 'the rest template returns a valid response entity' def mockResponseEntity = Mock(ResponseEntity) when: 'get module resources is invoked' - def result = objectUnderTest.postOperationWithJsonData(getModuleResourceUrl, jsonData, new HttpHeaders()) + def result = objectUnderTest.httpOperationWithJsonData(expectedHttpMethod, getModuleResourceUrl, jsonData, 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' }, - HttpMethod.POST, { it.body.contains(jsonData) }, String.class) >> mockResponseEntity + expectedHttpMethod, { it.body.contains(jsonData) }, String.class) >> mockResponseEntity and: 'the output of the method is the same as the output from the test template' result == mockResponseEntity + where: 'the following values are used' + scenario || expectedHttpMethod + 'POST' || POST + 'PUT' || PUT + 'GET' || GET + 'DELETE' || DELETE } def 'SDNC GET operation with header.'() { diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy index 4411971a..5079ada7 100644 --- a/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy +++ b/src/test/groovy/org/onap/cps/ncmp/dmi/service/operation/SdncOperationsSpec.groovy @@ -1,6 +1,7 @@ /* * ============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. @@ -20,15 +21,25 @@ 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.UPDATE +import static org.onap.cps.ncmp.dmi.model.DataAccessRequest.OperationEnum.READ + @SpringBootTest @ContextConfiguration(classes = [DmiConfiguration.SdncProperties, SdncOperations]) class SdncOperationsSpec extends Specification { @@ -39,14 +50,59 @@ class SdncOperationsSpec extends Specification { @Autowired SdncOperations objectUnderTest - def 'call get modules from node to SDNC.'() { + def 'get modules from node.'() { 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' - when: 'called get modules from node' - objectUnderTest.getModulesFromNode(nodeId) - then: 'the get operation is executed with the correct URL' - 1 * mockSdncRestClient.getOperation(expectedUrl) + and: 'sdnc returns one module in response' + mockSdncRestClient.getOperation(expectedUrl) >> + ResponseEntity.ok(TestUtils.getResourceFileContent('ModuleSchema.json')) + when: 'get modules from node is called' + 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: 'modules from node is called' + def moduleSchemas = objectUnderTest.getModuleSchemasFromNode(nodeId) + then: 'no modules are returned' + moduleSchemas.size() == 0 + where: + scenario | responseBody + '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: 'sdnc operation returns configured response' + mockSdncRestClient.getOperation(expectedUrl) >> new ResponseEntity<>(sdncResponseBody, sdncHttpStatus) + when: 'modules for 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.'() { @@ -56,7 +112,7 @@ class SdncOperationsSpec extends Specification { when: 'get module resources is called with the expected parameters' objectUnderTest.getModuleResource(nodeId, 'some-json-data') then: 'the SDNC Rest client is invoked with the correct URL and json data' - 1 * mockSdncRestClient.postOperationWithJsonData(expectedUrl, 'some-json-data', _ as HttpHeaders) + 1 * mockSdncRestClient.httpOperationWithJsonData(HttpMethod.POST, expectedUrl, 'some-json-data', _ as HttpHeaders) } def 'Get resource data from node to SDNC.'() { @@ -64,39 +120,45 @@ class SdncOperationsSpec extends Specification { def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/testResourceId?a=1&b=2&content=testContent' when: 'called get modules from node' objectUnderTest.getResouceDataForOperationalAndRunning('node1', 'testResourceId', - '(a=1,b=2)', 'testAcceptParam', 'content=testContent') + '(a=1,b=2)', 'testAcceptParam', 'content=testContent') then: 'the get operation is executed with the correct URL' 1 * mockSdncRestClient.getOperation(expectedUrl, _ as HttpHeaders) } - def 'Write resource data to SDNC.'() { + def 'Write resource data with #scenario operation to SDNC.'() { given: 'expected url, topology-id, sdncOperation object' def expectedUrl = '/rests/data/network-topology:network-topology/topology=test-topology/node=node1/yang-ext:mount/testResourceId' - when: 'write resource data for pass through running is called' - objectUnderTest.writeResourceDataPassthroughRunning('node1','testResourceId','application/json','requestData') - then: 'the post operation is executed with the correct URL and data' - 1 * mockSdncRestClient.postOperationWithJsonData(expectedUrl, 'requestData', _ as HttpHeaders) + 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 URL and data' + 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 } def 'build query param list for SDNC where options contains a #scenario'() { when: 'build query param list is called with #scenario' - def result = objectUnderTest.buildQueryParamList(optionsParamInQuery,'d=4') + def result = objectUnderTest.buildQueryParamList(optionsParamInQuery, 'd=4') then: 'result equals to expected result' result == expectedResult where: 'following parameters are used' - scenario | optionsParamInQuery || expectedResult - 'single key-value pair' | '(a=x)' || ['a=x','d=4'] - 'multiple key-value pairs'| '(a=x,b=y,c=z)' || ['a=x','b=y','c=z','d=4'] - '/ as special char' | '(a=x,b=y,c=t/z)' || ['a=x','b=y','c=t/z','d=4'] - '" as special char' | '(a=x,b=y,c="z")' || ['a=x','b=y','c="z"','d=4'] - '[] as special char' | '(a=x,b=y,c=[z])' || ['a=x','b=y','c=[z]','d=4'] - '= in value' | '(a=(x=y),b=x=y)' || ['a=(x=y)','b=x=y','d=4'] + scenario | optionsParamInQuery || expectedResult + 'single key-value pair' | '(a=x)' || ['a=x', 'd=4'] + 'multiple key-value pairs' | '(a=x,b=y,c=z)' || ['a=x', 'b=y', 'c=z', 'd=4'] + '/ as special char' | '(a=x,b=y,c=t/z)' || ['a=x', 'b=y', 'c=t/z', 'd=4'] + '" as special char' | '(a=x,b=y,c="z")' || ['a=x', 'b=y', 'c="z"', 'd=4'] + '[] as special char' | '(a=x,b=y,c=[z])' || ['a=x', 'b=y', 'c=[z]', 'd=4'] + '= in value' | '(a=(x=y),b=x=y)' || ['a=(x=y)', 'b=x=y', 'd=4'] } def 'options parameters contains a comma #scenario'() { // https://jira.onap.org/browse/CPS-719 when: 'build query param list is called with #scenario' - def result = objectUnderTest.buildQueryParamList(optionsParamInQuery,'d=4') + def result = objectUnderTest.buildQueryParamList(optionsParamInQuery, 'd=4') then: 'expect 2 elements from options +1 from content query param (2+1) = 3 elements' def expectedNoOfElements = 3 and: 'results contains more elements than expected' diff --git a/src/test/resources/dataWithNormalChar.json b/src/test/resources/createDataWithNormalChar.json index 31cdf1c5..31cdf1c5 100644 --- a/src/test/resources/dataWithNormalChar.json +++ b/src/test/resources/createDataWithNormalChar.json diff --git a/src/test/resources/dataWithSpecialChar.json b/src/test/resources/createDataWithSpecialChar.json index 1e7622ee..1e7622ee 100644 --- a/src/test/resources/dataWithSpecialChar.json +++ b/src/test/resources/createDataWithSpecialChar.json diff --git a/src/test/resources/deleteData.json b/src/test/resources/deleteData.json new file mode 100644 index 00000000..2233fa01 --- /dev/null +++ b/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/src/test/resources/readData.json b/src/test/resources/readData.json new file mode 100644 index 00000000..9f2b154f --- /dev/null +++ b/src/test/resources/readData.json @@ -0,0 +1,8 @@ +{ + "operation": "read", + "dataType": "application/json", + "data": "normal request body", + "cmHandleProperties": { + "some-property": "some-property-value" + } +}
\ No newline at end of file diff --git a/src/test/resources/updateData.json b/src/test/resources/updateData.json new file mode 100644 index 00000000..7cbf4f0c --- /dev/null +++ b/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 diff --git a/version.properties b/version.properties index b96ace0e..3f55958d 100644 --- a/version.properties +++ b/version.properties @@ -21,7 +21,7 @@ # because they are used in Jenkins, whose plug-in doesn't support this major=1 -minor=0 +minor=1 patch=0 base_version=${major}.${minor}.${patch} |