diff options
79 files changed, 2316 insertions, 1298 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index 0007bc8246..1eb1c11f20 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -1,190 +1,205 @@ -# ============LICENSE_START=======================================================
-# Copyright (C) 2021 Pantheon.tech
-# Modifications Copyright (C) 2021-2022 Bell Canada
-# Modifications Copyright (C) 2021-2023 Nordix Foundation
-# ================================================================================
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# SPDX-License-Identifier: Apache-2.0
-# ============LICENSE_END=========================================================
-
-server:
- port: 8080
-
-rest:
- api:
- cps-base-path: /cps/api
- ncmp-base-path: /ncmp
- ncmp-inventory-base-path: /ncmpInventory
-
-spring:
- main:
- banner-mode: "off"
- application:
- name: "cps-application"
- jpa:
- show-sql: false
- ddl-auto: create
- open-in-view: false
- properties:
- hibernate:
- enable_lazy_load_no_trans: true
- dialect: org.hibernate.dialect.PostgreSQLDialect
-
- datasource:
- url: jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/cpsdb
- username: ${DB_USERNAME}
- password: ${DB_PASSWORD}
- driverClassName: org.postgresql.Driver
- hikari:
- minimumIdle: 5
- maximumPoolSize: 80
- idleTimeout: 60000
- connectionTimeout: 120000
- leakDetectionThreshold: 30000
- pool-name: CpsDatabasePool
-
- cache:
- type: caffeine
- cache-names: yangSchema
- caffeine:
- spec: maximumSize=10000,expireAfterAccess=10m
-
- liquibase:
- change-log: classpath:changelog/changelog-master.yaml
- labels: ${LIQUIBASE_LABELS}
-
- servlet:
- multipart:
- enabled: true
- max-file-size: 100MB
- max-request-size: 100MB
-
- kafka:
- bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVER:localhost:9092}
- security:
- protocol: PLAINTEXT
- producer:
- value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
- client-id: cps-core
- consumer:
- group-id: ${NCMP_CONSUMER_GROUP_ID:ncmp-group}
- key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
- value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
- properties:
- spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
- spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
- spring.json.use.type.headers: false
-
- jackson:
- default-property-inclusion: NON_NULL
- serialization:
- FAIL_ON_EMPTY_BEANS: false
- sql:
- init:
- mode: ALWAYS
-app:
- ncmp:
- async-m2m:
- topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
- avc:
- subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:cm-avc-subscription}
- cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}
- lcm:
- events:
- topic: ${LCM_EVENTS_TOPIC:ncmp-events}
- dmi:
- cm-events:
- topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events}
-
-
-notification:
- enabled: true
- data-updated:
- topic: ${CPS_CHANGE_EVENT_TOPIC:cps.data-updated-events}
- filters:
- enabled-dataspaces: ${NOTIFICATION_DATASPACE_FILTER_PATTERNS:""}
- async:
- executor:
- core-pool-size: 2
- max-pool-size: 10
- queue-capacity: 500
- wait-for-tasks-to-complete-on-shutdown: true
- thread-name-prefix: Async-
- time-out-value-in-ms: 2000
-
-springdoc:
- swagger-ui:
- disable-swagger-default-url: true
- urlsPrimaryName: cps-core
- urls:
- - name: cps-core
- url: /api-docs/cps-core/openapi.yaml
- - name: cps-ncmp
- url: /api-docs/cps-ncmp/openapi.yaml
- - name: cps-ncmp-inventory
- url: /api-docs/cps-ncmp/openapi-inventory.yaml
-
-
-security:
- # comma-separated uri patterns which do not require authorization
- permit-uri: /manage/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/**
- auth:
- username: ${CPS_USERNAME}
- password: ${CPS_PASSWORD}
-
-# Actuator
-management:
- server:
- port: 8081
- endpoints:
- web:
- base-path: /manage
- exposure:
- include: info,health,loggers,prometheus
- endpoint:
- health:
- show-details: always
- # kubernetes probes: liveness and readiness
- probes:
- enabled: true
-
-logging:
- format: json
- level:
- org:
- springframework: INFO
- onap:
- cps: INFO
-ncmp:
- dmi:
- auth:
- username: ${DMI_USERNAME}
- password: ${DMI_PASSWORD}
- api:
- base-path: dmi
-
- timers:
- advised-modules-sync:
- sleep-time-ms: 5000
- locked-modules-sync:
- sleep-time-ms: 300000
- cm-handle-data-sync:
- sleep-time-ms: 30000
-
- modules-sync-watchdog:
- async-executor:
- parallelism-level: 10
-
- model-loader:
- subscription: false
\ No newline at end of file +# ============LICENSE_START======================================================= +# Copyright (C) 2021 Pantheon.tech +# Modifications Copyright (C) 2021-2022 Bell Canada +# Modifications Copyright (C) 2021-2023 Nordix Foundation +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ============LICENSE_END========================================================= + +server: + port: 8080 + +rest: + api: + cps-base-path: /cps/api + ncmp-base-path: /ncmp + ncmp-inventory-base-path: /ncmpInventory + +spring: + main: + banner-mode: "off" + application: + name: "cps-application" + jpa: + show-sql: false + ddl-auto: create + open-in-view: false + properties: + hibernate: + enable_lazy_load_no_trans: true + dialect: org.hibernate.dialect.PostgreSQLDialect + + datasource: + url: jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/cpsdb + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + driverClassName: org.postgresql.Driver + hikari: + minimumIdle: 5 + maximumPoolSize: 80 + idleTimeout: 60000 + connectionTimeout: 120000 + leakDetectionThreshold: 30000 + pool-name: CpsDatabasePool + + cache: + type: caffeine + cache-names: yangSchema + caffeine: + spec: maximumSize=10000,expireAfterAccess=10m + + liquibase: + change-log: classpath:changelog/changelog-master.yaml + labels: ${LIQUIBASE_LABELS} + + servlet: + multipart: + enabled: true + max-file-size: 100MB + max-request-size: 100MB + + kafka: + bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVER:localhost:9092} + security: + protocol: PLAINTEXT + producer: + value-serializer: org.springframework.kafka.support.serializer.JsonSerializer + client-id: cps-core + consumer: + group-id: ${NCMP_CONSUMER_GROUP_ID:ncmp-group} + key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer + value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer + properties: + spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer + spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer + spring.json.use.type.headers: false + + jackson: + default-property-inclusion: NON_NULL + serialization: + FAIL_ON_EMPTY_BEANS: false + sql: + init: + mode: ALWAYS +app: + ncmp: + async-m2m: + topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m} + avc: + subscription-topic: ${NCMP_CM_AVC_SUBSCRIPTION:cm-avc-subscription} + subscription-forward-topic: ${NCMP_FORWARD_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription} + subscription-response-topic: ${NCMP_RESPONSE_CM_AVC_SUBSCRIPTION:dmi-ncmp-cm-avc-subscription} + subscription-outcome-topic: ${NCMP_OUTCOME_CM_AVC_SUBSCRIPTION:cm-avc-subscription-response} + cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events} + lcm: + events: + topic: ${LCM_EVENTS_TOPIC:ncmp-events} + dmi: + cm-events: + topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events} + + +notification: + enabled: true + data-updated: + topic: ${CPS_CHANGE_EVENT_TOPIC:cps.data-updated-events} + filters: + enabled-dataspaces: ${NOTIFICATION_DATASPACE_FILTER_PATTERNS:""} + async: + executor: + core-pool-size: 2 + max-pool-size: 10 + queue-capacity: 500 + wait-for-tasks-to-complete-on-shutdown: true + thread-name-prefix: Async- + time-out-value-in-ms: 2000 + +springdoc: + swagger-ui: + disable-swagger-default-url: true + urlsPrimaryName: cps-core + urls: + - name: cps-core + url: /api-docs/cps-core/openapi.yaml + - name: cps-ncmp + url: /api-docs/cps-ncmp/openapi.yaml + - name: cps-ncmp-inventory + url: /api-docs/cps-ncmp/openapi-inventory.yaml + + +security: + # comma-separated uri patterns which do not require authorization + permit-uri: /manage/**,/swagger-ui.html,/swagger-ui/**,/swagger-resources/**,/api-docs/** + auth: + username: ${CPS_USERNAME} + password: ${CPS_PASSWORD} + +# Actuator +management: + server: + port: 8081 + endpoints: + web: + base-path: /manage + exposure: + include: info,health,loggers,prometheus + endpoint: + health: + show-details: always + # kubernetes probes: liveness and readiness + probes: + enabled: true + +logging: + format: json + level: + org: + springframework: INFO + onap: + cps: INFO +ncmp: + dmi: + auth: + username: ${DMI_USERNAME} + password: ${DMI_PASSWORD} + api: + base-path: dmi + + timers: + advised-modules-sync: + sleep-time-ms: 5000 + locked-modules-sync: + sleep-time-ms: 300000 + cm-handle-data-sync: + sleep-time-ms: 30000 + subscription-forwarding: + dmi-response-timeout-ms: 30000 + model-loader: + retry-time-ms: 1000 + + modules-sync-watchdog: + async-executor: + parallelism-level: 10 + + model-loader: + subscription: false + maximum-attempt-count: 20 + +# Custom Hazelcast Config. +hazelcast: + mode: + kubernetes: + enabled: ${HAZELCAST_MODE_KUBERNETES_ENABLED:false} + service-name: ${CPS_NCMP_SERVICE_NAME:"cps-and-ncmp-service"}
\ No newline at end of file diff --git a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java index 3990dd15d4..6d5ee8ce6e 100644 --- a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java +++ b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Bell Canada - * Modifications Copyright (c) 2022 Nordix Foundation + * Modifications Copyright (c) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,14 +31,10 @@ import java.util.List; import java.util.Map; import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType; import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; -import org.onap.cps.ncmp.rest.model.RestModuleDefinition; -import org.onap.cps.ncmp.rest.model.RestModuleReference; import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; -import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState; -import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties; +import org.onap.cps.ncmp.rest.stub.handlers.NetworkCmProxyApiStubDefaultImpl; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpStatus; @@ -49,9 +45,7 @@ import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("${rest.api.ncmp-stub-base-path}") -public class NetworkCmProxyStubController implements NetworkCmProxyApi { - - public static final String ASYNC_REQUEST_ID = "requestId"; +public class NetworkCmProxyStubController implements NetworkCmProxyApiStubDefaultImpl { @Value("${stub.path}") private String pathToResponseFiles; @@ -87,23 +81,6 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { } @Override - public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName, - final String resourceIdentifier, - final String cmHandleId, - final Object body, - final String contentType) { - return new ResponseEntity<>(HttpStatus.CREATED); - } - - @Override - public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName, - final String cmHandleId, - final String resourceIdentifier, - final String contentType) { - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - } - - @Override public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles( final CmHandleQueryParameters cmHandleQueryParameters) { List<RestOutputCmHandle> restOutputCmHandles = null; @@ -120,78 +97,12 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { return ResponseEntity.ok(restOutputCmHandles); } - @Override - public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId, - final Boolean dataSyncEnabled) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( - final String cmHandleId) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(final String cmHandle) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitionsByCmHandleId(final String cmHandle) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandleId) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName, - final String resourceIdentifier, - final String cmHandleId, - final Object body, - final String contentType) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<Object> queryResourceDataForCmHandle(final String datastoreName, - final String cmHandle, - final String cpsPath, - final String options, - final String topic, - final Boolean includeDescendants) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleId) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - - @Override - public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName, - final String resourceIdentifier, - final String cmHandleId, - final Object body, - final String contentType) { - return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); - } - private ResponseEntity<Map<String, Object>> populateAsyncResponse(final String topicParamInQuery) { final Map<String, Object> responseData; - if (topicParamInQuery != null) { - responseData = getAsyncResponseData(); - } else { + if (topicParamInQuery == null) { responseData = null; + } else { + responseData = getAsyncResponseData(); } return ResponseEntity.ok().body(responseData); } diff --git a/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java new file mode 100644 index 0000000000..6e28dbc44c --- /dev/null +++ b/cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.java @@ -0,0 +1,145 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.stub.handlers; + +import java.util.List; +import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi; +import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters; +import org.onap.cps.ncmp.rest.model.RestModuleDefinition; +import org.onap.cps.ncmp.rest.model.RestModuleReference; +import org.onap.cps.ncmp.rest.model.RestOutputCmHandle; +import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState; +import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public interface NetworkCmProxyApiStubDefaultImpl extends NetworkCmProxyApi { + + String ASYNC_REQUEST_ID = "requestId"; + + @Override + default ResponseEntity<Object> getResourceDataForCmHandle(final String datastoreName, + final String cmHandle, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<Object> getResourceDataForCmHandleBatch(final String resourceIdentifier, + final String topicParamInQuery, + final String datastoreName, + final Object requestBody, + final String optionsParamInQuery, + final Boolean includeDescendants) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<List<RestOutputCmHandle>> searchCmHandles( + final CmHandleQueryParameters cmHandleQueryParameters) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName, + final String resourceIdentifier, + final String cmHandleId, + final Object requestBody, + final String contentType) { + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @Override + default ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName, + final String cmHandleId, + final String resourceIdentifier, + final String contentType) { + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @Override + default ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId, + final Boolean dataSyncEnabled) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId( + final String cmHandleId) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(final String cmHandle) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<List<RestModuleDefinition>> getModuleDefinitionsByCmHandleId(final String cmHandle) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandleId) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName, + final String resourceIdentifier, + final String cmHandleId, + final Object requestBody, + final String contentType) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<Object> queryResourceDataForCmHandle(final String datastoreName, + final String cmHandle, + final String cpsPath, + final String optionsParamInQuery, + final String topicParamInQuery, + final Boolean includeDescendants) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<RestOutputCmHandle> retrieveCmHandleDetailsById(final String cmHandleId) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + @Override + default ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName, + final String resourceIdentifier, + final String cmHandleId, + final Object requestBody, + final String contentType) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } +} diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 6ca63c7caf..7fc1063a12 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2021-2022 Nordix Foundation +# Copyright (C) 2021-2023 Nordix Foundation # Modifications Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2022 Bell Canada # ================================================================================ @@ -522,6 +522,18 @@ components: sample 1: value: topic: my-topic-name + requiredTopicParamInQuery: + name: topic + in: query + description: mandatory topic parameter in query. + required: true + schema: + type: string + allowReserved: true + examples: + sample 1: + value: + topic: my-topic-name contentParamInHeader: name: Content-Type in: header diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index 1f7cce96ff..2b70d94892 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2021-2022 Nordix Foundation +# Copyright (C) 2021-2023 Nordix Foundation # Modifications Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2021-2022 Bell Canada # ================================================================================ @@ -194,6 +194,43 @@ resourceDataForCmHandle: 502: $ref: 'components.yaml#/components/responses/BadGateway' +getResourceDataForCmHandleBatch: + post: + tags: + - network-cm-proxy + summary: Get resource data for batch of cm handle ids + description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages. + operationId: getResourceDataForCmHandleBatch + parameters: + - $ref: 'components.yaml#/components/parameters/datastoreName' + - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery' + - $ref: 'components.yaml#/components/parameters/optionsParamInQuery' + - $ref: 'components.yaml#/components/parameters/requiredTopicParamInQuery' + - $ref: 'components.yaml#/components/parameters/includeDescendantsOptionInQuery' + requestBody: + required: true + content: + application/json: + schema: + type: object + responses: + 200: + description: OK + content: + application/json: + schema: + type: object + 400: + $ref: 'components.yaml#/components/responses/BadRequest' + 401: + $ref: 'components.yaml#/components/responses/Unauthorized' + 403: + $ref: 'components.yaml#/components/responses/Forbidden' + 500: + $ref: 'components.yaml#/components/responses/InternalServerError' + 502: + $ref: 'components.yaml#/components/responses/BadGateway' + queryResourceDataForCmHandle: get: tags: diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index ee29366906..5b4c0d3496 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2021-2022 Nordix Foundation +# Copyright (C) 2021-2023 Nordix Foundation # Modifications Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2021 Bell Canada # ================================================================================ @@ -34,6 +34,9 @@ paths: /v1/ch/{cm-handle}/data/ds/{datastore-name}: $ref: 'ncmp.yml#/resourceDataForCmHandle' + /v1/batch/data/ds/{datastore-name}: + $ref: 'ncmp.yml#/getResourceDataForCmHandleBatch' + /v1/ch/{cm-handle}/data/ds/{datastore-name}/query: $ref: 'ncmp.yml#/queryResourceDataForCmHandle' diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index 4da251f3cc..a8bc3aec76 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -23,10 +23,10 @@ package org.onap.cps.ncmp.rest.controller; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE; +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE; +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE; +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH; +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE; import java.util.Collection; import java.util.List; @@ -74,12 +74,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Get resource data from datastore. * - * @param datastoreName name of the datastore - * @param cmHandle cm handle identifier - * @param resourceIdentifier resource identifier - * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter - * @param includeDescendants whether include descendants + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param resourceIdentifier resource identifier + * @param optionsParamInQuery options query parameter + * @param topicParamInQuery topic query parameter + * @param includeDescendantsAsObject whether include descendants * @return {@code ResponseEntity} response from dmi plugin */ @@ -89,25 +89,48 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery, - final Boolean includeDescendants) { + final Boolean includeDescendantsAsObject) { final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler = - ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( + ncmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler( DatastoreType.fromDatastoreName(datastoreName)); + final boolean includeDescendants = toPrimitiveFlag(includeDescendantsAsObject); + return ncmpDatastoreRequestHandler.executeRequest(cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, includeDescendants); } + @Override + public ResponseEntity<Object> getResourceDataForCmHandleBatch(final String resourceIdentifier, + final String topicParamInQuery, + final String datastoreName, + final Object requestBody, + final String optionsParamInQuery, + final Boolean includeDescendantsAsObject) { + + final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler = + ncmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler( + DatastoreType.fromDatastoreName(datastoreName)); + + final List<String> cmHandleIds = jsonObjectMapper.convertJsonString(jsonObjectMapper.asJsonString(requestBody), + List.class); + + final boolean includeDescendants = toPrimitiveFlag(includeDescendantsAsObject); + + return ncmpDatastoreRequestHandler.executeRequest(cmHandleIds, resourceIdentifier, + optionsParamInQuery, topicParamInQuery, includeDescendants); + } + /** * Query resource data from datastore. * - * @param datastoreName name of the datastore - * @param cmHandle cm handle identifier - * @param cpsPath CPS Path - * @param optionsParamInQuery options query parameter - * @param topicParamInQuery topic query parameter - * @param includeDescendants whether include descendants + * @param datastoreName name of the datastore + * @param cmHandle cm handle identifier + * @param cpsPath CPS Path + * @param optionsParamInQuery options query parameter + * @param topicParamInQuery topic query parameter + * @param includeDescendantsAsObject whether include descendants * @return {@code ResponseEntity} response from dmi plugin */ @@ -117,13 +140,15 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { final String cpsPath, final String optionsParamInQuery, final String topicParamInQuery, - final Boolean includeDescendants) { + final Boolean includeDescendantsAsObject) { validateDataStore(DatastoreType.OPERATIONAL, datastoreName); - final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler = - ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceQueryHandler(); + final NcmpDatastoreRequestHandler ncmpCachedResourceRequestHandler = + ncmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler( + DatastoreType.fromDatastoreName(datastoreName)); - return ncmpDatastoreRequestHandler.executeRequest(cmHandle, cpsPath, optionsParamInQuery, - topicParamInQuery, includeDescendants); + final boolean includeDescendants = toPrimitiveFlag(includeDescendantsAsObject); + + return ncmpCachedResourceRequestHandler.executeRequest(cmHandle, cpsPath, includeDescendants); } /** @@ -367,5 +392,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { throw new InvalidDatastoreException(requestedDatastoreName + " is not supported"); } } + + private static boolean toPrimitiveFlag(final Boolean includeDescendantsAsObject) { + if (includeDescendantsAsObject == null) { + return false; + } + return includeDescendantsAsObject.booleanValue(); + } } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java new file mode 100644 index 0000000000..620f64782b --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpCachedResourceRequestHandler.java @@ -0,0 +1,70 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +import java.util.function.Supplier; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.onap.cps.ncmp.api.NetworkCmProxyQueryService; +import org.onap.cps.spi.FetchDescendantsOption; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class NcmpCachedResourceRequestHandler extends NcmpDatastoreRequestHandler { + + @Setter + private String dataStoreName; + private final NetworkCmProxyQueryService networkCmProxyQueryService; + + @Override + public Supplier<Object> getTaskSupplierForGetRequest(final String cmHandleId, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final boolean includeDescendants) { + + final FetchDescendantsOption fetchDescendantsOption = + TaskManagementDefaultHandler.getFetchDescendantsOption(includeDescendants); + + return () -> networkCmProxyDataService.getResourceDataForCmHandle(dataStoreName, cmHandleId, resourceIdentifier, + fetchDescendantsOption); + } + + /** + * Gets ncmp datastore query handler. + * Note. Currently only ncmp-datastore:operational supports query operations + * @return a ncmp datastore query handler. + */ + @Override + public Supplier<Object> getTaskSupplierForQueryRequest(final String cmHandleId, + final String resourceIdentifier, + final boolean includeDescendants) { + + final FetchDescendantsOption fetchDescendantsOption = + TaskManagementDefaultHandler.getFetchDescendantsOption(includeDescendants); + + return () -> networkCmProxyQueryService.queryResourceDataOperational(cmHandleId, resourceIdentifier, + fetchDescendantsOption); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalQueryHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalQueryHandler.java deleted file mode 100644 index 0586d42625..0000000000 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalQueryHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.rest.controller.handlers; - -import java.util.function.Supplier; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.NetworkCmProxyQueryService; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; -import org.onap.cps.spi.FetchDescendantsOption; - -@Slf4j -public class NcmpDatastoreOperationalQueryHandler extends NcmpDatastoreRequestHandler { - - private final NetworkCmProxyQueryService networkCmProxyQueryService; - - public NcmpDatastoreOperationalQueryHandler(final NetworkCmProxyQueryService networkCmProxyQueryService, - final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, - final int timeOutInMilliSeconds, - final boolean notificationFeatureEnabled) { - super(null, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); - this.networkCmProxyQueryService = networkCmProxyQueryService; - } - - @Override - public Supplier<Object> getTaskSupplier(final String cmHandle, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final Boolean includeDescendant) { - - final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendant); - - return () -> networkCmProxyQueryService.queryResourceDataOperational(cmHandle, resourceIdentifier, - fetchDescendantsOption); - } - -} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java deleted file mode 100644 index a4720b22ff..0000000000 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.rest.controller.handlers; - -import java.util.function.Supplier; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.NetworkCmProxyDataService; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; -import org.onap.cps.spi.FetchDescendantsOption; - -@Slf4j -public class NcmpDatastoreOperationalResourceRequestHandler extends NcmpDatastoreRequestHandler { - - public NcmpDatastoreOperationalResourceRequestHandler(final NetworkCmProxyDataService networkCmProxyDataService, - final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, - final int timeOutInMilliSeconds, - final boolean notificationFeatureEnabled) { - super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); - } - - @Override - public Supplier<Object> getTaskSupplier(final String cmHandle, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final Boolean includeDescendant) { - - final FetchDescendantsOption fetchDescendantsOption = getFetchDescendantsOption(includeDescendant); - - return () -> networkCmProxyDataService.getResourceDataOperational(cmHandle, resourceIdentifier, - fetchDescendantsOption); - } - -} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java deleted file mode 100644 index 1445e3e271..0000000000 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.rest.controller.handlers; - -import java.util.function.Supplier; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.NetworkCmProxyDataService; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; - -@Slf4j -public class NcmpDatastorePassthroughOperationalResourceRequestHandler extends NcmpDatastoreRequestHandler { - - public NcmpDatastorePassthroughOperationalResourceRequestHandler( - final NetworkCmProxyDataService networkCmProxyDataService, - final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, - final int timeOutInMilliSeconds, - final boolean notificationFeatureEnabled) { - super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); - } - - @Override - public Supplier<Object> getTaskSupplier(final String cmHandle, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final Boolean includeDescendant) { - - return () -> networkCmProxyDataService.getResourceDataOperationalForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); - } - -} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java deleted file mode 100644 index 8194ec9fd5..0000000000 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.rest.controller.handlers; - -import java.util.function.Supplier; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.ncmp.api.NetworkCmProxyDataService; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; - -@Slf4j -public class NcmpDatastorePassthroughRunningResourceRequestHandler extends NcmpDatastoreRequestHandler { - - public NcmpDatastorePassthroughRunningResourceRequestHandler( - final NetworkCmProxyDataService networkCmProxyDataService, - final CpsNcmpTaskExecutor cpsNcmpTaskExecutor, - final int timeOutInMilliSeconds, - final boolean notificationFeatureEnabled) { - super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); - } - - @Override - public Supplier<Object> getTaskSupplier(final String cmHandle, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final Boolean includeDescendant) { - - return () -> networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle( - cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); - } -} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java index 850200396b..050e724b2c 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,38 +20,37 @@ package org.onap.cps.ncmp.rest.controller.handlers; +import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.Supplier; -import lombok.RequiredArgsConstructor; +import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; import org.onap.cps.ncmp.rest.util.TopicValidator; -import org.onap.cps.spi.FetchDescendantsOption; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; -@RequiredArgsConstructor +@NoArgsConstructor @Slf4j -public abstract class NcmpDatastoreRequestHandler { +@Service +public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler { - private static final String NO_REQUEST_ID = null; - private static final String NO_TOPIC = null; - - protected final NetworkCmProxyDataService networkCmProxyDataService; - protected final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; - protected final int timeOutInMilliSeconds; - protected final boolean notificationFeatureEnabled; - - protected abstract Supplier<Object> getTaskSupplier(final String cmHandle, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId, - final Boolean includeDescendant); + @Value("${notification.async.executor.time-out-value-in-ms:2000}") + protected int timeOutInMilliSeconds; + @Value("${notification.enabled:true}") + protected boolean notificationFeatureEnabled; + @Autowired + protected NetworkCmProxyDataService networkCmProxyDataService; + @Autowired + protected CpsNcmpTaskExecutor cpsNcmpTaskExecutor; /** - * Execute a request on a datastore. + * Executes synchronous/asynchronous request for given cm handle. * * @param cmHandleId the cm handle * @param resourceIdentifier the resource identifier @@ -64,25 +63,62 @@ public abstract class NcmpDatastoreRequestHandler { final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery, - final Boolean includeDescendants) { + final boolean includeDescendants) { final boolean asyncResponseRequested = topicParamInQuery != null; if (asyncResponseRequested && notificationFeatureEnabled) { - final String requestId = UUID.randomUUID().toString(); - final Supplier<Object> taskSupplier = getTaskSupplier(cmHandleId, resourceIdentifier, optionsParamInQuery, - topicParamInQuery, requestId, includeDescendants); - return executeTaskAsync(topicParamInQuery, requestId, taskSupplier); + return executeAsyncTaskAndGetResponseEntity(cmHandleId, resourceIdentifier, optionsParamInQuery, + topicParamInQuery, includeDescendants, false); } if (asyncResponseRequested) { log.warn("Asynchronous request is unavailable as notification feature is currently disabled, " - + "will use synchronous operation."); + + "will use synchronous operation."); } - final Supplier<Object> taskSupplier = getTaskSupplier(cmHandleId, resourceIdentifier, optionsParamInQuery, - NO_TOPIC, NO_REQUEST_ID, includeDescendants); + final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(cmHandleId, + resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID, includeDescendants); return executeTaskSync(taskSupplier); } + /** + * Executes a synchronous request for given cm handle. + * Note. Currently only ncmp-datastore:operational supports query operations. + * + * @param cmHandleId the cm handle + * @param resourceIdentifier the resource identifier + * @param includeDescendants whether include descendants + * @return the response entity + */ + public ResponseEntity<Object> executeRequest(final String cmHandleId, + final String resourceIdentifier, + final boolean includeDescendants) { + + final Supplier<Object> taskSupplier = getTaskSupplierForQueryRequest(cmHandleId, resourceIdentifier, + includeDescendants); + return executeTaskSync(taskSupplier); + } + + /** + * Executes synchronous/asynchronous request for batch of cm handles. + * + * @param cmHandleIds list of cm handles + * @param resourceIdentifier the resource identifier + * @param optionsParamInQuery the options param in query + * @param topicParamInQuery the topic param in query + * @param includeDescendants whether include descendants + * @return the response entity + */ + public ResponseEntity<Object> executeRequest(final List<String> cmHandleIds, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final boolean includeDescendants) { + + return executeAsyncTaskAndGetResponseEntity(cmHandleIds, resourceIdentifier, optionsParamInQuery, + topicParamInQuery, includeDescendants, true); + + } + protected ResponseEntity<Object> executeTaskAsync(final String topicParamInQuery, final String requestId, final Supplier<Object> taskSupplier) { @@ -98,9 +134,25 @@ public abstract class NcmpDatastoreRequestHandler { return ResponseEntity.ok(taskSupplier.get()); } - protected static FetchDescendantsOption getFetchDescendantsOption(final Boolean includeDescendant) { - return Boolean.TRUE.equals(includeDescendant) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS - : FetchDescendantsOption.OMIT_DESCENDANTS; + private ResponseEntity<Object> executeAsyncTaskAndGetResponseEntity(final Object targetObject, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final boolean includeDescendants, + final boolean isBulkRequest) { + final String requestId = UUID.randomUUID().toString(); + final Supplier<Object> taskSupplier; + if (isBulkRequest) { + taskSupplier = getTaskSupplierForBulkRequest((List<String>) targetObject, + resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, includeDescendants); + } else { + taskSupplier = getTaskSupplierForGetRequest(targetObject.toString(), resourceIdentifier, + optionsParamInQuery, topicParamInQuery, requestId, includeDescendants); + } + if (taskSupplier == NO_OBJECT_SUPPLIER) { + return new ResponseEntity<>(Map.of("status", "Unable to execute request as " + + "datastore is not implemented."), HttpStatus.NOT_IMPLEMENTED); + } + return executeTaskAsync(topicParamInQuery, requestId, taskSupplier); } - } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java index ff7bda6a47..9a71798fa1 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,24 +21,13 @@ package org.onap.cps.ncmp.rest.controller.handlers; import lombok.RequiredArgsConstructor; -import org.onap.cps.ncmp.api.NetworkCmProxyDataService; -import org.onap.cps.ncmp.api.NetworkCmProxyQueryService; -import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor public class NcmpDatastoreResourceRequestHandlerFactory { - - private final NetworkCmProxyDataService networkCmProxyDataService; - private final NetworkCmProxyQueryService networkCmProxyQueryService; - private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor; - - @Value("${notification.async.executor.time-out-value-in-ms:2000}") - private int timeOutInMilliSeconds; - @Value("${notification.enabled:true}") - private boolean notificationFeatureEnabled; + private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler; + private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler; /** * Gets ncmp datastore handler. @@ -46,31 +35,17 @@ public class NcmpDatastoreResourceRequestHandlerFactory { * @param datastoreType the datastore type * @return the ncmp datastore handler */ - public NcmpDatastoreRequestHandler getNcmpDatastoreResourceRequestHandler( - final DatastoreType datastoreType) { + public NcmpDatastoreRequestHandler getNcmpResourceRequestHandler(final DatastoreType datastoreType) { switch (datastoreType) { case OPERATIONAL: - return new NcmpDatastoreOperationalResourceRequestHandler(networkCmProxyDataService, - cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + ncmpCachedResourceRequestHandler.setDataStoreName(datastoreType.getDatastoreName()); + return ncmpCachedResourceRequestHandler; case PASSTHROUGH_RUNNING: - return new NcmpDatastorePassthroughRunningResourceRequestHandler(networkCmProxyDataService, - cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); case PASSTHROUGH_OPERATIONAL: default: - return new NcmpDatastorePassthroughOperationalResourceRequestHandler(networkCmProxyDataService, - cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled); + ncmpPassthroughResourceRequestHandler.setDataStoreName(datastoreType.getDatastoreName()); + return ncmpPassthroughResourceRequestHandler; } } - - /** - * Gets ncmp datastore query handler. - * Note. Currently only ncmp-datastore:operational supports query operations - * @return a ncmp datastore query handler. - */ - public NcmpDatastoreRequestHandler getNcmpDatastoreResourceQueryHandler() { - return new NcmpDatastoreOperationalQueryHandler(networkCmProxyQueryService, cpsNcmpTaskExecutor, - timeOutInMilliSeconds, notificationFeatureEnabled); - } - } diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java new file mode 100644 index 0000000000..ab5d587e93 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java @@ -0,0 +1,58 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +import java.util.List; +import java.util.function.Supplier; +import lombok.Setter; +import org.springframework.stereotype.Component; + +@Component +public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestHandler { + + @Setter + private String dataStoreName; + + @Override + public Supplier<Object> getTaskSupplierForGetRequest(final String cmHandleId, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final boolean includeDescendants) { + + return () -> networkCmProxyDataService.getResourceDataForCmHandle( + dataStoreName, cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } + + @Override + public Supplier<Object> getTaskSupplierForBulkRequest(final List<String> cmHandleIds, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final boolean includeDescendants) { + + return () -> networkCmProxyDataService.getResourceDataForCmHandleBatch( + dataStoreName, cmHandleIds, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId); + } + +} diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java new file mode 100644 index 0000000000..08e8407c63 --- /dev/null +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java @@ -0,0 +1,63 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.rest.controller.handlers; + +import java.util.List; +import java.util.function.Supplier; +import org.onap.cps.spi.FetchDescendantsOption; + +public interface TaskManagementDefaultHandler { + + String NO_REQUEST_ID = null; + String NO_TOPIC = null; + Supplier<Object> NO_OBJECT_SUPPLIER = null; + + default Supplier<Object> getTaskSupplierForGetRequest(final String cmHandleId, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final boolean includeDescendant) { + return NO_OBJECT_SUPPLIER; + + } + + default Supplier<Object> getTaskSupplierForQueryRequest(final String cmHandleId, + final String resourceIdentifier, + final boolean includeDescendant) { + return NO_OBJECT_SUPPLIER; + + } + + default Supplier<Object> getTaskSupplierForBulkRequest(final List<String> cmHandleIds, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final boolean includeDescendant) { + return NO_OBJECT_SUPPLIER; + } + + static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) { + return includeDescendants ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS + : FetchDescendantsOption.OMIT_DESCENDANTS; + } +} diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index d67804e128..9531101e1d 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021 highstreet technologies GmbH - * Modifications Copyright (C) 2021-2022 Nordix Foundation + * Modifications Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2021-2022 Bell Canada. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,18 +33,13 @@ import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.inventory.DataStoreSyncState import org.onap.cps.ncmp.api.inventory.LockReasonCategory import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType -import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalQueryHandler -import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalResourceRequestHandler -import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughOperationalResourceRequestHandler -import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughRunningResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler +import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory -import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DeprecationHelper import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper @@ -55,31 +50,32 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.test.web.servlet.MockMvc -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder import spock.lang.Shared import spock.lang.Specification - import java.time.OffsetDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE +import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.rest.controller.handlers.DatastoreType.OPERATIONAL @WebMvcTest(NetworkCmProxyController) class NetworkCmProxyControllerSpec extends Specification { - public static final int TIMEOUT_IN_MS = 2000 - public static final boolean NOTIFICATION_ENABLED = true + private static final int TIMEOUT_IN_MS = 2000 + private static final boolean NOTIFICATION_ENABLED = true @Autowired MockMvc mvc @@ -115,6 +111,7 @@ class NetworkCmProxyControllerSpec extends Specification { def ncmpBasePathV1 def requestBody = '{"some-key":"some-value"}' + def bulkRequestBody = '["testCmHandle"]' @Shared def NO_TOPIC = null @@ -124,24 +121,15 @@ class NetworkCmProxyControllerSpec extends Specification { .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) void setup() { - stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( - DatastoreType.OPERATIONAL) >> - new NcmpDatastoreOperationalResourceRequestHandler( - mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler( + OPERATIONAL) >> getNcmpDatastoreRequestHandler(OPERATIONAL,new NcmpCachedResourceRequestHandler(mockNetworkCmProxyQueryService)) - stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( - DatastoreType.PASSTHROUGH_OPERATIONAL) >> - new NcmpDatastorePassthroughOperationalResourceRequestHandler( - mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler( + PASSTHROUGH_OPERATIONAL) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_OPERATIONAL,new NcmpPassthroughResourceRequestHandler()) - stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler( - DatastoreType.PASSTHROUGH_RUNNING) >> - new NcmpDatastorePassthroughRunningResourceRequestHandler( - mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED) + stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpResourceRequestHandler( + PASSTHROUGH_RUNNING) >> getNcmpDatastoreRequestHandler(PASSTHROUGH_RUNNING,new NcmpPassthroughResourceRequestHandler()) - stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceQueryHandler() >> - new NcmpDatastoreOperationalQueryHandler(mockNetworkCmProxyQueryService, spiedCpsTaskExecutor, - TIMEOUT_IN_MS, NOTIFICATION_ENABLED); } def 'Get Resource Data from pass-through operational.'() { @@ -154,11 +142,8 @@ class NetworkCmProxyControllerSpec extends Specification { .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle' - 1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle', - 'parent/child', - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) + 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', + 'parent/child','(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) and: 'response status is Ok' response.status == HttpStatus.OK.value() } @@ -205,6 +190,48 @@ class NetworkCmProxyControllerSpec extends Specification { 'invalid non-empty topic value in url' | 'passthrough-running' | '&topic=1_5_*_#' } + def 'Get bulk resource data for #datastoreName from dmi service.'() { + given: 'bulk resource data url' + def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastoreName}" + + "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic" + when: 'post data resource request is performed' + def response = mvc.perform( + post(getUrl) + .contentType(MediaType.APPLICATION_JSON) + .content(bulkRequestBody) + ).andReturn().response + then: 'response status is Ok' + response.status == HttpStatus.OK.value() + // TODO Need to be un-commented as it's failing into onap CICD pipeline + // but passed into nordix and local build. + //and: 'the NCMP data service is called with getResourceDataForCmHandleBatch' + // 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'], + // 'parent/child', + // '(a=1,b=2)', + // 'myTopic', + // _) + and: 'async request id is generated' + assert response.contentAsString.contains("requestId") + where: 'the following data stores are used' + datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName] + } + + def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() { + given: 'bulk resource data url' + def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" + + "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic" + when: 'post data resource request is performed' + def response = mvc.perform( + post(getUrl) + .contentType(MediaType.APPLICATION_JSON) + .content(bulkRequestBody) + ).andReturn().response + then: 'response status code is 501 not implemented' + response.status == HttpStatus.NOT_IMPLEMENTED.value() + where: 'the following data store is un-supported' + datastoreName << [OPERATIONAL.datastoreName] + } + def 'Query Resource Data from operational.'() { given: 'the query resource data url' def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational/query" + @@ -244,11 +271,8 @@ class NetworkCmProxyControllerSpec extends Specification { def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" + "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)" and: 'ncmp service returns json object' - mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle', - resourceIdentifier, - '(a=1,b=2)', - NO_TOPIC, - NO_REQUEST_ID) >> '{valid-json}' + mockNetworkCmProxyDataService.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', + resourceIdentifier,'(a=1,b=2)', NO_TOPIC, NO_REQUEST_ID) >> '{valid-json}' when: 'get data resource request is performed' def response = mvc.perform( get(getUrl) @@ -531,9 +555,7 @@ class NetworkCmProxyControllerSpec extends Specification { .contentType(MediaType.APPLICATION_JSON) ).andReturn().response then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption' - 1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle', - 'parent/child', - descendantsOption) + 1 * mockNetworkCmProxyDataService.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'parent/child', descendantsOption) and: 'response status is Ok' response.status == HttpStatus.OK.value() where: 'the following parameters are used' @@ -626,5 +648,19 @@ class NetworkCmProxyControllerSpec extends Specification { return assertContainsAll(response, expectedContent) } + def getNcmpDatastoreRequestHandler(dataStoreType, ncmpDatastoreRequestHandler) { + if (ncmpDatastoreRequestHandler instanceof NcmpCachedResourceRequestHandler) { + NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler = (NcmpCachedResourceRequestHandler) ncmpDatastoreRequestHandler + ncmpCachedResourceRequestHandler.dataStoreName = dataStoreType.datastoreName + } else { + NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler = (NcmpPassthroughResourceRequestHandler) ncmpDatastoreRequestHandler + ncmpPassthroughResourceRequestHandler.dataStoreName = dataStoreType.datastoreName + } + ncmpDatastoreRequestHandler.networkCmProxyDataService = mockNetworkCmProxyDataService + ncmpDatastoreRequestHandler.cpsNcmpTaskExecutor = spiedCpsTaskExecutor + ncmpDatastoreRequestHandler.notificationFeatureEnabled = NOTIFICATION_ENABLED + ncmpDatastoreRequestHandler.timeOutInMilliSeconds = TIMEOUT_IN_MS + return ncmpDatastoreRequestHandler + } } diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerFactorySpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerFactorySpec.groovy index 7c504981e2..15b3ee6c1e 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerFactorySpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandlerFactorySpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,21 +20,28 @@ package org.onap.cps.ncmp.rest.controller.handlers +import org.spockframework.spring.SpringBean import spock.lang.Specification class NcmpDatastoreRequestHandlerFactorySpec extends Specification { - def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(null, null, null) + @SpringBean + NcmpCachedResourceRequestHandler mockNcmpCachedResourceRequestHandler = new NcmpCachedResourceRequestHandler(null) + + @SpringBean + NcmpPassthroughResourceRequestHandler mockNcmpPassthroughResourceRequestHandler = new NcmpPassthroughResourceRequestHandler() + + def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler) def 'Creating ncmp datastore request handlers.'() { when: 'a ncmp datastore request handler is created for #datastoreType' - def result = objectUnderTest.getNcmpDatastoreResourceRequestHandler(datastoreType) + def result = objectUnderTest.getNcmpResourceRequestHandler(datastoreType) then: 'the result is of the expected class' result.class == expectedClass where: 'the following type of datastore is used' datastoreType || expectedClass - DatastoreType.OPERATIONAL || NcmpDatastoreOperationalResourceRequestHandler - DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpDatastorePassthroughOperationalResourceRequestHandler - DatastoreType.PASSTHROUGH_RUNNING || NcmpDatastorePassthroughRunningResourceRequestHandler + DatastoreType.OPERATIONAL || NcmpCachedResourceRequestHandler + DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpPassthroughResourceRequestHandler + DatastoreType.PASSTHROUGH_RUNNING || NcmpPassthroughResourceRequestHandler } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java index 128eed3f2c..03737bc51b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java @@ -23,10 +23,10 @@ package org.onap.cps.ncmp.api; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; - import java.util.Collection; +import java.util.List; import java.util.Map; +import org.onap.cps.ncmp.api.impl.operations.OperationEnum; import org.onap.cps.ncmp.api.inventory.CompositeState; import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters; @@ -51,50 +51,55 @@ public interface NetworkCmProxyDataService { DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration); /** - * Get resource data for data store pass-through operational - * using dmi. + * Get resource data for given data store using dmi. * - * @param cmHandleId cm handle identifier - * @param resourceIdentifier resource identifier + * @param dataStoreName data store name + * @param cmHandleId cm handle identifier + * @param resourceIdentifier resource identifier * @param optionsParamInQuery options query - * @param topicParamInQuery topic name for (triggering) async responses - * @param requestId unique requestId for async request + * @param topicParamInQuery topic name for (triggering) async responses + * @param requestId unique requestId for async request * @return {@code Object} resource data */ - Object getResourceDataOperationalForCmHandle(String cmHandleId, - String resourceIdentifier, - String optionsParamInQuery, - String topicParamInQuery, - String requestId); + Object getResourceDataForCmHandle(String dataStoreName, + String cmHandleId, + String resourceIdentifier, + String optionsParamInQuery, + String topicParamInQuery, + String requestId); /** * Get resource data for operational. * + * @param dataStoreName data store name * @param cmHandleId cm handle identifier * @param resourceIdentifier resource identifier * @Link FetchDescendantsOption fetch descendants option * @return {@code Object} resource data */ - Object getResourceDataOperational(String cmHandleId, + Object getResourceDataForCmHandle(String dataStoreName, + String cmHandleId, String resourceIdentifier, FetchDescendantsOption fetchDescendantsOption); /** - * Get resource data for data store pass-through running - * using dmi. + * Get resource data for given batch of cm handles using dmi. * - * @param cmHandleId cm handle identifier - * @param resourceIdentifier resource identifier + * @param dataStoreName data store name + * @param cmHandleIds cm handle identifiers + * @param resourceIdentifier resource identifier * @param optionsParamInQuery options query - * @param topicParamInQuery topic name for (triggering) async responses - * @param requestId unique requestId for async request + * @param topicParamInQuery topic name for (triggering) async responses + * @param requestId unique requestId for async request * @return {@code Object} resource data */ - Object getResourceDataPassThroughRunningForCmHandle(String cmHandleId, - String resourceIdentifier, - String optionsParamInQuery, - String topicParamInQuery, - String requestId); + Object getResourceDataForCmHandleBatch(String dataStoreName, + List<String> cmHandleIds, + String resourceIdentifier, + String optionsParamInQuery, + String topicParamInQuery, + String requestId); + /** * Write resource data for data store pass-through running diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java index b3904bd0bb..1b1997f23a 100755 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java @@ -25,7 +25,6 @@ package org.onap.cps.ncmp.api.impl; import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCmHandleQueryParameters; import com.google.common.collect.Lists; @@ -46,7 +45,7 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService; import org.onap.cps.ncmp.api.NetworkCmProxyDataService; import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler; import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; -import org.onap.cps.ncmp.api.impl.operations.DmiOperations; +import org.onap.cps.ncmp.api.impl.operations.OperationEnum; import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions; import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; @@ -115,38 +114,41 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService } @Override - public Object getResourceDataOperationalForCmHandle(final String cmHandleId, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId) { - final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId, + public Object getResourceDataForCmHandle(final String dataStoreName, + final String cmHandleId, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId) { + final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(dataStoreName, cmHandleId, resourceIdentifier, optionsParamInQuery, - DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, - requestId, topicParamInQuery); + topicParamInQuery, + requestId); return responseEntity.getBody(); } @Override - public Object getResourceDataOperational(final String cmHandleId, + public Object getResourceDataForCmHandle(final String dataStoreName, + final String cmHandleId, final String resourceIdentifier, final FetchDescendantsOption fetchDescendantsOption) { - return cpsDataService.getDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier, + return cpsDataService.getDataNodes(dataStoreName, cmHandleId, resourceIdentifier, fetchDescendantsOption).iterator().next(); } @Override - public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId, - final String resourceIdentifier, - final String optionsParamInQuery, - final String topicParamInQuery, - final String requestId) { - final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId, + public Object getResourceDataForCmHandleBatch(final String dataStoreName, + final List<String> cmHandleIds, + final String resourceIdentifier, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId) { + final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(dataStoreName, cmHandleIds, resourceIdentifier, optionsParamInQuery, - DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, - requestId, topicParamInQuery); + topicParamInQuery, + requestId); return responseEntity.getBody(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java index d5b459b025..9d087806c0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/client/DmiRestClient.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ package org.onap.cps.ncmp.api.impl.client; import lombok.AllArgsConstructor; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties; import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException; -import org.onap.cps.ncmp.api.impl.operations.DmiRequestBody; +import org.onap.cps.ncmp.api.impl.operations.OperationEnum; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -43,14 +43,14 @@ public class DmiRestClient { /** * Sends POST operation to DMI with json body containing module references. * @param dmiResourceUrl dmi resource url - * @param jsonData json data body + * @param requestBodyAsJsonString json data body * @param operation the type of operation being executed (for error reporting only) * @return response entity of type String */ public ResponseEntity<Object> postOperationWithJsonData(final String dmiResourceUrl, - final String jsonData, - final DmiRequestBody.OperationEnum operation) { - final var httpEntity = new HttpEntity<>(jsonData, configureHttpHeaders(new HttpHeaders())); + final String requestBodyAsJsonString, + final OperationEnum operation) { + final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders())); try { return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class); } catch (final HttpStatusCodeException httpStatusCodeException) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java index ac2bd45969..ff7afc9eb7 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfig.java @@ -20,14 +20,12 @@ package org.onap.cps.ncmp.api.impl.config.embeddedcache; -import com.hazelcast.config.Config; import com.hazelcast.config.MapConfig; -import com.hazelcast.config.NamedConfig; import com.hazelcast.config.QueueConfig; -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; import java.util.concurrent.BlockingQueue; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.cache.HazelcastCacheConfig; import org.onap.cps.spi.model.DataNode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,13 +33,14 @@ import org.springframework.context.annotation.Configuration; /** * Core infrastructure of the hazelcast distributed caches for Module Sync and Data Sync use cases. */ +@Slf4j @Configuration -public class SynchronizationCacheConfig { +public class SynchronizationCacheConfig extends HazelcastCacheConfig { public static final int MODULE_SYNC_STARTED_TTL_SECS = 600; public static final int DATA_SYNC_SEMAPHORE_TTL_SECS = 1800; - private static final QueueConfig commonQueueConfig = createQueueConfig(); + private static final QueueConfig commonQueueConfig = createQueueConfig("defaultQueueConfig"); private static final MapConfig moduleSyncStartedConfig = createMapConfig("moduleSyncStartedConfig"); private static final MapConfig dataSyncSemaphoresConfig = createMapConfig("dataSyncSemaphoresConfig"); @@ -52,7 +51,8 @@ public class SynchronizationCacheConfig { */ @Bean public BlockingQueue<DataNode> moduleSyncWorkQueue() { - return createHazelcastInstance("moduleSyncWorkQueue", commonQueueConfig) + return createHazelcastInstance("moduleSyncWorkQueue", commonQueueConfig, + "synchronization-caches") .getQueue("moduleSyncWorkQueue"); } @@ -63,7 +63,8 @@ public class SynchronizationCacheConfig { */ @Bean public IMap<String, Object> moduleSyncStartedOnCmHandles() { - return createHazelcastInstance("moduleSyncStartedOnCmHandles", moduleSyncStartedConfig) + return createHazelcastInstance("moduleSyncStartedOnCmHandles", moduleSyncStartedConfig, + "synchronization-caches") .getMap("moduleSyncStartedOnCmHandles"); } @@ -74,39 +75,8 @@ public class SynchronizationCacheConfig { */ @Bean public IMap<String, Boolean> dataSyncSemaphores() { - return createHazelcastInstance("dataSyncSemaphores", dataSyncSemaphoresConfig) + return createHazelcastInstance("dataSyncSemaphores", dataSyncSemaphoresConfig, + "synchronization-caches") .getMap("dataSyncSemaphores"); } - - private HazelcastInstance createHazelcastInstance( - final String hazelcastInstanceName, final NamedConfig namedConfig) { - return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig)); - } - - private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) { - final Config config = new Config(instanceName); - if (namedConfig instanceof MapConfig) { - config.addMapConfig((MapConfig) namedConfig); - } - if (namedConfig instanceof QueueConfig) { - config.addQueueConfig((QueueConfig) namedConfig); - } - config.setClusterName("synchronization-caches"); - return config; - } - - private static QueueConfig createQueueConfig() { - final QueueConfig commonQueueConfig = new QueueConfig("defaultQueueConfig"); - commonQueueConfig.setBackupCount(3); - commonQueueConfig.setAsyncBackupCount(3); - return commonQueueConfig; - } - - private static MapConfig createMapConfig(final String configName) { - final MapConfig mapConfig = new MapConfig(configName); - mapConfig.setBackupCount(3); - mapConfig.setAsyncBackupCount(3); - return mapConfig; - } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfig.java new file mode 100644 index 0000000000..d2c3dc2599 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfig.java @@ -0,0 +1,51 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.event.avc; + +import com.hazelcast.config.MapConfig; +import com.hazelcast.map.IMap; +import java.util.Set; +import org.onap.cps.cache.HazelcastCacheConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Core infrastructure of the hazelcast distributed cache for subscription forward config use cases. + */ +@Configuration +public class ForwardedSubscriptionEventCacheConfig extends HazelcastCacheConfig { + + private static final MapConfig forwardedSubscriptionEventCacheMapConfig = + createMapConfig("forwardedSubscriptionEventCacheMapConfig"); + + /** + * Distributed instance of forwarded subscription information cache that contains subscription event + * id by dmi names as properties. + * + * @return configured map of subscription event ids as keys to sets of dmi names for values + */ + @Bean + public IMap<String, Set<String>> forwardedSubscriptionEventCache() { + return createHazelcastInstance("hazelCastInstanceSubscriptionEvents", + forwardedSubscriptionEventCacheMapConfig, "cps-ncmp-service-caches") + .getMap("forwardedSubscriptionEventCache"); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ResponseTimeoutTask.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ResponseTimeoutTask.java new file mode 100644 index 0000000000..e7edecfacc --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/ResponseTimeoutTask.java @@ -0,0 +1,51 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.event.avc; + +import com.hazelcast.map.IMap; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +public class ResponseTimeoutTask implements Runnable { + + private final IMap<String, Set<String>> forwardedSubscriptionEventCache; + private final String subscriptionEventId; + + @Override + public void run() { + if (forwardedSubscriptionEventCache.containsKey(subscriptionEventId)) { + final Set<String> dmiNames = forwardedSubscriptionEventCache.get(subscriptionEventId); + if (dmiNames.isEmpty()) { + //TODO full outcome response here + log.info("placeholder to create full outcome response for subscriptionEventId: {}.", + subscriptionEventId); + } else { + //TODO partial outcome response here + log.info("placeholder to create partial outcome response for subscriptionEventId: {}.", + subscriptionEventId); + } + forwardedSubscriptionEventCache.remove(subscriptionEventId); + } + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumer.java new file mode 100644 index 0000000000..b332ad1a0e --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumer.java @@ -0,0 +1,82 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.event.avc; + +import com.hazelcast.map.IMap; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.models.SubscriptionEventResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@RequiredArgsConstructor +public class SubscriptionEventResponseConsumer { + + private final IMap<String, Set<String>> forwardedSubscriptionEventCache; + + @Value("${app.ncmp.avc.subscription-outcome-topic}") + private String subscriptionOutcomeEventTopic; + + @Value("${notification.enabled:true}") + private boolean notificationFeatureEnabled; + + @Value("${ncmp.model-loader.subscription:false}") + private boolean subscriptionModelLoaderEnabled; + + /** + * Consume subscription response event. + * + * @param subscriptionEventResponse the event to be consumed + */ + @KafkaListener(topics = "${app.ncmp.avc.subscription-response-topic}", + properties = {"spring.json.value.default.type=org.onap.cps.ncmp.api.models.SubscriptionEventResponse"}) + public void consumeSubscriptionEventResponse(final SubscriptionEventResponse subscriptionEventResponse) { + log.info("subscription event response of clientId: {} is received.", subscriptionEventResponse.getClientId()); + final String subscriptionEventId = subscriptionEventResponse.getClientId() + + subscriptionEventResponse.getSubscriptionName(); + final boolean createOutcomeResponse; + if (forwardedSubscriptionEventCache.containsKey(subscriptionEventId)) { + forwardedSubscriptionEventCache.get(subscriptionEventId).remove(subscriptionEventResponse.getDmiName()); + createOutcomeResponse = forwardedSubscriptionEventCache.get(subscriptionEventId).isEmpty(); + if (createOutcomeResponse) { + forwardedSubscriptionEventCache.remove(subscriptionEventId); + } + } else { + createOutcomeResponse = true; + } + if (subscriptionModelLoaderEnabled) { + updateSubscriptionEvent(subscriptionEventResponse); + } + if (createOutcomeResponse && notificationFeatureEnabled) { + log.info("placeholder to create full outcome response for subscriptionEventId: {}.", subscriptionEventId); + //TODO Create outcome response + } + } + + private void updateSubscriptionEvent(final SubscriptionEventResponse subscriptionEventResponse) { + log.info("placeholder to update persisted subscription for subscriptionEventId: {}.", + subscriptionEventResponse.getClientId() + subscriptionEventResponse.getSubscriptionName()); + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java index c3624b8005..4afa051d30 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarder.java @@ -20,21 +20,28 @@ package org.onap.cps.ncmp.api.impl.events.avcsubscription; +import com.hazelcast.map.IMap; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.impl.event.avc.ResponseTimeoutTask; import org.onap.cps.ncmp.api.impl.events.EventsPublisher; -import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; +import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; import org.onap.cps.ncmp.event.model.SubscriptionEvent; import org.onap.cps.spi.exceptions.OperationNotYetSupportedException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -45,9 +52,15 @@ public class SubscriptionEventForwarder { private final InventoryPersistence inventoryPersistence; private final EventsPublisher<SubscriptionEvent> eventsPublisher; + private final IMap<String, Set<String>> forwardedSubscriptionEventCache; + + private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); private static final String DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX = "ncmp-dmi-cm-avc-subscription-"; + @Value("${ncmp.timers.subscription-forwarding.dmi-response-timeout-ms:30000}") + private int dmiResponseTimeoutInMs; + /** * Forward subscription event. * @@ -56,38 +69,41 @@ public class SubscriptionEventForwarder { public void forwardCreateSubscriptionEvent(final SubscriptionEvent subscriptionEvent) { final List<Object> cmHandleTargets = subscriptionEvent.getEvent().getPredicates().getTargets(); if (cmHandleTargets == null || cmHandleTargets.isEmpty() - || cmHandleTargets.stream().anyMatch(id -> ((String) id).contains("*"))) { + || cmHandleTargets.stream().anyMatch(id -> ((String) id).contains("*"))) { throw new OperationNotYetSupportedException( - "CMHandle targets are required. \"Wildcard\" operations are not yet supported"); + "CMHandle targets are required. \"Wildcard\" operations are not yet supported"); } final List<String> cmHandleTargetsAsStrings = cmHandleTargets.stream().map( - Objects::toString).collect(Collectors.toList()); + Objects::toString).collect(Collectors.toList()); final Collection<YangModelCmHandle> yangModelCmHandles = - inventoryPersistence.getYangModelCmHandles(cmHandleTargetsAsStrings); - final Map<String, Map<String, Map<String, String>>> dmiNameCmHandleMap = - organizeByDmiName(yangModelCmHandles); + inventoryPersistence.getYangModelCmHandles(cmHandleTargetsAsStrings); + + final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName + = DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles); + + final Set<String> dmisToRespond = new HashSet<>(dmiPropertiesPerCmHandleIdPerServiceName.keySet()); + startResponseTimeout(subscriptionEvent, dmisToRespond); + forwardEventToDmis(dmiPropertiesPerCmHandleIdPerServiceName, subscriptionEvent); + } + + private void forwardEventToDmis(final Map<String, Map<String, Map<String, String>>> dmiNameCmHandleMap, + final SubscriptionEvent subscriptionEvent) { dmiNameCmHandleMap.forEach((dmiName, cmHandlePropertiesMap) -> { subscriptionEvent.getEvent().getPredicates().setTargets(Collections.singletonList(cmHandlePropertiesMap)); final String eventKey = createEventKey(subscriptionEvent, dmiName); - eventsPublisher.publishEvent(DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX + dmiName, eventKey, subscriptionEvent); + eventsPublisher.publishEvent( + DMI_AVC_SUBSCRIPTION_TOPIC_PREFIX + dmiName, eventKey, subscriptionEvent); }); } - private Map<String, Map<String, Map<String, String>>> organizeByDmiName( - final Collection<YangModelCmHandle> yangModelCmHandles) { - final Map<String, Map<String, Map<String, String>>> dmiNameCmHandlePropertiesMap = new HashMap<>(); - yangModelCmHandles.forEach(cmHandle -> { - final String dmiName = cmHandle.resolveDmiServiceName(RequiredDmiService.DATA); - if (!dmiNameCmHandlePropertiesMap.containsKey(dmiName)) { - final Map<String, Map<String, String>> cmHandleDmiPropertiesMap = new HashMap<>(); - cmHandleDmiPropertiesMap.put(cmHandle.getId(), dmiPropertiesAsMap(cmHandle)); - dmiNameCmHandlePropertiesMap.put(cmHandle.getDmiDataServiceName(), cmHandleDmiPropertiesMap); - } else { - dmiNameCmHandlePropertiesMap.get(cmHandle.getDmiDataServiceName()) - .put(cmHandle.getId(), dmiPropertiesAsMap(cmHandle)); - } - }); - return dmiNameCmHandlePropertiesMap; + private void startResponseTimeout(final SubscriptionEvent subscriptionEvent, final Set<String> dmisToRespond) { + final String subscriptionEventId = subscriptionEvent.getEvent().getSubscription().getClientID() + + subscriptionEvent.getEvent().getSubscription().getName(); + + forwardedSubscriptionEventCache.put(subscriptionEventId, dmisToRespond); + final ResponseTimeoutTask responseTimeoutTask = + new ResponseTimeoutTask(forwardedSubscriptionEventCache, subscriptionEventId); + executorService.schedule(responseTimeoutTask, dmiResponseTimeoutInMs, TimeUnit.MILLISECONDS); } private String createEventKey(final SubscriptionEvent subscriptionEvent, final String dmiName) { @@ -98,9 +114,4 @@ public class SubscriptionEventForwarder { + dmiName; } - public Map<String, String> dmiPropertiesAsMap(final YangModelCmHandle yangModelCmHandle) { - return yangModelCmHandle.getDmiProperties().stream().collect( - Collectors.toMap(YangModelCmHandle.Property::getName, YangModelCmHandle.Property::getValue)); - } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/executor/TaskExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/executor/TaskExecutor.java new file mode 100644 index 0000000000..192062fde5 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/executor/TaskExecutor.java @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.executor; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TaskExecutor { + + /** + * Execute task asynchronously. + * + * @param taskSupplier functional method is get() task need to executed asynchronously + * @param timeOutInMillis the timeout value in milliseconds + */ + public static CompletableFuture<Object> executeTask(final Supplier<Object> taskSupplier, + final long timeOutInMillis) { + return CompletableFuture.supplyAsync(taskSupplier::get) + .orTimeout(timeOutInMillis, MILLISECONDS); + } +} + + + diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DataStoreEnum.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DataStoreEnum.java new file mode 100644 index 0000000000..24edc73f3c --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DataStoreEnum.java @@ -0,0 +1,34 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operations; + +import lombok.Getter; + +@Getter +public enum DataStoreEnum { + PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"), + PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"); + private final String value; + + DataStoreEnum(final String value) { + this.value = value; + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java index 83faa005f0..d648352f15 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,19 +21,24 @@ package org.onap.cps.ncmp.api.impl.operations; -import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum; -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ; +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING; +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ; +import java.util.Collection; +import java.util.List; +import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; +import org.onap.cps.ncmp.api.impl.executor.TaskExecutor; +import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer; import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CmHandleState; import org.onap.cps.ncmp.api.inventory.InventoryPersistence; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.utils.JsonObjectMapper; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; @@ -44,15 +49,14 @@ import org.springframework.stereotype.Component; @Slf4j public class DmiDataOperations extends DmiOperations { - /** - * Constructor for {@code DmiOperations}. This method also manipulates url properties. - * - * @param dmiRestClient {@code DmiRestClient} - */ + private static final long DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS = 30000L; + private static final String NO_CM_HANDLE_ID = ""; + public DmiDataOperations(final InventoryPersistence inventoryPersistence, final JsonObjectMapper jsonObjectMapper, final NcmpConfiguration.DmiProperties dmiProperties, - final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) { + final DmiRestClient dmiRestClient, + final DmiServiceUrlBuilder dmiServiceUrlBuilder) { super(inventoryPersistence, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder); } @@ -60,48 +64,78 @@ public class DmiDataOperations extends DmiOperations { * This method fetches the resource data from operational data store for given cm handle * identifier on given resource using dmi client. * - * @param cmHandleId network resource identifier - * @param resourceId resource identifier + * @param dataStoreName name of data store + * @param cmHandleId network resource identifier + * @param resourceId resource identifier * @param optionsParamInQuery options query - * @param dataStore data store enum - * @param requestId requestId for async responses * @param topicParamInQuery topic name for (triggering) async responses + * @param requestId requestId for async responses * @return {@code ResponseEntity} response entity */ - public ResponseEntity<Object> getResourceDataFromDmi(final String cmHandleId, + public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName, + final String cmHandleId, final String resourceId, final String optionsParamInQuery, - final DataStoreEnum dataStore, - final String requestId, - final String topicParamInQuery) { + final String topicParamInQuery, + final String requestId) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); - final String dmiResourceDataUrl = getDmiRequestUrl(cmHandleId, resourceId, optionsParamInQuery, dataStore, - topicParamInQuery, yangModelCmHandle); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); - isCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, READ); + validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); + final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, + yangModelCmHandle); + final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, resourceId, optionsParamInQuery, + topicParamInQuery, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); + return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ); + } + + /** + * This method fetches the resource data by data store for given list of cm handles using dmi client. + * + * @param dataStoreName data store name + * @param cmHandleIds list of cm handles + * @param resourceId resource identifier + * @param optionsParamInQuery options query + * @param topicParamInQuery topic name for (triggering) async responses + * @param requestId requestId for async responses + * @return {@code ResponseEntity} response entity + */ + public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName, + final List<String> cmHandleIds, + final String resourceId, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId) { + final Collection<YangModelCmHandle> yangModelCmHandles + = inventoryPersistence.getYangModelCmHandles(cmHandleIds); + final Map<String, Map<String, Map<String, String>>> dmiServiceNameCmHandlePropertiesMap = + DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles); + + buildBulkResourceDataRequestAndSend(dataStoreName, resourceId, optionsParamInQuery, + topicParamInQuery, requestId, dmiServiceNameCmHandlePropertiesMap); + return new ResponseEntity<>(HttpStatus.ACCEPTED); } /** * This method fetches all the resource data from operational data store for given cm handle * identifier using dmi client. * + * @param dataStoreName data store name * @param cmHandleId network resource identifier - * @param dataStore data store enum * @param requestId requestId for async responses * @return {@code ResponseEntity} response entity */ - public ResponseEntity<Object> getResourceDataFromDmi(final String cmHandleId, - final DataStoreEnum dataStore, + public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName, + final String cmHandleId, final String requestId) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonBody = getDmiRequestBody(READ, requestId, null, null, yangModelCmHandle); - final String dmiResourceDataUrl = getDmiRequestUrl(cmHandleId, "/", null, dataStore, - null, yangModelCmHandle); + final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null, + yangModelCmHandle); + final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/", null, + null, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); - isCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, READ); + validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); + return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, + READ); } /** @@ -121,12 +155,14 @@ public class DmiDataOperations extends DmiOperations { final String requestData, final String dataType) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); - final String jsonBody = getDmiRequestBody(operation, null, requestData, dataType, yangModelCmHandle); - final String dmiUrl = getDmiRequestUrl(cmHandleId, resourceId, null, PASSTHROUGH_RUNNING, - null, yangModelCmHandle); + final String jsonRequestBody = getDmiRequestBody(operation, null, requestData, dataType, + yangModelCmHandle); + final String dmiUrl = getDmiRequestUrl(PASSTHROUGH_RUNNING.getValue(), cmHandleId, resourceId, + null, null, + yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA)); final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); - isCmHandleStateReady(yangModelCmHandle, cmHandleState); - return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody, operation); + validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); + return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operation); } private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) { @@ -145,24 +181,80 @@ public class DmiDataOperations extends DmiOperations { return jsonObjectMapper.asJsonString(dmiRequestBody); } - private String getDmiRequestUrl(final String cmHandleId, - final String resourceId, - final String optionsParamInQuery, - final DataStoreEnum dataStore, - final String topicParamInQuery, - final YangModelCmHandle yangModelCmHandle) { + private String getDmiBulkRequestBody(final OperationEnum operation, + final String requestId, + final String requestData) { + final DmiRequestBody dmiBulkRequestBody = DmiRequestBody.builder() + .operation(operation) + .requestId(requestId) + .data(requestData) + .build(); + return jsonObjectMapper.asJsonString(dmiBulkRequestBody); + } + + private String getDmiRequestUrl(final String dataStoreName, + final String cmHandleId, + final String resourceId, + final String optionsParamInQuery, + final String topicParamInQuery, + final String dmiServiceName) { return dmiServiceUrlBuilder.getDmiDatastoreUrl( dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, - topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables( - yangModelCmHandle, cmHandleId, dataStore)); + topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName, + cmHandleId)); } - private void isCmHandleStateReady(final YangModelCmHandle yangModelCmHandle, final CmHandleState cmHandleState) { + private String getDmiServiceBulkRequestUrl(final String dataStoreName, + final String resourceId, + final String optionsParamInQuery, + final String topicParamInQuery, + final String dmiServiceName) { + return dmiServiceUrlBuilder.getBulkRequestUrl( + dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery, + topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName, + NO_CM_HANDLE_ID)); + } + + private void validateIfCmHandleStateReady(final YangModelCmHandle yangModelCmHandle, + final CmHandleState cmHandleState) { if (cmHandleState != CmHandleState.READY) { throw new CpsException("State mismatch exception.", "Cm-Handle not in READY state. " - + "cm handle state is " - + yangModelCmHandle.getCompositeState().getCmHandleState()); + + "cm handle state is " + + yangModelCmHandle.getCompositeState().getCmHandleState()); } } + private void buildBulkResourceDataRequestAndSend(final String dataStoreName, + final String resourceId, + final String optionsParamInQuery, + final String topicParamInQuery, + final String requestId, + final Map<String, Map<String, Map<String, String>>> + dmiServiceNameCmHandlePropertiesMap) { + dmiServiceNameCmHandlePropertiesMap.entrySet().parallelStream().forEach( + dmiServiceNameCmHandlePropertiesEntry -> { + final String dmiBulkResourceDataUrl = getDmiServiceBulkRequestUrl(dataStoreName, resourceId, + optionsParamInQuery, topicParamInQuery, dmiServiceNameCmHandlePropertiesEntry.getKey()); + final String jsonRequestBodyAsJsonString = + jsonObjectMapper.asJsonString(dmiServiceNameCmHandlePropertiesEntry.getValue()); + final String jsonRequestBody + = getDmiBulkRequestBody(READ, requestId, jsonRequestBodyAsJsonString); + sendDmiResourceDataRequestToDmiService(dmiBulkResourceDataUrl, jsonRequestBody); + }); + } + + private void sendDmiResourceDataRequestToDmiService(final String dmiBulkResourceDataUrl, + final String dmiResourceDataRequestAsJsonString) { + TaskExecutor.executeTask(() -> + dmiRestClient.postOperationWithJsonData(dmiBulkResourceDataUrl, + dmiResourceDataRequestAsJsonString, READ), + DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS) + .whenCompleteAsync(this::handleTaskCompletion); + } + + private void handleTaskCompletion(final Object response, final Throwable throwable) { + // TODO Need to publish an error response to client given topic. + // Code should be implemented into https://jira.onap.org/browse/CPS-1558 ( + // NCMP : Handle non responding DMI-Plugin) + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java index d8d03041fb..392e9c1a24 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -98,17 +98,18 @@ public class DmiModelOperations extends DmiOperations { * Get resources from DMI for modules. * * @param dmiServiceName dmi service name - * @param jsonData module names and revisions as JSON + * @param jsonRequestBody module names and revisions as JSON * @param cmHandle cmHandle * @param resourceName name of the resource(s) * @return {@code ResponseEntity} response entity */ private ResponseEntity<Object> getResourceFromDmiWithJsonData(final String dmiServiceName, - final String jsonData, + final String jsonRequestBody, final String cmHandle, final String resourceName) { final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName); - return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonData, DmiRequestBody.OperationEnum.READ); + return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, + OperationEnum.READ); } private static String getRequestBodyToFetchYangResources(final Collection<ModuleReference> newModuleReferences, diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java index e26ffef870..7e9079ec94 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,6 @@ package org.onap.cps.ncmp.api.impl.operations; -import lombok.Getter; import lombok.RequiredArgsConstructor; import org.onap.cps.ncmp.api.impl.client.DmiRestClient; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; @@ -34,17 +33,6 @@ import org.springframework.stereotype.Service; @Service public class DmiOperations { - @Getter - public enum DataStoreEnum { - PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"), - PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"); - private final String value; - - DataStoreEnum(final String value) { - this.value = value; - } - } - protected final InventoryPersistence inventoryPersistence; protected final JsonObjectMapper jsonObjectMapper; protected final NcmpConfiguration.DmiProperties dmiProperties; @@ -52,7 +40,7 @@ public class DmiOperations { protected final DmiServiceUrlBuilder dmiServiceUrlBuilder; String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) { - return dmiServiceUrlBuilder.getCmHandleUrl() + return dmiServiceUrlBuilder.getResourceDataBasePathUriBuilder() .pathSegment("{resourceName}") .buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString(); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java index c84e4cb870..3aa6366155 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java @@ -22,7 +22,6 @@ package org.onap.cps.ncmp.api.impl.operations; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonValue; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -34,24 +33,6 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; @Getter @Builder public class DmiRequestBody { - public enum OperationEnum { - READ("read"), - CREATE("create"), - UPDATE("update"), - PATCH("patch"), - DELETE("delete"); - private final String value; - - OperationEnum(final String value) { - this.value = value; - } - - @Override - @JsonValue - public String toString() { - return String.valueOf(value); - } - } private OperationEnum operation; private String dataType; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java new file mode 100644 index 0000000000..796cef23d0 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java @@ -0,0 +1,43 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.operations; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum OperationEnum { + + READ("read"), + CREATE("create"), + UPDATE("update"), + PATCH("patch"), + DELETE("delete"); + private final String value; + + OperationEnum(final String value) { + this.value = value; + } + + @Override + @JsonValue + public String toString() { + return String.valueOf(value); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java new file mode 100644 index 0000000000..26e94866a1 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceNameOrganizer.java @@ -0,0 +1,64 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.utils; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService; +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class DmiServiceNameOrganizer { + + /** + * organizes a map with dmi service name as key for cm handle with its properties. + * + * @param yangModelCmHandles list of cm handle model + */ + public static Map<String, Map<String, Map<String, String>>> getDmiPropertiesPerCmHandleIdPerServiceName( + final Collection<YangModelCmHandle> yangModelCmHandles) { + final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName + = new HashMap<>(); + yangModelCmHandles.forEach(yangModelCmHandle -> { + final String dmiServiceName = yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA); + if (!dmiPropertiesPerCmHandleIdPerServiceName.containsKey(dmiServiceName)) { + final Map<String, Map<String, String>> cmHandleDmiPropertiesMap = new HashMap<>(); + cmHandleDmiPropertiesMap.put(yangModelCmHandle.getId(), + dmiPropertiesAsMap(yangModelCmHandle.getDmiProperties())); + dmiPropertiesPerCmHandleIdPerServiceName.put(dmiServiceName, cmHandleDmiPropertiesMap); + } else { + dmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName) + .put(yangModelCmHandle.getId(), dmiPropertiesAsMap(yangModelCmHandle.getDmiProperties())); + } + }); + return dmiPropertiesPerCmHandleIdPerServiceName; + } + + private static Map<String, String> dmiPropertiesAsMap(final List<YangModelCmHandle.Property> dmiProperties) { + return dmiProperties.stream().collect( + Collectors.toMap(YangModelCmHandle.Property::getName, YangModelCmHandle.Property::getValue)); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java index 5f4a6540c2..bba8f48fbd 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +20,12 @@ package org.onap.cps.ncmp.api.impl.utils; -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA; - import java.util.HashMap; import java.util.Map; import lombok.RequiredArgsConstructor; import org.apache.logging.log4j.util.Strings; import org.apache.logging.log4j.util.TriConsumer; import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration; -import org.onap.cps.ncmp.api.impl.operations.DmiOperations; -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.spi.utils.CpsValidator; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; @@ -52,13 +48,21 @@ public class DmiServiceUrlBuilder { */ public String getDmiDatastoreUrl(final MultiValueMap<String, String> queryParams, final Map<String, Object> uriVariables) { - final UriComponentsBuilder uriComponentsBuilder = getCmHandleUrl() - .pathSegment("data") - .pathSegment("ds") - .pathSegment("{dataStore}") - .queryParams(queryParams) - .uriVariables(uriVariables); - return uriComponentsBuilder.buildAndExpand().toUriString(); + return getUriComponentsBuilder(getResourceDataBasePathUriBuilder(), queryParams, uriVariables) + .buildAndExpand().toUriString(); + } + + /** + * This method creates the dmi service url for bulk request. + * + * @param queryParams query param map as key,value pair + * @param uriVariables uri param map as key (placeholder),value pair + * @return {@code String} dmi service url as string + */ + public String getBulkRequestUrl(final MultiValueMap<String, String> queryParams, + final Map<String, Object> uriVariables) { + return getUriComponentsBuilder(getBulkResourceDataBasePathUriBuilder(), queryParams, uriVariables) + .buildAndExpand().toUriString(); } /** @@ -66,7 +70,7 @@ public class DmiServiceUrlBuilder { * * @return {@code UriComponentsBuilder} dmi service url builder object */ - public UriComponentsBuilder getCmHandleUrl() { + public UriComponentsBuilder getResourceDataBasePathUriBuilder() { return UriComponentsBuilder.newInstance() .path("{dmiServiceName}") .pathSegment("{dmiBasePath}") @@ -76,23 +80,36 @@ public class DmiServiceUrlBuilder { } /** + * This method creates the dmi service url builder object with path variables for batch of cm handles. + * + * @return {@code UriComponentsBuilder} dmi service url builder object + */ + public UriComponentsBuilder getBulkResourceDataBasePathUriBuilder() { + return UriComponentsBuilder.newInstance() + .path("{dmiServiceName}") + .pathSegment("{dmiBasePath}") + .pathSegment("v1") + .pathSegment("batch"); + } + + /** * This method populates uri variables. * - * @param yangModelCmHandle get dmi service name + * @param dataStoreName data store name + * @param dmiServiceName dmi service name * @param cmHandleId cm handle id for dmi registration * @return {@code String} dmi service url as string */ - public Map<String, Object> populateUriVariables(final YangModelCmHandle yangModelCmHandle, - final String cmHandleId, - final DmiOperations.DataStoreEnum dataStore) { + public Map<String, Object> populateUriVariables(final String dataStoreName, + final String dmiServiceName, + final String cmHandleId) { cpsValidator.validateNameCharacters(cmHandleId); final Map<String, Object> uriVariables = new HashMap<>(); final String dmiBasePath = dmiProperties.getDmiBasePath(); - uriVariables.put("dmiServiceName", - yangModelCmHandle.resolveDmiServiceName(DATA)); + uriVariables.put("dmiServiceName", dmiServiceName); uriVariables.put("dmiBasePath", dmiBasePath); uriVariables.put("cmHandleId", cmHandleId); - uriVariables.put("dataStore", dataStore.getValue()); + uriVariables.put("dataStore", dataStoreName); return uriVariables; } @@ -124,4 +141,15 @@ public class DmiServiceUrlBuilder { } }; } + + private UriComponentsBuilder getUriComponentsBuilder(final UriComponentsBuilder uriComponentsBuilder, + final MultiValueMap<String, String> queryParams, + final Map<String, Object> uriVariables) { + return uriComponentsBuilder + .pathSegment("data") + .pathSegment("ds") + .pathSegment("{dataStore}") + .queryParams(queryParams) + .uriVariables(uriVariables); + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java index 537f50122c..b9cecfb3d0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/SyncUtils.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,8 @@ package org.onap.cps.ncmp.api.inventory.sync; +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL; + import com.fasterxml.jackson.databind.JsonNode; import java.time.Duration; import java.time.OffsetDateTime; @@ -37,7 +39,6 @@ import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations; -import org.onap.cps.ncmp.api.impl.operations.DmiOperations; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle; import org.onap.cps.ncmp.api.inventory.CmHandleQueries; @@ -167,7 +168,8 @@ public class SyncUtils { */ public String getResourceData(final String cmHandleId) { final ResponseEntity<Object> resourceDataResponseEntity = dmiDataOperations.getResourceDataFromDmi( - cmHandleId, DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, + PASSTHROUGH_OPERATIONAL.getValue(), + cmHandleId, UUID.randomUUID().toString()); if (resourceDataResponseEntity.getStatusCode().is2xxSuccessful()) { return getFirstResource(resourceDataResponseEntity.getBody()); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java new file mode 100644 index 0000000000..95e773c8c9 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/SubscriptionEventResponse.java @@ -0,0 +1,37 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.models; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; +import org.onap.cps.ncmp.api.impl.subscriptions.SubscriptionStatus; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Getter +@Setter +public class SubscriptionEventResponse { + private String clientId; + private String subscriptionName; + private String dmiName; + private Map<String, SubscriptionStatus> cmHandleIdToStatus; +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java index 231ba75b5d..659779acf9 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/SubscriptionModelLoader.java @@ -31,6 +31,7 @@ import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException; import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.model.Dataspace; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; @@ -51,6 +52,12 @@ public class SubscriptionModelLoader implements ModelLoader { private static final String SUBSCRIPTION_SCHEMASET_NAME = "subscriptions"; private static final String SUBSCRIPTION_REGISTRY_DATANODE_NAME = "subscription-registry"; + @Value("${ncmp.model-loader.maximum-attempt-count:20}") + private int maximumAttemptCount; + + @Value("${ncmp.timers.model-loader.retry-time-ms:1000}") + private long retryTimeMs; + @Value("${ncmp.model-loader.subscription:false}") private boolean subscriptionModelLoaderEnabled; @@ -63,6 +70,7 @@ public class SubscriptionModelLoader implements ModelLoader { public void onApplicationEvent(final ApplicationReadyEvent applicationReadyEvent) { try { if (subscriptionModelLoaderEnabled) { + checkNcmpDataspaceExists(); onboardSubscriptionModel(createYangResourceToContentMap()); } else { log.info("Subscription Model Loader is disabled"); @@ -73,6 +81,29 @@ public class SubscriptionModelLoader implements ModelLoader { } } + private void checkNcmpDataspaceExists() { + boolean ncmpDataspaceExists = false; + int attemptCount = 0; + while (!ncmpDataspaceExists) { + final Dataspace ncmpDataspace = cpsAdminService.getDataspace(SUBSCRIPTION_DATASPACE_NAME); + if (ncmpDataspace != null) { + ncmpDataspaceExists = true; + } + if (attemptCount < maximumAttemptCount) { + try { + Thread.sleep(attemptCount * retryTimeMs); + attemptCount++; + log.info("Retrieving NCMP dataspace... {} attempt(s) ", attemptCount); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } else { + throw new NcmpStartUpException("Retrieval of NCMP dataspace fails", + "NCMP dataspace does not exist"); + } + } + } + /** * Method to onboard subscription model for NCMP. */ @@ -108,7 +139,7 @@ public class SubscriptionModelLoader implements ModelLoader { */ @Override public boolean createAnchor(final String dataspaceName, final String schemaSetName, - final String anchorName) { + final String anchorName) { try { cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName); } catch (final AlreadyDefinedException exception) { diff --git a/cps-ncmp-service/src/main/resources/model/subscription.yang b/cps-ncmp-service/src/main/resources/model/subscription.yang index 8ae1be6646..e332a2898a 100644 --- a/cps-ncmp-service/src/main/resources/model/subscription.yang +++ b/cps-ncmp-service/src/main/resources/model/subscription.yang @@ -4,7 +4,7 @@ module subscription { prefix subs; - revision "2023-21-03" { + revision "2023-03-21" { description "NCMP subscription model"; } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy index 871af842ea..5b49e04635 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy @@ -52,10 +52,10 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE class NetworkCmProxyDataServiceImplSpec extends Specification { @@ -94,61 +94,64 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { def 'Write resource data for pass-through running from DMI using POST.'() { given: 'cpsDataService returns valid datanode' - mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', - cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode + mockDataNode() when: 'write resource data is called' objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle', 'testResourceId', CREATE, '{some-json}', 'application/json') then: 'DMI called with correct data' 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', - CREATE, '{some-json}', 'application/json') + CREATE, '{some-json}', 'application/json') >> { new ResponseEntity<>(HttpStatus.CREATED) } } def 'Get resource data for pass-through operational from DMI.'() { given: 'get data node is called' - mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', - cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode + mockDataNode() and: 'get resource data from DMI is called' - mockDmiDataOperations.getResourceDataFromDmi( - 'testCmHandle', - 'testResourceId', - OPTIONS_PARAM, - PASSTHROUGH_OPERATIONAL, - NO_REQUEST_ID, - NO_TOPIC) >> new ResponseEntity<>('dmi-response', HttpStatus.OK) + mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.value,'testCmHandle', + 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >> + new ResponseEntity<>('dmi-response', HttpStatus.OK) when: 'get resource data operational for cm-handle is called' - def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle', - 'testResourceId', - OPTIONS_PARAM, - NO_TOPIC, - NO_REQUEST_ID) + def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.value, 'testCmHandle', + 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) then: 'DMI returns a json response' response == 'dmi-response' } def 'Get resource data for pass-through running from DMI.'() { given: 'cpsDataService returns valid data node' - mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', - cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode + mockDataNode() and: 'DMI returns valid response and data' - mockDmiDataOperations.getResourceDataFromDmi('testCmHandle', - 'testResourceId', - OPTIONS_PARAM, - PASSTHROUGH_RUNNING, - NO_REQUEST_ID, - NO_TOPIC) >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK) + mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.value, 'testCmHandle', + 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >> + new ResponseEntity<>('{dmi-response}', HttpStatus.OK) when: 'get resource data is called' - def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle', - 'testResourceId', - OPTIONS_PARAM, - NO_TOPIC, - NO_REQUEST_ID) + def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.value, 'testCmHandle', + 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) then: 'get resource data returns expected response' response == '{dmi-response}' } + def 'Get bulk resource data for #datastoreName from DMI.'() { + given: 'cpsDataService returns valid data node' + mockDataNode() + and: 'DMI returns valid response and data' + mockDmiDataOperations.getResourceDataFromDmi(datastoreName, ['testCmHandle'], + 'testResourceId', OPTIONS_PARAM,'some topic','requestId') >> + new ResponseEntity<>('{dmi-bulk-response}', HttpStatus.OK) + when: 'get batch resource data is called' + def response = objectUnderTest.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'], + 'testResourceId', + OPTIONS_PARAM, + 'some topic', + 'requestId') + then: 'get bulk resource data returns expected response' + response == '{dmi-bulk-response}' + where: 'the following data stores are used' + datastoreName << [PASSTHROUGH_RUNNING.value, PASSTHROUGH_OPERATIONAL.value] + } + def 'Getting Yang Resources.'() { when: 'yang resources is called' objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') @@ -242,7 +245,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { '{some-json}', 'application/json') then: 'DMI called with correct data' 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId', - UPDATE, '{some-json}', 'application/json') + UPDATE, '{some-json}', 'application/json') >> { new ResponseEntity<>(HttpStatus.OK) } } @@ -365,4 +368,9 @@ class NetworkCmProxyDataServiceImplSpec extends Specification { .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) .lastSyncTime('some-timestamp').build()).build() } + + def mockDataNode() { + mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', + cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy index 90839f8ac0..b38ca10f7b 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,10 +34,9 @@ import org.springframework.web.client.HttpServerErrorException import org.springframework.web.client.RestTemplate import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE - +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE @SpringBootTest @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiRestClient]) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy index 868ee7a705..567debdded 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================== - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ package org.onap.cps.ncmp.api.impl.config.embeddedcache +import com.hazelcast.config.Config import com.hazelcast.core.Hazelcast import com.hazelcast.map.IMap import org.onap.cps.spi.model.DataNode @@ -50,8 +51,8 @@ class SynchronizationCacheConfigSpec extends Specification { assert null != moduleSyncStartedOnCmHandles and: 'system is able to create an instance of a map to hold data sync semaphores' assert null != dataSyncSemaphores - and: 'there 3 instances' - assert Hazelcast.allHazelcastInstances.size() == 3 + and: 'there are at least 3 instances' + assert Hazelcast.allHazelcastInstances.size() > 2 and: 'they have the correct names (in any order)' assert Hazelcast.allHazelcastInstances.name.containsAll('moduleSyncWorkQueue', 'moduleSyncStartedOnCmHandles', 'dataSyncSemaphores' ) } @@ -74,6 +75,40 @@ class SynchronizationCacheConfigSpec extends Specification { assert dataSyncSemaphoresConfig.asyncBackupCount == 3 } + def 'Verify deployment network configs for Distributed objects'() { + given: 'the Module Sync Work Queue config' + def queueNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncWorkQueue').config.networkConfig + and: 'the Module Sync Started Cm Handle Map config' + def moduleSyncStartedOnCmHandlesNetworkConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncStartedOnCmHandles').config.networkConfig + and: 'the Data Sync Semaphores Map config' + def dataSyncSemaphoresNetworkConfig = Hazelcast.getHazelcastInstanceByName('dataSyncSemaphores').config.networkConfig + expect: 'system created instance with correct config of Module Sync Work Queue' + assert queueNetworkConfig.join.autoDetectionConfig.enabled + assert !queueNetworkConfig.join.kubernetesConfig.enabled + and: 'Module Sync Started Cm Handle Map has the correct settings' + assert moduleSyncStartedOnCmHandlesNetworkConfig.join.autoDetectionConfig.enabled + assert !moduleSyncStartedOnCmHandlesNetworkConfig.join.kubernetesConfig.enabled + and: 'Data Sync Semaphore Map has the correct settings' + assert dataSyncSemaphoresNetworkConfig.join.autoDetectionConfig.enabled + assert !dataSyncSemaphoresNetworkConfig.join.kubernetesConfig.enabled + + } + + def 'Verify network config'() { + given: 'Synchronization config object and test configuration' + def objectUnderTest = new SynchronizationCacheConfig() + def testConfig = new Config() + when: 'kubernetes properties are enabled' + objectUnderTest.cacheKubernetesEnabled = true + objectUnderTest.cacheKubernetesServiceName = 'test-service-name' + and: 'method called to update the discovery mode' + objectUnderTest.updateDiscoveryMode(testConfig) + then: 'applied properties are reflected' + assert testConfig.networkConfig.join.kubernetesConfig.enabled + assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name' + + } + def 'Time to Live Verify for Module Sync Semaphore'() { when: 'the key is inserted with a TTL of 1 second (Hazelcast TTL resolution is seconds!)' moduleSyncStartedOnCmHandles.put('testKeyModuleSync', 'toBeExpired' as Object, 1, TimeUnit.SECONDS) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfigSpec.groovy new file mode 100644 index 0000000000..7448daf598 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/ForwardedSubscriptionEventCacheConfigSpec.groovy @@ -0,0 +1,75 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.event.avc + +import com.hazelcast.config.Config +import com.hazelcast.core.Hazelcast +import com.hazelcast.map.IMap +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import spock.lang.Specification + +@SpringBootTest(classes = [ForwardedSubscriptionEventCacheConfig]) +class ForwardedSubscriptionEventCacheConfigSpec extends Specification { + + @Autowired + private IMap<String, Set<String>> forwardedSubscriptionEventCache + + def 'Embedded (hazelcast) cache for Forwarded Subscription Event Cache.'() { + expect: 'system is able to create an instance of the Forwarded Subscription Event Cache' + assert null != forwardedSubscriptionEventCache + and: 'there is at least 1 instance' + assert Hazelcast.allHazelcastInstances.size() > 0 + and: 'Forwarded Subscription Event Cache is present' + assert Hazelcast.allHazelcastInstances.name.contains('hazelCastInstanceSubscriptionEvents') + } + + def 'Verify configs for Distributed Caches'(){ + given: 'the Forwarded Subscription Event Cache config' + def forwardedSubscriptionEventCacheConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceSubscriptionEvents').config.mapConfigs.get('forwardedSubscriptionEventCacheMapConfig') + expect: 'system created instance with correct config' + assert forwardedSubscriptionEventCacheConfig.backupCount == 3 + assert forwardedSubscriptionEventCacheConfig.asyncBackupCount == 3 + } + + def 'Verify deployment network configs for Distributed Caches'() { + given: 'the Forwarded Subscription Event Cache config' + def forwardedSubscriptionEventCacheNetworkConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceSubscriptionEvents').config.networkConfig + expect: 'system created instance with correct config' + assert forwardedSubscriptionEventCacheNetworkConfig.join.autoDetectionConfig.enabled + assert !forwardedSubscriptionEventCacheNetworkConfig.join.kubernetesConfig.enabled + } + + def 'Verify network config'() { + given: 'Synchronization config object and test configuration' + def objectUnderTest = new ForwardedSubscriptionEventCacheConfig() + def testConfig = new Config() + when: 'kubernetes properties are enabled' + objectUnderTest.cacheKubernetesEnabled = true + objectUnderTest.cacheKubernetesServiceName = 'test-service-name' + and: 'method called to update the discovery mode' + objectUnderTest.updateDiscoveryMode(testConfig) + then: 'applied properties are reflected' + assert testConfig.networkConfig.join.kubernetesConfig.enabled + assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name' + + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumerSpec.groovy new file mode 100644 index 0000000000..a673462008 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/event/avc/SubscriptionEventResponseConsumerSpec.groovy @@ -0,0 +1,79 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (c) 2023 Nordix Foundation. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.impl.event.avc + +import com.fasterxml.jackson.databind.ObjectMapper +import com.hazelcast.map.IMap +import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec +import org.onap.cps.ncmp.api.models.SubscriptionEventResponse +import org.onap.cps.utils.JsonObjectMapper +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) +class SubscriptionEventResponseConsumerSpec extends MessagingBaseSpec { + + IMap<String, Set<String>> mockForwardedSubscriptionEventCache = Mock(IMap<String, Set<String>>) + + def objectUnderTest = new SubscriptionEventResponseConsumer(mockForwardedSubscriptionEventCache) + + + def 'Consume Subscription Event Response where all DMIs have responded'() { + given: 'a subscription event response with a clientId, subscriptionName and dmiName' + def testEventReceived = new SubscriptionEventResponse() + testEventReceived.clientId = 'some-client-id' + testEventReceived.subscriptionName = 'some-subscription-name' + testEventReceived.dmiName = 'some-dmi-name' + and: 'notifications are enabled' + objectUnderTest.notificationFeatureEnabled = true + and: 'subscription model loader is enabled' + objectUnderTest.subscriptionModelLoaderEnabled = true + when: 'the valid event is consumed' + objectUnderTest.consumeSubscriptionEventResponse(testEventReceived) + then: 'the forwarded subscription event cache returns only the received dmiName existing for the subscription create event' + 1 * mockForwardedSubscriptionEventCache.containsKey('some-client-idsome-subscription-name') >> true + 1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> (['some-dmi-name'] as Set) + and: 'the forwarded subscription event cache returns an empty Map when the dmiName has been removed' + 1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> ([] as Set) + and: 'the subscription event is removed from the map' + 1 * mockForwardedSubscriptionEventCache.remove('some-client-idsome-subscription-name') + } + + def 'Consume Subscription Event Response where another DMI has not yet responded'() { + given: 'a subscription event response with a clientId, subscriptionName and dmiName' + def testEventReceived = new SubscriptionEventResponse() + testEventReceived.clientId = 'some-client-id' + testEventReceived.subscriptionName = 'some-subscription-name' + testEventReceived.dmiName = 'some-dmi-name' + and: 'notifications are enabled' + objectUnderTest.notificationFeatureEnabled = true + and: 'subscription model loader is enabled' + objectUnderTest.subscriptionModelLoaderEnabled = true + when: 'the valid event is consumed' + objectUnderTest.consumeSubscriptionEventResponse(testEventReceived) + then: 'the forwarded subscription event cache returns only the received dmiName existing for the subscription create event' + 1 * mockForwardedSubscriptionEventCache.containsKey('some-client-idsome-subscription-name') >> true + 1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> (['some-dmi-name', 'non-responded-dmi'] as Set) + and: 'the forwarded subscription event cache returns an empty Map when the dmiName has been removed' + 1 * mockForwardedSubscriptionEventCache.get('some-client-idsome-subscription-name') >> (['non-responded-dmi'] as Set) + and: 'the subscription event is not removed from the map' + 0 * mockForwardedSubscriptionEventCache.remove(_) + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/SubscriptionEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/SubscriptionEventMapperSpec.groovy index 3a7aa481c3..f2ff1f7b23 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/SubscriptionEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avc/SubscriptionEventMapperSpec.groovy @@ -48,7 +48,7 @@ class SubscriptionEventMapperSpec extends Specification { def result = objectUnderTest.toYangModelSubscriptionEvent(testEventToMap) then: 'the resulting yang model subscription event contains the correct clientId' assert result.clientId == "SCO-9989752" - and: 'client name' + and: 'subscription name' assert result.subscriptionName == "cm-subscription-001" and: 'is tagged value is false' assert !result.isTagged @@ -60,4 +60,21 @@ class SubscriptionEventMapperSpec extends Specification { assert result.topic == null } + def 'Map null subscription event to yang model subscription event where #scenario'() { + given: 'a new Subscription Event with no data' + def testEventToMap = new SubscriptionEvent() + when: 'the event is mapped to a yang model subscription' + def result = objectUnderTest.toYangModelSubscriptionEvent(testEventToMap) + then: 'the resulting yang model subscription event contains null clientId' + assert result.clientId == null + and: 'subscription name is null' + assert result.subscriptionName == null + and: 'is tagged value is false' + assert result.isTagged == false + and: 'predicates is null' + assert result.predicates == null + and: 'the topic is null' + assert result.topic == null + } + }
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy index 2b0adf3420..457eb6fa99 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/avcsubscription/SubscriptionEventForwarderSpec.groovy @@ -21,6 +21,7 @@ package org.onap.cps.ncmp.api.impl.events.avcsubscription import com.fasterxml.jackson.databind.ObjectMapper +import com.hazelcast.map.IMap import org.onap.cps.ncmp.api.impl.events.EventsPublisher import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.inventory.InventoryPersistence @@ -29,20 +30,28 @@ import org.onap.cps.ncmp.event.model.SubscriptionEvent import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.spi.exceptions.OperationNotYetSupportedException import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import spock.util.concurrent.BlockingVariable -@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper]) +@SpringBootTest(classes = [ObjectMapper, JsonObjectMapper, SubscriptionEventForwarder]) class SubscriptionEventForwarderSpec extends MessagingBaseSpec { - def mockInventoryPersistence = Mock(InventoryPersistence) - def mockSubscriptionEventPublisher = Mock(EventsPublisher<SubscriptionEvent>) - def objectUnderTest = new SubscriptionEventForwarder(mockInventoryPersistence, mockSubscriptionEventPublisher) + @Autowired + SubscriptionEventForwarder objectUnderTest + + @SpringBean + InventoryPersistence mockInventoryPersistence = Mock(InventoryPersistence) + @SpringBean + EventsPublisher<SubscriptionEvent> mockSubscriptionEventPublisher = Mock(EventsPublisher<SubscriptionEvent>) + @SpringBean + IMap<String, Set<String>> mockForwardedSubscriptionEventCache = Mock(IMap<String, Set<String>>) @Autowired JsonObjectMapper jsonObjectMapper - def 'Forward valid CM create subscription'() { + def 'Forward valid CM create subscription and simulate timeout where #scenario'() { given: 'an event' def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json') def testEventSent = jsonObjectMapper.convertJsonString(jsonData, SubscriptionEvent.class) @@ -52,9 +61,17 @@ class SubscriptionEventForwarderSpec extends MessagingBaseSpec { createYangModelCmHandleWithDmiProperty(2, 1,"shape","square"), createYangModelCmHandleWithDmiProperty(3, 2,"shape","triangle") ] + and: 'the thread creation delay is reduced to 2 seconds for testing' + objectUnderTest.dmiResponseTimeoutInMs = 2000 + and: 'a Blocking Variable is used for the Asynchronous call with a timeout of 5 seconds' + def block = new BlockingVariable<Object>(5) when: 'the valid event is forwarded' objectUnderTest.forwardCreateSubscriptionEvent(testEventSent) - then: 'the event is forwarded twice with the CMHandle private properties and provides a valid listenable future' + then: 'An asynchronous call is made to the blocking variable' + block.get() + then: 'the event is added to the forwarded subscription event cache' + 1 * mockForwardedSubscriptionEventCache.put("SCO-9989752cm-subscription-001", ["DMIName1", "DMIName2"] as Set) + and: 'the event is forwarded twice with the CMHandle private properties and provides a valid listenable future' 1 * mockSubscriptionEventPublisher.publishEvent("ncmp-dmi-cm-avc-subscription-DMIName1", "SCO-9989752-cm-subscription-001-DMIName1", subscriptionEvent -> { Map targets = subscriptionEvent.getEvent().getPredicates().getTargets().get(0) @@ -68,6 +85,15 @@ class SubscriptionEventForwarderSpec extends MessagingBaseSpec { targets["CMHandle3"] == ["shape":"triangle"] } ) + and: 'a separate thread has been created where the map is polled' + 1 * mockForwardedSubscriptionEventCache.containsKey("SCO-9989752cm-subscription-001") >> true + 1 * mockForwardedSubscriptionEventCache.get(_) >> (DMINamesInMap) + and: 'the subscription id is removed from the event cache map returning the asynchronous blocking variable' + 1 * mockForwardedSubscriptionEventCache.remove("SCO-9989752cm-subscription-001") >> {block.set(_)} + where: + scenario | DMINamesInMap + 'there are dmis which have not responded' | ["DMIName1", "DMIName2"] as Set + 'all dmis have responded ' | [] as Set } def 'Forward CM create subscription where target CM Handles are #scenario'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy index 03825c2bbf..89b3a2ff26 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,14 +30,14 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration +import org.springframework.http.HttpStatus import spock.lang.Shared -import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE -import org.springframework.http.HttpStatus +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE @SpringBootTest @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiDataOperations]) @@ -50,6 +50,8 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def NO_REQUEST_ID = null @Shared def OPTIONS_PARAM = '(a=1,b=2)' + @Shared + def expectedBulkRequestAsJson = '{"operation": "read","data": {"fe1c1f1a070c4ce5bbfda7198592a0d3": {"neType": "RadioNode"},"b8e42eed0d9541ed8d8839e8eb86b3e0": {"neType": "RadioNode"}},"requestId": "bbb67474-f705-410a-93d1-b2844e7f45fd"}' @SpringBean JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) @@ -66,8 +68,8 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, READ) >> responseFromDmi dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl when: 'get resource data is invoked' - def result = objectUnderTest.getResourceDataFromDmi(cmHandleId, resourceIdentifier, - options, dataStore, NO_REQUEST_ID, NO_TOPIC) + def result = objectUnderTest.getResourceDataFromDmi(dataStore.value, cmHandleId, resourceIdentifier, + options, NO_TOPIC, NO_REQUEST_ID) then: 'the result is the response from the DMI service' assert result == responseFromDmi where: 'the following parameters are used' @@ -80,6 +82,25 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { 'datastore running with properties' | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running' | '&options=(a=1,b=2)' } + def 'call get bulk resource data for #dataStore from DMI service with topic #scenario.'() { + given: 'collection of yang model cm Handles' + mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty]) + and: 'a positive response from DMI service when it is called with the expected parameters' + def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED) + def expectedDmiBulkResourceDataUrl = "ncmp/v1/batch/data/ds/${dataStore}?resourceIdentifier=parent/child%26options=(a=1,b=2)&topic=my-topic-name&options=(fields=schemas/schema)" + mockDmiRestClient.postOperationWithJsonData(expectedDmiBulkResourceDataUrl, expectedBulkRequestAsJson, READ) >> responseFromDmi + dmiServiceUrlBuilder.getBulkRequestUrl(_, _) >> expectedDmiBulkResourceDataUrl + when: 'get resource data for bulk cm handle is invoked' + def result = objectUnderTest.getResourceDataFromDmi( dataStore.value, [cmHandleId], resourceIdentifier, + OPTIONS_PARAM, 'some-topic','requestId') + then: 'the result is the response from the DMI service' + assert result == responseFromDmi + where: 'the following parameters are used' + scenario | dataStore + 'datastore operational' | PASSTHROUGH_OPERATIONAL + 'datastore running' | PASSTHROUGH_RUNNING + } + def 'call get all resource data.'() { given: 'the system returns a cm handle with a sample property' mockYangModelCmHandleRetrieval([yangModelCmHandleProperty]) @@ -89,7 +110,7 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}', READ) >> responseFromDmi dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl when: 'get resource data is invoked' - def result = objectUnderTest.getResourceDataFromDmi(cmHandleId, PASSTHROUGH_OPERATIONAL, NO_REQUEST_ID) + def result = objectUnderTest.getResourceDataFromDmi( PASSTHROUGH_OPERATIONAL.value, cmHandleId, NO_REQUEST_ID) then: 'the result is the response from the DMI service' assert result == responseFromDmi } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy index ed8f08698d..ed74ad3342 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +24,8 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration +import org.onap.cps.ncmp.api.impl.executor.TaskExecutor +import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -34,7 +36,7 @@ import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration import spock.lang.Shared -import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ +import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ @SpringBootTest @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations]) @@ -49,6 +51,12 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { @SpringBean JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + @SpringBean + TaskExecutor stubbedTaskExecutor = Stub() + + @SpringBean + DmiServiceNameOrganizer stubbedDmiServiceNameOrganizer = Stub() + def 'Retrieving module references.'() { given: 'a cm handle' mockYangModelCmHandleRetrieval([]) @@ -89,7 +97,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { and: 'a positive response from DMI service when it is called with tha expected parameters' def responseFromDmi = new ResponseEntity<String>(HttpStatus.OK) mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules", - '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi + '{"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi when: 'a get module references is called' def result = objectUnderTest.getModuleReferences(yangModelCmHandle) then: 'the result is the response from DMI service' @@ -108,7 +116,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { [moduleName: 'mod2', revision: 'C', yangSource: 'other yang source']], HttpStatus.OK) def expectedModuleReferencesInRequest = '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}' mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ) >> responseFromDmi + '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":{}}', READ) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, newModuleReferences) then: 'the result has the 2 expected yang (re)sources (order is not guaranteed)' @@ -140,7 +148,7 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec { and: 'a positive response from DMI service when it is called with the expected parameters' def responseFromDmi = new ResponseEntity<>([[moduleName: 'mod1', revision: 'A', yangSource: 'some yang source']], HttpStatus.OK) mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/moduleResources", - '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi + '{"data":{"modules":[' + expectedModuleReferencesInRequest + ']},"cmHandleProperties":' + expectedAdditionalPropertiesInRequest + '}', READ) >> responseFromDmi when: 'get new yang resources from DMI service' def result = objectUnderTest.getNewYangResourcesFromDmi(yangModelCmHandle, unknownModuleReferences) then: 'the result is the response from DMI service' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy index c4d0020a6c..1b2c50ae76 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy @@ -58,12 +58,21 @@ abstract class DmiOperationsBaseSpec extends Specification { def static resourceIdentifier = 'parent/child' def mockYangModelCmHandleRetrieval(dmiProperties) { + populateYangModelCmHandle(dmiProperties) + mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle + } + + def mockYangModelCmHandleCollectionRetrieval(dmiProperties) { + populateYangModelCmHandle(dmiProperties) + mockInventoryPersistence.getYangModelCmHandles(_) >> [yangModelCmHandle] + } + + def populateYangModelCmHandle(dmiProperties) { yangModelCmHandle.dmiDataServiceName = dmiServiceName yangModelCmHandle.dmiServiceName = dmiServiceName yangModelCmHandle.dmiProperties = dmiProperties yangModelCmHandle.id = cmHandleId yangModelCmHandle.compositeState = new CompositeState() yangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY - mockInventoryPersistence.getYangModelCmHandle(cmHandleId) >> yangModelCmHandle } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy index 01569887ce..6ca3105500 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilderSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,10 @@ package org.onap.cps.ncmp.api.impl.utils +import org.onap.cps.ncmp.api.impl.operations.RequiredDmiService import org.onap.cps.spi.utils.CpsValidator -import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_RUNNING import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration @@ -45,8 +46,8 @@ class DmiServiceUrlBuilderSpec extends Specification { def 'Create the dmi service url with #scenario.'() { given: 'uri variables' dmiProperties.dmiBasePath = 'dmi' - def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle, - "cmHandle", PASSTHROUGH_RUNNING) + def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.value, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), + "cmHandle") and: 'query params' def uriQueries = objectUnderTest.populateQueryParams(resourceId, 'optionsParamInQuery', topic) @@ -65,8 +66,8 @@ class DmiServiceUrlBuilderSpec extends Specification { def 'Populate dmi data store url #scenario.'() { given: 'uri variables are created' dmiProperties.dmiBasePath = dmiBasePath - def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle, - "cmHandle", PASSTHROUGH_RUNNING) + def uriVars = objectUnderTest.populateUriVariables(PASSTHROUGH_RUNNING.value, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA), + "cmHandle") and: 'null query params' def uriQueries = objectUnderTest.populateQueryParams(null, null, null) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy index f4176d6212..8164dcf9ca 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * Modifications Copyright (C) 2022 Bell Canada * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,11 @@ package org.onap.cps.ncmp.api.inventory.sync +import static org.onap.cps.ncmp.api.impl.operations.DataStoreEnum.PASSTHROUGH_OPERATIONAL + import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations -import org.onap.cps.ncmp.api.impl.operations.DmiOperations import org.onap.cps.ncmp.api.inventory.CmHandleQueries import org.onap.cps.ncmp.api.inventory.CmHandleState import org.onap.cps.ncmp.api.inventory.CompositeState @@ -38,7 +39,6 @@ import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import spock.lang.Shared import spock.lang.Specification - import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.stream.Collectors @@ -137,7 +137,7 @@ class SyncUtilsSpec extends Specification{ def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString); def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK) - mockDmiDataOperations.getResourceDataFromDmi('cm-handle-123', DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, _) >> responseEntity + mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.value, 'cm-handle-123', _) >> responseEntity when: 'get resource data is called' def result = objectUnderTest.getResourceData('cm-handle-123') then: 'the returned data is correct' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy index aa8bc53c9d..a14a0f286c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/SubscriptionModelLoaderSpec.groovy @@ -32,6 +32,7 @@ import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.exceptions.SchemaSetNotFoundException +import org.onap.cps.spi.model.Dataspace import org.springframework.boot.SpringApplication import org.slf4j.LoggerFactory import org.springframework.boot.context.event.ApplicationReadyEvent @@ -73,9 +74,15 @@ class SubscriptionModelLoaderSpec extends Specification { } def 'Onboard subscription model successfully via application ready event'() { - when:'model loader is enabled' + given: 'dataspace is ready for use' + mockCpsAdminService.getDataspace('NCMP-Admin') >> new Dataspace('NCMP-Admin') + and:'model loader is enabled' objectUnderTest.subscriptionModelLoaderEnabled = true - and: 'the application is ready' + and: 'maximum attempt count is set' + objectUnderTest.maximumAttemptCount = 20 + and: 'retry time is set' + objectUnderTest.retryTimeMs = 100 + when: 'the application is ready' objectUnderTest.onApplicationEvent(applicationReadyEvent) then: 'the module service to create schema set is called once' 1 * mockCpsModuleService.createSchemaSet('NCMP-Admin', 'subscriptions',sampleYangContentMap) @@ -90,6 +97,8 @@ class SubscriptionModelLoaderSpec extends Specification { objectUnderTest.subscriptionModelLoaderEnabled = false and: 'application is ready' objectUnderTest.onApplicationEvent(applicationReadyEvent) + and: 'dataspace is ready for use' + mockCpsAdminService.getDataspace('NCMP-Admin') >> new Dataspace('NCMP-Admin') then: 'the module service to create schema set was not called' 0 * mockCpsModuleService.createSchemaSet(*_) and: 'the admin service to create an anchor set was not called' @@ -98,11 +107,34 @@ class SubscriptionModelLoaderSpec extends Specification { 0 * mockCpsDataService.saveData(*_) } + def 'Onboard subscription model fails as NCMP dataspace does not exist' () { + given: 'model loader is enabled' + objectUnderTest.subscriptionModelLoaderEnabled = true + and: 'maximum attempt count is set' + objectUnderTest.maximumAttemptCount = 20 + and: 'retry time is set' + objectUnderTest.retryTimeMs = 100 + when: 'the application is ready' + objectUnderTest.onApplicationEvent(applicationReadyEvent) + then: 'the module service to create schema set was not called' + 0 * mockCpsModuleService.createSchemaSet(*_) + and: 'the admin service to create an anchor set was not called' + 0 * mockCpsAdminService.createAnchor(*_) + and: 'the data service to create a top level datanode was not called' + 0 * mockCpsDataService.saveData(*_) + and: 'the log message contains the correct exception message' + def logs = appender.list.toString() + assert logs.contains("Retrieval of NCMP dataspace fails") + } + + def 'Exception occurred while schema set creation' () { given: 'creating a schema set throws an exception' mockCpsModuleService.createSchemaSet(*_) >> { throw new DataValidationException(*_) } and: 'model loader is enabled' objectUnderTest.subscriptionModelLoaderEnabled = true + and: 'dataspace is ready for use' + mockCpsAdminService.getDataspace('NCMP-Admin') >> new Dataspace('NCMP-Admin') when: 'application is ready' objectUnderTest.onApplicationEvent(applicationReadyEvent) then: 'the admin service to create an anchor set was not called' diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index e66f23d23f..679248ba86 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -35,4 +35,11 @@ ncmp: parallelism-level: 3 model-loader: - subscription: true
\ No newline at end of file + subscription: true + +# Custom Hazelcast Config. +hazelcast: + mode: + kubernetes: + enabled: false + service-name: "cps-and-ncmp-service"
\ No newline at end of file diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java index 854450c8bb..f44e310a1f 100644 --- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java +++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathBuilder.java @@ -24,7 +24,7 @@ package org.onap.cps.cpspath.parser; import static org.onap.cps.cpspath.parser.CpsPathPrefixType.DESCENDANT; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -47,7 +47,7 @@ public class CpsPathBuilder extends CpsPathBaseListener { final CpsPathQuery cpsPathQuery = new CpsPathQuery(); - final Map<String, Object> leavesData = new HashMap<>(); + final Map<String, Object> leavesData = new LinkedHashMap<>(); final StringBuilder normalizedXpathBuilder = new StringBuilder(); diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy index 96fdf88cf6..9552a4d342 100644 --- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy +++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathQuerySpec.groovy @@ -84,6 +84,8 @@ class CpsPathQuerySpec extends Specification { 'parent & child with more than one attribute has OR operator' | '/parent/child[@key1=1 or @key2="abc"]/child2' || "/parent/child[@key1='1' or @key2='abc']/child2" 'parent & child with multiple AND operators' | '/parent/child[@key1=1 and @key2="abc" and @key="xyz"]/child2' || "/parent/child[@key1='1' and @key2='abc' and @key='xyz']/child2" 'parent & child with multiple OR operators' | '/parent/child[@key1=1 or @key2="abc" or @key="xyz"]/child2' || "/parent/child[@key1='1' or @key2='abc' or @key='xyz']/child2" + 'parent & child with multiple AND/OR combination' | '/parent/child[@key1=1 and @key2="abc" or @key="xyz"]/child2' || "/parent/child[@key1='1' and @key2='abc' or @key='xyz']/child2" + 'parent & child with multiple OR/AND combination' | '/parent/child[@key1=1 or @key2="abc" and @key="xyz"]/child2' || "/parent/child[@key1='1' or @key2='abc' and @key='xyz']/child2" } def 'Parse xpath to form the Normalized xpath containing #scenario.'() { @@ -105,14 +107,17 @@ class CpsPathQuerySpec extends Specification { and: 'the right parameters are set' result.descendantName == "child" result.leavesData.size() == expectedNumberOfLeaves + and: 'the given operator(s) returns in the correct order' result.booleanOperatorsType == expectedOperators where: 'the following data is used' - scenario | cpsPath || expectedNumberOfLeaves || expectedOperators - 'one attribute' | '//child[@common-leaf-name-int=5]' || 1 || null - 'more than one attribute has AND operator' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2 || ['and'] - 'more than one attribute has OR operator' | '//child[@int-leaf=5 or @leaf-name="leaf value"]' || 2 || ['or'] - 'more than one attribute has combinations and/or operator' | '//child[@int-leaf=5 and @leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 2 || ['and', 'and'] - 'more than one attribute has combinations or/and operator' | '//child[@int-leaf=5 or @leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 2 || ['or', 'or'] + scenario | cpsPath || expectedNumberOfLeaves || expectedOperators + 'one attribute' | '//child[@common-leaf-name-int=5]' || 1 || null + 'more than one attribute has AND operator' | '//child[@int-leaf=5 and @leaf-name="leaf value"]' || 2 || ['and'] + 'more than one attribute has OR operator' | '//child[@int-leaf=5 or @leaf-name="leaf value"]' || 2 || ['or'] + 'more than one attribute has combinations and operators' | '//child[@int-leaf=5 and @leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 2 || ['and', 'and'] + 'more than one attribute has combinations or operators' | '//child[@int-leaf=5 or @leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 2 || ['or', 'or'] + 'more than one attribute has combinations and/or combination' | '//child[@int-leaf=5 and @leaf-name="leaf value" or @leaf-name="leaf value1" ]' || 2 || ['and', 'or'] + 'more than one attribute has combinations or/and combination' | '//child[@int-leaf=5 or @leaf-name="leaf value" and @leaf-name="leaf value1" ]' || 2 || ['or', 'and'] } def 'Parse #scenario cps path with text function condition'() { @@ -136,7 +141,7 @@ class CpsPathQuerySpec extends Specification { 'descendant with leaf value and ancestor' | '//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent' || true | true } - def 'Parse #scenario cps path with contains function condition'() { + def 'Parse cps path with contains function condition'() { when: 'the given cps path is parsed' def result = CpsPathQuery.createFrom('//someContainer[contains(@lang,"en")]') then: 'the query has the right xpath type' diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 369e5289b1..3d9105f680 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -494,21 +494,6 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } @Override - public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName, - final DataNode dataNode) { - final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); - final FragmentEntity fragmentEntity = getFragmentEntity(anchorEntity, dataNode.getXpath()); - updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode); - try { - fragmentRepository.save(fragmentEntity); - } catch (final StaleStateException staleStateException) { - throw new ConcurrencyException("Concurrent Transactions", - String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.", - dataspaceName, anchorName, dataNode.getXpath())); - } - } - - @Override public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName, final Collection<DataNode> updatedDataNodes) { final AnchorEntity anchorEntity = getAnchorEntity(dataspaceName, anchorName); diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java index b4366de75b..cd1457e359 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java @@ -159,7 +159,7 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ @Override public Collection<SchemaSet> getSchemaSetsByDataspaceName(final String dataspaceName) { final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); - final List<SchemaSetEntity> schemaSetEntities = schemaSetRepository.getByDataspace(dataspaceEntity); + final List<SchemaSetEntity> schemaSetEntities = schemaSetRepository.findByDataspace(dataspaceEntity); return schemaSetEntities.stream() .map(CpsModulePersistenceServiceImpl::toSchemaSet).collect(Collectors.toList()); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java index f7b586d7b3..fe9ff9e2f0 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java @@ -27,6 +27,7 @@ import org.onap.cps.spi.entities.DataspaceEntity; import org.onap.cps.spi.entities.SchemaSetEntity; import org.onap.cps.spi.exceptions.AnchorNotFoundException; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -45,11 +46,27 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> { Collection<AnchorEntity> findAllBySchemaSet(SchemaSetEntity schemaSetEntity); - Collection<AnchorEntity> findAllByDataspaceAndNameIn(DataspaceEntity dataspaceEntity, - Collection<String> anchorNames); + @Query(value = "SELECT * FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)", + nativeQuery = true) + Collection<AnchorEntity> findAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId, + @Param("anchorNames") String[] anchorNames); - Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(DataspaceEntity dataspaceEntity, - Collection<String> schemaSetNames); + default Collection<AnchorEntity> findAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity, + final Collection<String> anchorNames) { + return findAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0])); + } + + @Query(value = "SELECT a.* FROM anchor a" + + " LEFT OUTER JOIN schema_set s ON a.schema_set_id = s.id" + + " WHERE a.dataspace_id = :dataspaceId AND s.name = ANY (:schemaSetNames)", + nativeQuery = true) + Collection<AnchorEntity> findAllByDataspaceIdAndSchemaSetNameIn(@Param("dataspaceId") int dataspaceId, + @Param("schemaSetNames") String[] schemaSetNames); + + default Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(final DataspaceEntity dataspaceEntity, + final Collection<String> schemaSetNames) { + return findAllByDataspaceIdAndSchemaSetNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0])); + } Integer countByDataspace(DataspaceEntity dataspaceEntity); @@ -57,12 +74,29 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> { + "JOIN schema_set_yang_resources ON schema_set_yang_resources.yang_resource_id = yang_resource.id\n" + "JOIN schema_set ON schema_set.id = schema_set_yang_resources.schema_set_id\n" + "JOIN anchor ON anchor.schema_set_id = schema_set.id\n" - + "WHERE schema_set.dataspace_id = :dataspaceId AND module_name IN (:moduleNames)\n" + + "WHERE schema_set.dataspace_id = :dataspaceId AND module_name = ANY (:moduleNames)\n" + "GROUP BY anchor.id, anchor.name, anchor.dataspace_id, anchor.schema_set_id\n" + "HAVING COUNT(DISTINCT module_name) = :sizeOfModuleNames", nativeQuery = true) Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId, - @Param("moduleNames") Collection<String> moduleNames, @Param("sizeOfModuleNames") int sizeOfModuleNames); + @Param("moduleNames") String[] moduleNames, + @Param("sizeOfModuleNames") int sizeOfModuleNames); + + default Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(final int dataspaceId, + final Collection<String> moduleNames, + final int sizeOfModuleNames) { + final String[] moduleNamesArray = moduleNames.toArray(new String[0]); + return getAnchorsByDataspaceIdAndModuleNames(dataspaceId, moduleNamesArray, sizeOfModuleNames); + } + + @Modifying + @Query(value = "DELETE FROM anchor WHERE dataspace_id = :dataspaceId AND name = ANY (:anchorNames)", + nativeQuery = true) + void deleteAllByDataspaceIdAndNameIn(@Param("dataspaceId") int dataspaceId, + @Param("anchorNames") String[] anchorNames); + + default void deleteAllByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity, + final Collection<String> anchorNames) { + deleteAllByDataspaceIdAndNameIn(dataspaceEntity.getId(), anchorNames.toArray(new String[0])); + } - void deleteAllByDataspaceAndNameIn(DataspaceEntity dataspaceEntity, - Collection<String> anchorNames); } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepository.java deleted file mode 100644 index bad68f7e58..0000000000 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepository.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.spi.repository; - -import java.util.Collection; - -/** - * This interface is used in delete fragment entity by id with child using native sql queries. - */ -public interface FragmentNativeRepository { - - /** - * Delete fragment entities for each supplied xpath. - * This method will delete list elements or other data nodes, but not whole lists. - * Non-existing xpaths will not result in an exception. - * @param anchorId the id of the anchor - * @param xpaths xpaths of data nodes to remove - */ - void deleteByAnchorIdAndXpaths(int anchorId, Collection<String> xpaths); - - /** - * Delete fragment entities that are list elements of each supplied list xpath. - * For example, if xpath '/parent/list' is provided, then list all elements in '/parent/list' will be deleted, - * e.g. /parent/list[@key='A'], /parent/list[@key='B']. - * This method will only delete whole lists by xpath; xpaths to list elements or other data nodes will be ignored. - * Non-existing xpaths will not result in an exception. - * @param anchorId the id of the anchor - * @param listXpaths xpaths of whole lists to remove - */ - void deleteListsByAnchorIdAndXpaths(int anchorId, Collection<String> listXpaths); -} diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java deleted file mode 100644 index 04b7080def..0000000000 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.spi.repository; - -import java.util.Collection; -import java.util.Collections; -import java.util.stream.Collectors; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class FragmentNativeRepositoryImpl implements FragmentNativeRepository { - - @PersistenceContext - private final EntityManager entityManager; - - @Override - public void deleteByAnchorIdAndXpaths(final int anchorId, final Collection<String> xpaths) { - final String queryString = - "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath IN (:parameterPlaceholders))"; - executeUpdateWithAnchorIdAndCollection(queryString, anchorId, xpaths); - } - - @Override - public void deleteListsByAnchorIdAndXpaths(final int anchorId, final Collection<String> listXpaths) { - final Collection<String> listXpathPatterns = - listXpaths.stream().map(listXpath -> listXpath + "[%").collect(Collectors.toSet()); - final String queryString = - "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath LIKE ANY (array[:parameterPlaceholders]))"; - executeUpdateWithAnchorIdAndCollection(queryString, anchorId, listXpathPatterns); - } - - // Accept security hotspot as placeholders in SQL query are created internally, not from user input. - @SuppressWarnings("squid:S2077") - private void executeUpdateWithAnchorIdAndCollection(final String sqlTemplate, final int anchorId, - final Collection<String> collection) { - if (!collection.isEmpty()) { - final String parameterPlaceholders = String.join(",", Collections.nCopies(collection.size(), "?")); - final String queryStringWithParameterPlaceholders = - sqlTemplate.replaceFirst(":parameterPlaceholders\\b", parameterPlaceholders); - - final Query query = entityManager.createNativeQuery(queryStringWithParameterPlaceholders); - query.setParameter(1, anchorId); - int parameterIndex = 2; - for (final String parameterValue : collection) { - query.setParameter(parameterIndex++, parameterValue); - } - query.executeUpdate(); - } - } - -} diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java index d486a39c7e..8114f1055a 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java @@ -37,8 +37,7 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository;
@Repository
-public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery,
- FragmentNativeRepository {
+public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, FragmentRepositoryCpsPathQuery {
Optional<FragmentEntity> findByAnchorAndXpath(AnchorEntity anchorEntity, String xpath);
@@ -52,13 +51,39 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, @Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor")
List<FragmentExtract> findAllExtractsByAnchor(@Param("anchor") AnchorEntity anchorEntity);
- List<FragmentEntity> findAllByAnchorAndXpathIn(AnchorEntity anchorEntity, Collection<String> xpath);
+ @Query(value = "SELECT * FROM fragment WHERE xpath = ANY (:xpaths)", nativeQuery = true)
+ List<FragmentEntity> findAllByXpathIn(@Param("xpaths") String[] xpath);
- List<FragmentEntity> findAllByXpathIn(Collection<String> xpath);
+ default List<FragmentEntity> findAllByXpathIn(final Collection<String> xpaths) {
+ return findAllByXpathIn(xpaths.toArray(new String[0]));
+ }
+
+ @Modifying
+ @Query(value = "DELETE FROM fragment WHERE anchor_id = ANY (:anchorIds)", nativeQuery = true)
+ void deleteByAnchorIdIn(@Param("anchorIds") int[] anchorIds);
+
+ default void deleteByAnchorIn(final Collection<AnchorEntity> anchorEntities) {
+ deleteByAnchorIdIn(anchorEntities.stream().map(AnchorEntity::getId).mapToInt(id -> id).toArray());
+ }
@Modifying
- @Query("DELETE FROM FragmentEntity WHERE anchor IN (:anchors)")
- void deleteByAnchorIn(@Param("anchors") Collection<AnchorEntity> anchorEntities);
+ @Query(value = "DELETE FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)", nativeQuery = true)
+ void deleteByAnchorIdAndXpaths(@Param("anchorId") int anchorId, @Param("xpaths") String[] xpaths);
+
+ default void deleteByAnchorIdAndXpaths(final int anchorId, final Collection<String> xpaths) {
+ deleteByAnchorIdAndXpaths(anchorId, xpaths.toArray(new String[0]));
+ }
+
+ @Modifying
+ @Query(value = "DELETE FROM fragment f WHERE anchor_id = :anchorId AND xpath LIKE ANY (:xpathPatterns)",
+ nativeQuery = true)
+ void deleteByAnchorIdAndXpathLikeAny(@Param("anchorId") int anchorId,
+ @Param("xpathPatterns") String[] xpathPatterns);
+
+ default void deleteListsByAnchorIdAndXpaths(int anchorId, Collection<String> xpaths) {
+ final String[] listXpathPatterns = xpaths.stream().map(xpath -> xpath + "[%").toArray(String[]::new);
+ deleteByAnchorIdAndXpathLikeAny(anchorId, listXpathPatterns);
+ }
@Query("SELECT f FROM FragmentEntity f WHERE anchor = :anchor"
+ " AND (xpath = :parentXpath OR xpath LIKE CONCAT(:parentXpath,'/%'))")
@@ -66,13 +91,6 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, @Param("parentXpath") String parentXpath);
@Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"
- + " CAST(attributes AS TEXT) AS attributes"
- + " FROM FRAGMENT WHERE "
- + "( xpath = :parentXpath OR xpath LIKE CONCAT(:parentXpath,'/%') )",
- nativeQuery = true)
- List<FragmentExtract> findByParentXpath(@Param("parentXpath") String parentXpath);
-
- @Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"
+ " CAST(attributes AS TEXT) AS attributes"
+ " FROM FRAGMENT WHERE anchor_id = :anchorId"
+ " AND xpath ~ :xpathRegex",
@@ -80,9 +98,15 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, List<FragmentExtract> quickFindWithDescendants(@Param("anchorId") int anchorId,
@Param("xpathRegex") String xpathRegex);
- @Query("SELECT xpath FROM FragmentEntity WHERE anchor = :anchor AND xpath IN :xpaths")
- List<String> findAllXpathByAnchorAndXpathIn(@Param("anchor") AnchorEntity anchorEntity,
- @Param("xpaths") Collection<String> xpaths);
+ @Query(value = "SELECT xpath FROM fragment WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths)",
+ nativeQuery = true)
+ List<String> findAllXpathByAnchorIdAndXpathIn(@Param("anchorId") int anchorId,
+ @Param("xpaths") String[] xpaths);
+
+ default List<String> findAllXpathByAnchorAndXpathIn(final AnchorEntity anchorEntity,
+ final Collection<String> xpaths) {
+ return findAllXpathByAnchorIdAndXpathIn(anchorEntity.getId(), xpaths.toArray(new String[0]));
+ }
boolean existsByAnchorAndXpathStartsWith(AnchorEntity anchorEntity, String xpath);
@@ -93,7 +117,7 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, = "WITH RECURSIVE parent_search AS ("
+ " SELECT id, 0 AS depth "
+ " FROM fragment "
- + " WHERE anchor_id = :anchorId AND xpath IN :xpaths "
+ + " WHERE anchor_id = :anchorId AND xpath = ANY (:xpaths) "
+ " UNION "
+ " SELECT c.id, depth + 1 "
+ " FROM fragment c INNER JOIN parent_search p ON c.parent_id = p.id"
@@ -104,9 +128,14 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>, nativeQuery = true
)
List<FragmentExtract> findExtractsWithDescendants(@Param("anchorId") int anchorId,
- @Param("xpaths") Collection<String> xpaths,
+ @Param("xpaths") String[] xpaths,
@Param("maxDepth") int maxDepth);
+ default List<FragmentExtract> findExtractsWithDescendants(final int anchorId, final Collection<String> xpaths,
+ final int maxDepth) {
+ return findExtractsWithDescendants(anchorId, xpaths.toArray(new String[0]), maxDepth);
+ }
+
@Query(value = "SELECT id, anchor_id AS anchorId, xpath, parent_id AS parentId,"
+ " CAST(attributes AS TEXT) AS attributes"
+ " FROM FRAGMENT WHERE xpath ~ :xpathRegex",
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java index 3263f34473..3c5f973cb0 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/SchemaSetRepository.java @@ -24,7 +24,6 @@ package org.onap.cps.spi.repository; import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import org.onap.cps.spi.entities.DataspaceEntity; import org.onap.cps.spi.entities.SchemaSetEntity; import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; @@ -44,7 +43,7 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte * @param dataspaceEntity dataspace entity * @return list of schema set entity */ - Collection<SchemaSetEntity> findByDataspace(DataspaceEntity dataspaceEntity); + List<SchemaSetEntity> findByDataspace(DataspaceEntity dataspaceEntity); Integer countByDataspace(DataspaceEntity dataspaceEntity); @@ -61,24 +60,20 @@ public interface SchemaSetRepository extends JpaRepository<SchemaSetEntity, Inte .orElseThrow(() -> new SchemaSetNotFoundException(dataspaceEntity.getName(), schemaSetName)); } - /** - * Gets all schema sets for a given dataspace. - * - * @param dataspaceEntity dataspace entity - * @return list of schema set entity - * @throws SchemaSetNotFoundException if SchemaSet not found - */ - default List<SchemaSetEntity> getByDataspace(final DataspaceEntity dataspaceEntity) { - return findByDataspace(dataspaceEntity).stream().collect(Collectors.toList()); - } + @Modifying + @Query(value = "DELETE FROM schema_set WHERE dataspace_id = :dataspaceId AND name = ANY (:schemaSetNames)", + nativeQuery = true) + void deleteByDataspaceIdAndNameIn(@Param("dataspaceId") final int dataspaceId, + @Param("schemaSetNames") final String[] schemaSetNames); /** * Delete multiple schema sets in a given dataspace. * @param dataspaceEntity dataspace entity * @param schemaSetNames schema set names */ - @Modifying - @Query("DELETE FROM SchemaSetEntity s WHERE s.dataspace = :dataspaceEntity AND s.name IN (:schemaSetNames)") - void deleteByDataspaceAndNameIn(@Param("dataspaceEntity") DataspaceEntity dataspaceEntity, - @Param("schemaSetNames") Collection<String> schemaSetNames); + default void deleteByDataspaceAndNameIn(final DataspaceEntity dataspaceEntity, + final Collection<String> schemaSetNames) { + deleteByDataspaceIdAndNameIn(dataspaceEntity.getId(), schemaSetNames.toArray(new String[0])); + } + } diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceRepository.java index fff0a6a037..7584ff65c0 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceRepository.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceRepository.java @@ -35,7 +35,11 @@ import org.springframework.stereotype.Repository; public interface YangResourceRepository extends JpaRepository<YangResourceEntity, Long>, YangResourceNativeRepository, SchemaSetYangResourceRepository { - List<YangResourceEntity> findAllByChecksumIn(Set<String> checksum); + List<YangResourceEntity> findAllByChecksumIn(String[] checksums); + + default List<YangResourceEntity> findAllByChecksumIn(final Collection<String> checksums) { + return findAllByChecksumIn(checksums.toArray(new String[0])); + } @Query(value = "SELECT DISTINCT\n" + "yang_resource.module_name AS module_name,\n" @@ -86,9 +90,14 @@ public interface YangResourceRepository extends JpaRepository<YangResourceEntity + "schema_set.id\n" + "JOIN yang_resource ON yang_resource.id = schema_set_yang_resources.yang_resource_id\n" + "WHERE\n" - + "dataspace.name = :dataspaceName and yang_resource.module_Name IN (:moduleNames)", nativeQuery = true) + + "dataspace.name = :dataspaceName and yang_resource.module_Name = ANY (:moduleNames)", nativeQuery = true) Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames( - @Param("dataspaceName") String dataspaceName, @Param("moduleNames") Collection<String> moduleNames); + @Param("dataspaceName") String dataspaceName, @Param("moduleNames") String[] moduleNames); + + default Set<YangResourceModuleReference> findAllModuleReferencesByDataspaceAndModuleNames( + final String dataspaceName, final Collection<String> moduleNames) { + return findAllModuleReferencesByDataspaceAndModuleNames(dataspaceName, moduleNames.toArray(new String[0])); + } @Modifying @Query(value = "DELETE FROM yang_resource yr WHERE NOT EXISTS " diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy index e60afa78df..93d7662014 100755 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy @@ -23,7 +23,6 @@ package org.onap.cps.spi.impl -import com.fasterxml.jackson.databind.ObjectMapper import com.google.common.collect.ImmutableSet import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.entities.FragmentEntity @@ -35,7 +34,6 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.exceptions.DataspaceNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.utils.JsonObjectMapper import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql @@ -49,7 +47,6 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { @Autowired CpsDataPersistenceService objectUnderTest - static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) static DataNodeBuilder dataNodeBuilder = new DataNodeBuilder() static final String SET_DATA = '/data/fragment.sql' @@ -353,11 +350,11 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) - def 'Update data node and descendants by removing descendants.'() { - given: 'data node object with leaves updated, no children' - def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], []) + def 'Update data nodes and descendants by removing descendants.'() { + given: 'data nodes with leaves updated, no children' + def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [])] when: 'update data nodes and descendants is performed' - objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode) + objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes) then: 'leaves have been updated for selected data node' def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID) def updatedLeaves = getLeavesMap(updatedFragment) @@ -370,13 +367,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) - def 'Update data node and descendants with new descendants'() { - given: 'data node object with leaves updated, having child with old content' - def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [ + def 'Update data nodes and descendants with new descendants'() { + given: 'data nodes with leaves updated, having child with old content' + def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [ buildDataNode('/parent-200/child-201/grand-child', ['leaf-value': 'original'], []) - ]) + ])] when: 'update is performed including descendants' - objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode) + objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes) then: 'leaves have been updated for selected data node' def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID) def updatedLeaves = getLeavesMap(updatedFragment) @@ -390,13 +387,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) - def 'Update data node and descendants with same descendants but changed leaf value.'() { - given: 'data node object with leaves updated, having child with old content' - def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [ + def 'Update data nodes and descendants with same descendants but changed leaf value.'() { + given: 'data nodes with leaves updated, having child with old content' + def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [ buildDataNode('/parent-200/child-201/grand-child', ['leaf-value': 'new'], []) - ]) + ])] when: 'update is performed including descendants' - objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode) + objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes) then: 'leaves have been updated for selected data node' def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID) def updatedLeaves = getLeavesMap(updatedFragment) @@ -410,13 +407,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) - def 'Update data node and descendants with different descendants xpath'() { - given: 'data node object with leaves updated, having child with old content' - def submittedDataNode = buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [ + def 'Update data nodes and descendants with different descendants xpath'() { + given: 'data nodes with leaves updated, having child with old content' + def submittedDataNodes = [buildDataNode('/parent-200/child-201', ['leaf-value': 'new'], [ buildDataNode('/parent-200/child-201/grand-child-new', ['leaf-value': 'new'], []) - ]) + ])] when: 'update is performed including descendants' - objectUnderTest.updateDataNodeAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode) + objectUnderTest.updateDataNodesAndDescendants(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNodes) then: 'leaves have been updated for selected data node' def updatedFragment = fragmentRepository.getById(DATA_NODE_202_FRAGMENT_ID) def updatedLeaves = getLeavesMap(updatedFragment) @@ -432,19 +429,17 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { } @Sql([CLEAR_DATA, SET_DATA]) - def 'Update data node and descendants error scenario: #scenario.'() { - given: 'data node object' - def submittedDataNode = buildDataNode(xpath, ['leaf-name': 'leaf-value'], []) + def 'Update data nodes and descendants error scenario: #scenario.'() { + given: 'data nodes collection' + def submittedDataNodes = [buildDataNode(xpath, ['leaf-name': 'leaf-value'], [])] when: 'attempt to update data node for #scenario' - objectUnderTest.updateDataNodeAndDescendants(dataspaceName, anchorName, submittedDataNode) + objectUnderTest.updateDataNodesAndDescendants(dataspaceName, anchorName, submittedDataNodes) then: 'a #expectedException is thrown' thrown(expectedException) where: 'the following data is used' scenario | dataspaceName | anchorName | xpath || expectedException 'non-existing dataspace' | 'NO DATASPACE' | 'not relevant' | '/not relevant' || DataspaceNotFoundException 'non-existing anchor' | DATASPACE_NAME | 'NO ANCHOR' | '/not relevant' || AnchorNotFoundException - 'non-existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | '/NON-EXISTING-XPATH' || DataNodeNotFoundException - 'invalid xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'INVALID XPATH' || CpsPathException } @Sql([CLEAR_DATA, SET_DATA]) @@ -699,7 +694,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { return dataNodeBuilder.withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build() } - static Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) { + Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) { return jsonObjectMapper.convertJsonString(fragmentEntity.attributes, Map<String, Object>.class) } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy index f02aa754f6..204b93442f 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy @@ -77,24 +77,6 @@ class CpsDataPersistenceServiceSpec extends Specification { 1 * objectUnderTest.storeDataNodes('dataspace1', 'anchor1', [dataNode]) } - def 'Handling of StaleStateException (caused by concurrent updates) during update data node and descendants.'() { - given: 'the fragment repository returns a fragment entity' - mockFragmentRepository.getByAnchorAndXpath(*_) >> { - def fragmentEntity = new FragmentEntity() - fragmentEntity.setChildFragments([new FragmentEntity()] as Set<FragmentEntity>) - return fragmentEntity - } - and: 'a data node is concurrently updated by another transaction' - mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") } - when: 'attempt to update data node with submitted data nodes' - objectUnderTest.updateDataNodeAndDescendants('some-dataspace', 'some-anchor', new DataNodeBuilder().withXpath('/some/xpath').build()) - then: 'concurrency exception is thrown' - def concurrencyException = thrown(ConcurrencyException) - assert concurrencyException.getDetails().contains('some-dataspace') - assert concurrencyException.getDetails().contains('some-anchor') - assert concurrencyException.getDetails().contains('/some/xpath') - } - def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() { given: 'the system can update one datanode and has two more datanodes that throw an exception while updating' def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([ diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy index 214fd69ff1..65d63dfe3b 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2022 Bell Canada. + * Modifications Copyright (C) 2021-2023 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. @@ -19,18 +20,14 @@ */ package org.onap.cps.spi.impl -import com.fasterxml.jackson.databind.ObjectMapper + import org.hibernate.exception.ConstraintViolationException -import org.onap.cps.spi.CpsAdminPersistenceService import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.entities.DataspaceEntity import org.onap.cps.spi.exceptions.DuplicatedYangResourceException import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.spi.repository.AnchorRepository import org.onap.cps.spi.repository.DataspaceRepository -import org.onap.cps.spi.repository.SchemaSetRepository import org.onap.cps.spi.repository.YangResourceRepository -import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired import org.springframework.dao.DataIntegrityViolationException @@ -43,24 +40,12 @@ class CpsModulePersistenceServiceConcurrencySpec extends CpsPersistenceSpecBase @Autowired CpsModulePersistenceService objectUnderTest - @Autowired - AnchorRepository anchorRepository - - @Autowired - SchemaSetRepository schemaSetRepository - - @Autowired - CpsAdminPersistenceService cpsAdminPersistenceService - @SpringBean YangResourceRepository yangResourceRepositoryMock = Mock() @SpringBean DataspaceRepository dataspaceRepositoryMock = Mock() - @SpringBean - JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - static final String DATASPACE_NAME = 'DATASPACE-001' static final String SCHEMA_SET_NAME_NEW = 'SCHEMA-SET-NEW' static final String NEW_RESOURCE_NAME = 'some new resource' diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy index 864b3e3b61..53f42f5a9f 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy @@ -30,13 +30,10 @@ import org.onap.cps.spi.exceptions.SchemaSetNotFoundException import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference import org.onap.cps.spi.model.SchemaSet -import org.onap.cps.spi.repository.AnchorRepository import org.onap.cps.spi.repository.SchemaSetRepository import org.onap.cps.spi.repository.SchemaSetYangResourceRepositoryImpl -import org.onap.cps.spi.repository.YangResourceRepository import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql -import spock.lang.Ignore class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { @@ -44,17 +41,11 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase CpsModulePersistenceService objectUnderTest @Autowired - AnchorRepository anchorRepository - - @Autowired SchemaSetRepository schemaSetRepository @Autowired CpsAdminPersistenceService cpsAdminPersistenceService - @Autowired - YangResourceRepository yangResourceRepository - final static String SET_DATA = '/data/schemaset.sql' def static EXISTING_SCHEMA_SET_NAME = SCHEMA_SET_NAME1 diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceSpec.groovy index 9ef9732681..811c3290b9 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceSpec.groovy @@ -84,7 +84,7 @@ class CpsModulePersistenceServiceSpec extends Specification { def 'Store schema set error scenario: #scenario.'() { given: 'no yang resource are currently saved' - yangResourceRepositoryMock.findAllByChecksumIn(_) >> Collections.emptyList() + yangResourceRepositoryMock.findAllByChecksumIn(_ as Collection<String>) >> Collections.emptyList() and: 'persisting yang resource raises db constraint exception (in case of concurrent requests for example)' yangResourceRepositoryMock.saveAll(_) >> { throw dbException } when: 'attempt to store schema set ' diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy index 9b722cddae..222a828b9f 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,6 @@ import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.entities.SchemaSetEntity import org.onap.cps.spi.impl.CpsPersistenceSpecBase import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.spi.repository.DataspaceRepository import org.onap.cps.spi.repository.ModuleReferenceRepository import org.onap.cps.spi.repository.SchemaSetRepository import org.springframework.beans.factory.annotation.Autowired @@ -53,9 +52,6 @@ class CpsModuleReferenceRepositoryPerfTest extends CpsPersistenceSpecBase { CpsModulePersistenceService objectUnderTest @Autowired - DataspaceRepository dataspaceRepository - - @Autowired SchemaSetRepository schemaSetRepository @Autowired diff --git a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java index 54d6ff3953..5ee6b3804e 100644 --- a/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java +++ b/cps-service/src/main/java/org/onap/cps/cache/AnchorDataCacheConfig.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================== - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,7 @@ package org.onap.cps.cache; -import com.hazelcast.config.Config; import com.hazelcast.config.MapConfig; -import com.hazelcast.config.NamedConfig; -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,7 +29,7 @@ import org.springframework.context.annotation.Configuration; * Core infrastructure of the hazelcast distributed cache for anchor data config use cases. */ @Configuration -public class AnchorDataCacheConfig { +public class AnchorDataCacheConfig extends HazelcastCacheConfig { private static final MapConfig anchorDataCacheMapConfig = createMapConfig("anchorDataCacheMapConfig"); @@ -44,27 +40,7 @@ public class AnchorDataCacheConfig { */ @Bean public IMap<String, AnchorDataCacheEntry> anchorDataCache() { - return createHazelcastInstance("hazelCastInstanceCpsCore", anchorDataCacheMapConfig) - .getMap("anchorDataCache"); + return createHazelcastInstance("hazelCastInstanceCpsCore", anchorDataCacheMapConfig, "cps-service-caches") + .getMap("anchorDataCache"); } - - private HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName, - final NamedConfig namedConfig) { - return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig)); - } - - private Config initializeConfig(final String instanceName, final NamedConfig namedConfig) { - final Config config = new Config(instanceName); - config.addMapConfig((MapConfig) namedConfig); - config.setClusterName("cps-service-caches"); - return config; - } - - private static MapConfig createMapConfig(final String configName) { - final MapConfig mapConfig = new MapConfig(configName); - mapConfig.setBackupCount(3); - mapConfig.setAsyncBackupCount(3); - return mapConfig; - } - } diff --git a/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java new file mode 100644 index 0000000000..4aebceae0a --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/cache/HazelcastCacheConfig.java @@ -0,0 +1,85 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.cache; + +import com.hazelcast.config.Config; +import com.hazelcast.config.MapConfig; +import com.hazelcast.config.NamedConfig; +import com.hazelcast.config.QueueConfig; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; + +/** + * Core infrastructure of the hazelcast distributed cache. + */ +@Slf4j +public class HazelcastCacheConfig { + + @Value("${hazelcast.mode.kubernetes.enabled}") + protected boolean cacheKubernetesEnabled; + + @Value("${hazelcast.mode.kubernetes.service-name}") + protected String cacheKubernetesServiceName; + + protected HazelcastInstance createHazelcastInstance(final String hazelcastInstanceName, + final NamedConfig namedConfig, final String clusterName) { + return Hazelcast.newHazelcastInstance(initializeConfig(hazelcastInstanceName, namedConfig, clusterName)); + } + + private Config initializeConfig(final String instanceName, final NamedConfig namedConfig, + final String clusterName) { + final Config config = new Config(instanceName); + if (namedConfig instanceof MapConfig) { + config.addMapConfig((MapConfig) namedConfig); + } + if (namedConfig instanceof QueueConfig) { + config.addQueueConfig((QueueConfig) namedConfig); + } + config.setClusterName(clusterName); + updateDiscoveryMode(config); + return config; + } + + protected static MapConfig createMapConfig(final String configName) { + final MapConfig mapConfig = new MapConfig(configName); + mapConfig.setBackupCount(3); + mapConfig.setAsyncBackupCount(3); + return mapConfig; + } + + protected static QueueConfig createQueueConfig(final String configName) { + final QueueConfig commonQueueConfig = new QueueConfig(configName); + commonQueueConfig.setBackupCount(3); + commonQueueConfig.setAsyncBackupCount(3); + return commonQueueConfig; + } + + protected void updateDiscoveryMode(final Config config) { + if (cacheKubernetesEnabled) { + log.info("Enabling kubernetes mode with service-name : {}", cacheKubernetesServiceName); + config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(true) + .setProperty("service-name", cacheKubernetesServiceName); + } + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java index 540401913b..949fbc2c29 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java @@ -138,15 +138,6 @@ public interface CpsDataPersistenceService { void updateDataLeaves(String dataspaceName, String anchorName, String xpath, Map<String, Serializable> leaves); /** - * Replaces an existing data node's content including descendants. - * - * @param dataspaceName dataspace name - * @param anchorName anchor name - * @param dataNode data node - */ - void updateDataNodeAndDescendants(String dataspaceName, String anchorName, DataNode dataNode); - - /** * Replaces multiple existing data nodes' content including descendants in a batch operation. * * @param dataspaceName dataspace name diff --git a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy index 839444b680..76b5345534 100644 --- a/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/cache/AnchorDataCacheConfigSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ */ package org.onap.cps.cache + +import com.hazelcast.config.Config import com.hazelcast.core.Hazelcast import com.hazelcast.map.IMap import org.springframework.beans.factory.annotation.Autowired @@ -49,4 +51,28 @@ class AnchorDataCacheConfigSpec extends Specification { assert anchorDataCacheConfig.backupCount == 3 assert anchorDataCacheConfig.asyncBackupCount == 3 } + + def 'Verify deployment network configs for Distributed Caches'() { + given: 'the Anchor Data Cache config' + def anchorDataCacheNetworkConfig = Hazelcast.getHazelcastInstanceByName('hazelCastInstanceCpsCore').config.networkConfig + expect: 'system created instance with correct config' + assert anchorDataCacheNetworkConfig.join.autoDetectionConfig.enabled + assert !anchorDataCacheNetworkConfig.join.kubernetesConfig.enabled + } + + def 'Verify network config'() { + given: 'Synchronization config object and test configuration' + def objectUnderTest = new AnchorDataCacheConfig() + def testConfig = new Config() + when: 'kubernetes properties are enabled' + objectUnderTest.cacheKubernetesEnabled = true + objectUnderTest.cacheKubernetesServiceName = 'test-service-name' + and: 'method called to update the discovery mode' + objectUnderTest.updateDiscoveryMode(testConfig) + then: 'applied properties are reflected' + assert testConfig.networkConfig.join.kubernetesConfig.enabled + assert testConfig.networkConfig.join.kubernetesConfig.properties.get('service-name') == 'test-service-name' + + } + } diff --git a/cps-service/src/test/resources/application.yml b/cps-service/src/test/resources/application.yml index 04295eb74f..21f3745337 100644 --- a/cps-service/src/test/resources/application.yml +++ b/cps-service/src/test/resources/application.yml @@ -48,3 +48,10 @@ spring: logging: level: org.apache.kafka: ERROR + +# Custom Hazelcast Config. +hazelcast: + mode: + kubernetes: + enabled: false + service-name: "cps-and-ncmp-service" diff --git a/dmi-plugin-stub/mappings/batchCmHandles.json b/dmi-plugin-stub/mappings/batchCmHandles.json index 2018516b4c..5f290cd47d 100644 --- a/dmi-plugin-stub/mappings/batchCmHandles.json +++ b/dmi-plugin-stub/mappings/batchCmHandles.json @@ -1,7 +1,7 @@ { "request": { "method": "POST", - "urlPattern": "/dmi/v1/ch/batch/data/ds/.*" + "urlPattern": "/dmi/v1/batch/data/ds/.*" }, "response": { "status": 200, diff --git a/docs/cps-path.rst b/docs/cps-path.rst index a24bf1e9fe..08892e09ec 100644 --- a/docs/cps-path.rst +++ b/docs/cps-path.rst @@ -223,7 +223,7 @@ descendant-path leaf-conditions --------------- -**Syntax**: ``<xpath> '[' @<leaf-name1> '=' <leaf-value1> ( ' and ' @<leaf-name> '=' <leaf-value> )* ']'`` +**Syntax**: ``<xpath> '[' @<leaf-name1> '=' <leaf-value1> ( ' <and|or> ' @<leaf-name> '=' <leaf-value> )* ']'`` - ``xpath``: Absolute or descendant or xpath to the (list) node which elements will be queried. - ``leaf-name``: The name of the leaf which value needs to be compared. - ``leaf-value``: The required value of the leaf. @@ -233,10 +233,13 @@ leaf-conditions - ``//categories[@name="Kids"]`` - ``//categories[@name='Kids']`` - ``//categories[@code='1']/books/book[@title='Dune' and @price=5]`` + - ``//categories[@code='1']/books/book[@title='xyz' or @price=15]`` - ``//categories[@code=1]`` **Limitations** - Only the last list or container can be queried leaf values. Any ancestor list will have to be referenced by its key name-value pair(s). - - Multiple attributes can only be combined using ``and``. ``or`` and bracketing is not supported. + - When mixing ``and/or`` operators, ``and`` has precedence over ``or`` . So ``and`` operators get evaluated first. + - Bracketing is not supported. + - Leaf names are not validated so ``or`` operations with invalid leaf names will silently be ignored. - Only leaves can be used, leaf-list are not supported. - Only string and integer values are supported, boolean and float values are not supported. - The key should be supplied with correct data type for it to be queried from DB. In the last example above the attribute code is of type diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy index 44287b221b..a04302fa63 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy @@ -62,19 +62,21 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase { and: 'the cps-path of queryDataNodes has the expectedLeaves' assert result.leaves.sort() == expectedLeaves.sort() where: 'the following data is used' - scenario | cpspath || expectedResultSize | expectedLeaves - 'the "OR" condition' | '//books[@lang="English" or @price=15]' || 6 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]], - [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]], - [lang: "English", price: 14, title: "The Light Fantastic", authors: ["Terry Pratchett"], editions: [1986]], - [lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]], - [lang: "English", price: 12, title: "The Colour of Magic", authors: ["Terry Pratchett"], editions: [1983]], - [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]]] - 'the "OR" condition with non-json data' | '//books[@title="xyz" or @price=15]' || 2 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]], - [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]] - 'combination of multiple AND' | '//books[@lang="English" and @price=15 and @edition=1983]' || 0 | [] - 'combination of multiple OR' | '//books[ @title="Matilda" or @price=15 or @edition=1983]' || 3 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]], - [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]], - [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]] + scenario | cpspath || expectedResultSize | expectedLeaves + 'the "OR" condition' | '//books[@lang="English" or @price=15]' || 6 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]], + [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]], + [lang: "English", price: 14, title: "The Light Fantastic", authors: ["Terry Pratchett"], editions: [1986]], + [lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]], + [lang: "English", price: 12, title: "The Colour of Magic", authors: ["Terry Pratchett"], editions: [1983]], + [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]]] + 'the "OR" condition with non-json data' | '//books[@title="xyz" or @price=15]' || 2 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]], + [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]] + 'combination of multiple AND' | '//books[@lang="English" and @price=15 and @edition=1983]' || 0 | [] + 'combination of multiple OR' | '//books[ @title="Matilda" or @price=15 or @edition=1983]' || 3 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]], + [lang: "English", price: 10, title: "Matilda", authors: ["Roald Dahl"], editions: [1988, 2000]], + [lang: "English", price: 15, title: "The Gruffalo", authors: ["Julia Donaldson"], editions: [1999]]] + 'combination of AND/OR' | '//books[@edition=1983 and @price=15 or @title="Good Omens"]' || 1 | [[lang: "English", price: 13, title: "Good Omens", authors: ["Terry Pratchett", "Neil Gaiman"], editions: [2006]]] + 'combination of OR/AND' | '//books[@title="Annihilation" or @price=39 and @lang="arabic"]' || 1 | [[lang: "English", price: 15, title: "Annihilation", authors: ["Jeff VanderMeer"], editions: [2014]]] } def 'Cps Path query for leaf value(s) with #scenario.'() { @@ -169,17 +171,19 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase { def bookTitles = result.collect { it.getLeaves().get('title') } assert bookTitles.sort() == expectedBookTitles.sort() where: 'the following data is used' - scenario | cpsPath || expectedBookTitles - 'one leaf' | '//books[@price=14]' || ['The Light Fantastic'] - 'one text' | '//books/authors[text()="Terry Pratchett"]' || ['Good Omens', 'The Colour of Magic', 'The Light Fantastic'] - 'more than one leaf' | '//books[@price=12 and @lang="English"]' || ['The Colour of Magic'] - 'more than one leaf has "OR" condition' | '//books[@lang="English" or @price=15]' || ['Annihilation', 'Good Omens', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic'] - 'more than one leaf has "OR" condition with non-json data' | '//books[@title="xyz" or @price=13]' || ['Good Omens'] - 'more than one leaf has multiple AND' | '//books[@lang="English" and @price=13 and @edition=1983]' || [] - 'more than one leaf has multiple OR' | '//books[ @title="Matilda" or @price=15 or @edition=2006]' || ['Annihilation', 'Matilda', 'The Gruffalo'] - 'leaves reversed in order' | '//books[@lang="English" and @price=12]' || ['The Colour of Magic'] - 'leaf and text' | '//books[@price=14]/authors[text()="Terry Pratchett"]' || ['The Light Fantastic'] - 'leaf and contains' | '//books[contains(@price,"13")]' || ['Good Omens'] + scenario | cpsPath || expectedBookTitles + 'one leaf' | '//books[@price=14]' || ['The Light Fantastic'] + 'one text' | '//books/authors[text()="Terry Pratchett"]' || ['Good Omens', 'The Colour of Magic', 'The Light Fantastic'] + 'more than one leaf' | '//books[@price=12 and @lang="English"]' || ['The Colour of Magic'] + 'more than one leaf has "OR" condition' | '//books[@lang="English" or @price=15]' || ['Annihilation', 'Good Omens', 'Matilda', 'The Colour of Magic', 'The Gruffalo', 'The Light Fantastic'] + 'more than one leaf has "OR" condition with non-json data' | '//books[@title="xyz" or @price=13]' || ['Good Omens'] + 'more than one leaf has multiple AND' | '//books[@lang="English" and @price=13 and @edition=1983]' || [] + 'more than one leaf has multiple OR' | '//books[ @title="Matilda" or @price=15 or @edition=2006]' || ['Annihilation', 'Matilda', 'The Gruffalo'] + 'leaves reversed in order' | '//books[@lang="English" and @price=12]' || ['The Colour of Magic'] + 'more than one leaf has combination of AND/OR' | '//books[@edition=1983 and @price=13 or @title="Good Omens"]' || ['Good Omens'] + 'more than one leaf has OR/AND' | '//books[@title="The Light Fantastic" or @price=11 and @edition=1983]' || ['The Light Fantastic'] + 'leaf and text' | '//books[@price=14]/authors[text()="Terry Pratchett"]' || ['The Light Fantastic'] + 'leaf and contains' | '//books[contains(@price,"13")]' || ['Good Omens'] } def 'Cps Path query using descendant anywhere with #scenario condition(s) for a list element.'() { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsAdminServiceLimits.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsAdminServiceLimits.groovy index 7875caec35..0034af453b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsAdminServiceLimits.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsAdminServiceLimits.groovy @@ -22,7 +22,6 @@ package org.onap.cps.integration.performance.cps import org.onap.cps.api.CpsAdminService import org.onap.cps.integration.performance.base.CpsPerfTestBase -import org.springframework.dao.DataAccessResourceFailureException class CpsAdminServiceLimits extends CpsPerfTestBase { @@ -32,20 +31,20 @@ class CpsAdminServiceLimits extends CpsPerfTestBase { def 'Get anchors from multiple schema set names limit exceeded: 32,766 (~ 2^15) schema set names.'() { given: 'more than 32,766 schema set names' - def schemaSetNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it } + def schemaSetNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it } when: 'single get is executed to get all the anchors' objectUnderTest.getAnchors(CPS_PERFORMANCE_TEST_DATASPACE, schemaSetNames) - then: 'a database exception is thrown' - thrown(DataAccessResourceFailureException.class) + then: 'a database exception is not thrown' + noExceptionThrown() } def 'Querying anchor names limit exceeded: 32,766 (~ 2^15) modules.'() { given: 'more than 32,766 module names' - def moduleNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it } + def moduleNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it } when: 'single query is executed to get all the anchors' objectUnderTest.queryAnchorNames(CPS_PERFORMANCE_TEST_DATASPACE, moduleNames) - then: 'a database exception is thrown' - thrown(DataAccessResourceFailureException.class) + then: 'a database exception is not thrown' + noExceptionThrown() } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimits.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimits.groovy index 2df910194d..1579470eab 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimits.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimits.groovy @@ -23,8 +23,7 @@ package org.onap.cps.integration.performance.cps import java.time.OffsetDateTime import org.onap.cps.api.CpsDataService import org.onap.cps.integration.performance.base.CpsPerfTestBase -import org.springframework.dao.DataAccessResourceFailureException -import org.springframework.transaction.TransactionSystemException +import org.onap.cps.spi.exceptions.DataNodeNotFoundException import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS @@ -36,29 +35,29 @@ class CpsDataServiceLimits extends CpsPerfTestBase { def 'Multiple get limit exceeded: 32,764 (~ 2^15) xpaths.'() { given: 'more than 32,764 xpaths' - def xpaths = (0..32_764).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" } + def xpaths = (0..40_000).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" } when: 'single operation is executed to get all datanodes with given xpaths' objectUnderTest.getDataNodesForMultipleXpaths(CPS_PERFORMANCE_TEST_DATASPACE, 'bookstore1', xpaths, INCLUDE_ALL_DESCENDANTS) - then: 'a database exception is thrown' - thrown(DataAccessResourceFailureException.class) + then: 'a database exception is not thrown' + noExceptionThrown() } def 'Delete multiple datanodes limit exceeded: 32,767 (~ 2^15) xpaths.'() { given: 'more than 32,767 xpaths' - def xpaths = (0..32_767).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" } + def xpaths = (0..40_000).collect { "/size/of/this/path/does/not/matter/for/limit[@id='" + it + "']" } when: 'single operation is executed to delete all datanodes with given xpaths' objectUnderTest.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'bookstore1', xpaths, OffsetDateTime.now()) - then: 'a database exception is thrown' - thrown(TransactionSystemException.class) + then: 'a database exception is not thrown (but a CPS DataNodeNotFoundException is thrown)' + thrown(DataNodeNotFoundException.class) } def 'Delete datanodes from multiple anchors limit exceeded: 32,766 (~ 2^15) anchors.'() { given: 'more than 32,766 anchor names' - def anchorNames = (0..32_766).collect { "size-of-this-name-does-not-matter-for-limit-" + it } + def anchorNames = (0..40_000).collect { "size-of-this-name-does-not-matter-for-limit-" + it } when: 'single operation is executed to delete all datanodes in given anchors' objectUnderTest.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, anchorNames, OffsetDateTime.now()) - then: 'a database exception is thrown' - thrown(DataAccessResourceFailureException.class) + then: 'a database exception is not thrown' + noExceptionThrown() } } |