From 37d82d5d54ede4c05862fe648911c383d253cec3 Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Mon, 24 Jun 2024 13:24:57 +0100 Subject: Repackage Inventory Feature - decided that cm handle queries is part of 'inventory' feature - a lot of files (~100 total) have changes because of some files have been moved. - The reviewers should probably just focus on the MOVED/renamed (~30) files - New packages (top level only): -- org.onap.cps.ncmp.api.inventory -- org.onap.cps.ncmp.impl.inventory -- org.onap.cps.ncmp.impl.models (common models NOT used in API, this was unforeseen) -- org.onap.cps.ncmp.impl.utils - I am NOT updating copyright years as part of this commit since there are no real code changes and it is simply NOT worth the effort TODO: - extend scope to 'TrustLevel' as an 'internal feature' (TBD) Issue-ID: CPS-2255 Signed-off-by: ToineSiebelink Change-Id: I40b4efc8326068ab9ae1f31b77f46b2fe8579431 --- ...leRegistrationServicePropertyHandlerSpec.groovy | 296 ------------- .../impl/CmHandleRegistrationServiceSpec.groovy | 458 --------------------- .../impl/NetworkCmProxyInventoryFacadeSpec.groovy | 226 ---------- .../impl/NetworkCmProxyQueryServiceImplSpec.groovy | 49 --- .../ParameterizedCmHandleQueryServiceSpec.groovy | 222 ---------- .../ncmp/api/impl/WriteRequestExaminerSpec.groovy | 4 +- .../SynchronizationCacheConfigSpec.groovy | 142 ------- ...NotificationSubscriptionCacheHandlerSpec.groovy | 6 +- ...ficationSubscriptionDmiInEventMapperSpec.groovy | 6 +- .../LcmEventsCmHandleStateHandlerImplSpec.groovy | 22 +- .../impl/events/lcm/LcmEventsCreatorSpec.groovy | 16 +- .../inventory/CmHandleQueryServiceImplSpec.groovy | 206 --------- .../inventory/CompositeStateBuilderSpec.groovy | 92 ----- .../api/impl/inventory/CompositeStateSpec.groovy | 64 --- .../inventory/InventoryPersistenceImplSpec.groovy | 342 --------------- .../inventory/sync/DataSyncWatchdogSpec.groovy | 116 ------ .../sync/ModuleOperationsUtilsSpec.groovy | 225 ---------- .../inventory/sync/ModuleSyncServiceSpec.groovy | 171 -------- .../impl/inventory/sync/ModuleSyncTasksSpec.groovy | 225 ---------- .../inventory/sync/ModuleSyncWatchdogSpec.groovy | 121 ------ .../config/WatchdogSchedulingConfigurerSpec.groovy | 41 -- .../sync/executor/AsyncTaskExecutorSpec.groovy | 62 --- .../impl/operations/DmiDataOperationsSpec.groovy | 24 +- .../impl/operations/DmiOperationsBaseSpec.groovy | 8 +- .../impl/trustlevel/TrustLevelManagerSpec.groovy | 4 +- .../dmiavailability/DmiPluginWatchDogSpec.groovy | 2 +- .../api/impl/utils/AlternateIdCheckerSpec.groovy | 6 +- .../impl/utils/CmHandleQueryConditionsSpec.groovy | 36 -- .../ncmp/api/impl/utils/DataNodeBaseSpec.groovy | 4 +- .../ncmp/api/impl/utils/DataNodeHelperSpec.groovy | 4 +- .../impl/utils/InventoryQueryConditionsSpec.groovy | 36 -- .../utils/RestQueryParametersValidatorSpec.groovy | 124 ------ .../api/impl/utils/YangDataConverterSpec.groovy | 3 +- .../ResourceDataOperationRequestUtilsSpec.groovy | 13 +- .../impl/yangmodels/YangModelCmHandleSpec.groovy | 115 ------ .../models/CmHandleRegistrationResponseSpec.groovy | 102 +++++ .../models/CompositeStateBuilderSpec.groovy | 91 ++++ .../api/inventory/models/CompositeStateSpec.groovy | 64 +++ .../api/inventory/models/YangResourceTest.groovy | 21 + .../models/CmHandleRegistrationResponseSpec.groovy | 101 ----- .../cps/ncmp/api/models/YangResourceTest.groovy | 20 - .../CmHandleQueryParametersValidatorSpec.groovy | 124 ++++++ .../inventory/CmHandleQueryServiceImplSpec.groovy | 208 ++++++++++ ...leRegistrationServicePropertyHandlerSpec.groovy | 295 +++++++++++++ .../CmHandleRegistrationServiceSpec.groovy | 455 ++++++++++++++++++++ .../inventory/InventoryPersistenceImplSpec.groovy | 345 ++++++++++++++++ .../NetworkCmProxyInventoryFacadeSpec.groovy | 223 ++++++++++ .../NetworkCmProxyQueryServiceImplSpec.groovy | 50 +++ .../ParameterizedCmHandleQueryServiceSpec.groovy | 220 ++++++++++ .../models/CmHandleQueryConditionsSpec.groovy | 37 ++ .../models/InventoryQueryConditionsSpec.groovy | 37 ++ .../inventory/models/YangModelCmHandleSpec.groovy | 113 +++++ .../inventory/sync/AsyncTaskExecutorSpec.groovy | 63 +++ .../inventory/sync/DataSyncWatchdogSpec.groovy | 116 ++++++ .../sync/ModuleOperationsUtilsSpec.groovy | 225 ++++++++++ .../inventory/sync/ModuleSyncServiceSpec.groovy | 170 ++++++++ .../impl/inventory/sync/ModuleSyncTasksSpec.groovy | 226 ++++++++++ .../inventory/sync/ModuleSyncWatchdogSpec.groovy | 121 ++++++ .../sync/SynchronizationCacheConfigSpec.groovy | 143 +++++++ .../sync/WatchdogSchedulingConfigurerSpec.groovy | 42 ++ .../ncmp/impl/utils/AlternateIdMatcherSpec.groovy | 66 +++ .../init/CmDataSubscriptionModelLoaderSpec.groovy | 2 +- .../cps/ncmp/init/InventoryModelLoaderSpec.groovy | 4 +- .../cps/ncmp/utils/AlternateIdMatcherSpec.groovy | 66 --- 64 files changed, 3622 insertions(+), 3619 deletions(-) delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyInventoryFacadeSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/ParameterizedCmHandleQueryServiceSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/AlternateIdMatcherSpec.groovy (limited to 'cps-ncmp-service/src/test/groovy') diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy deleted file mode 100644 index 5eac44b8f2..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServicePropertyHandlerSpec.groovy +++ /dev/null @@ -1,296 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * Modifications Copyright (C) 2024 TechMahindra Ltd. - * ================================================================================ - * 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 - -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.read.ListAppender -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.spi.exceptions.DataNodeNotFoundException -import org.onap.cps.spi.exceptions.DataValidationException -import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.utils.ContentType -import org.onap.cps.utils.JsonObjectMapper -import org.slf4j.LoggerFactory -import spock.lang.Specification - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status - -class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { - - def mockInventoryPersistence = Mock(InventoryPersistence) - def mockCpsDataService = Mock(CpsDataService) - def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def mockAlternateIdChecker = Mock(AlternateIdChecker) - - def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) - def logger = Spy(ListAppender) - - void setup() { - def setupLogger = ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)) - setupLogger.addAppender(logger) - setupLogger.setLevel(Level.DEBUG) - logger.start() - // Always accept all alternate IDs - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] - } - - void cleanup() { - ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)).detachAndStopAllAppenders() - } - - def static cmHandleId = 'myHandle1' - def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}']" - - def static propertyDataNodes = [new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp1']").withLeaves(['name': 'additionalProp1', 'value': 'additionalValue1']).build(), - new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(), - new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(), - new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()] - def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes, leaves: ['id': cmHandleId])] - - def 'Update CM Handle Public Properties: #scenario'() { - given: 'the CPS service return a CM handle' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection - and: 'an update cm handle request with public properties updates' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)] - when: 'update data node leaves is called with the update request' - objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'the replace list method is called with correct params' - 1 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> - { - assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() - assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) - } - } - where: 'following public properties updates are made' - scenario | updatedPublicProperties || expectedPropertiesAfterUpdate - 'property added' | ['newPubProp1': 'pub-val'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4'], ['newPubProp1': 'pub-val']] - 'property updated' | ['publicProp4': 'newPubVal'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'newPubVal']] - 'property removed' | ['publicProp4': null] || [['publicProp3': 'publicValue3']] - 'property ignored(value is null)' | ['pub-prop': null] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4']] - } - - def 'Update DMI Properties: #scenario'() { - given: 'the CPS service return a CM handle' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection - and: 'an update cm handle request with DMI properties updates' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)] - when: 'update data node leaves is called with the update request' - objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'replace list method should is called with correct params' - expectedCallsToReplaceMethod * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> - { - assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() - assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) - } - } - where: 'following DMI properties updates are made' - scenario | updatedDmiProperties || expectedPropertiesAfterUpdate | expectedCallsToReplaceMethod - 'property added' | ['newAdditionalProp1': 'add-value'] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2'], ['newAdditionalProp1': 'add-value']] | 1 - 'property updated' | ['additionalProp1': 'newValue'] || [['additionalProp2': 'additionalValue2'], ['additionalProp1': 'newValue']] | 1 - 'property removed' | ['additionalProp1': null] || [['additionalProp2': 'additionalValue2']] | 1 - 'property ignored(value is null)' | ['new-prop': null] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 1 - 'no property changes' | [:] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 0 - } - - def 'Update CM Handle Properties, remove all properties: #scenario'() { - given: 'the CPS service return a CM handle' - def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId], childDataNodes: originalPropertyDataNodes) - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [cmHandleDataNode] - and: 'an update cm handle request that removes all public properties(existing and non-existing)' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])] - when: 'update data node leaves is called with the update request' - objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'the replace list method is not called' - 0 * mockInventoryPersistence.replaceListContent(*_) - then: 'delete data node will be called for any existing property' - expectedCallsToDeleteDataNode * mockInventoryPersistence.deleteDataNode(_) >> { arg -> - { - assert arg[0].contains("@name='publicProp") - } - } - where: 'following public properties updates are made' - scenario | originalPropertyDataNodes || expectedCallsToDeleteDataNode - '2 original properties, both removed' | propertyDataNodes || 2 - 'no original properties' | [] || 0 - } - - def '#scenario error leads to #exception when we try to update cmHandle'() { - given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: [:], dmiProperties: [:])] - and: 'data node cannot be found' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> { throw exception } - when: 'update data node leaves is called using correct parameters' - def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'one failed registration response' - response.size() == 1 - and: 'it has expected error details' - with(response.get(0)) { - assert it.status == Status.FAILURE - assert it.cmHandle == cmHandleId - assert it.ncmpResponseStatus == expectedError - assert it.errorText == expectedErrorText - } - where: - scenario | cmHandleId | exception || expectedError | expectedErrorText - 'Cm Handle does not exist' | 'cmHandleId' | new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' - 'Unknown' | 'cmHandleId' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' - 'Invalid cm handle id' | 'cmHandleId with spaces' | new DataValidationException('Name Validation Error.', cmHandleId + 'contains an invalid character') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' - } - - def 'Multiple update operations in a single request'() { - given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), - new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), - new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])] - and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle' - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> cmHandleDataNodeAsCollection >> { - throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) } >> cmHandleDataNodeAsCollection - when: 'update data node leaves is called using correct parameters' - def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'response has 3 values' - cmHandleResponseList.size() == 3 - and: 'the 1st and 3rd requests were processed successfully' - with(cmHandleResponseList.get(0)) { - assert it.status == Status.SUCCESS - assert it.cmHandle == cmHandleId - } - with(cmHandleResponseList.get(2)) { - assert it.status == Status.SUCCESS - assert it.cmHandle == cmHandleId - } - and: 'the 2nd request failed with correct error code' - with(cmHandleResponseList.get(1)) { - assert it.status == Status.FAILURE - assert it.cmHandle == cmHandleId - assert it.ncmpResponseStatus == CM_HANDLES_NOT_FOUND - assert it.errorText == 'cm handle id(s) not found' - } - then: 'the replace list method is called twice' - 2 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) - } - - def 'Update alternate id of existing CM Handle.'() { - given: 'cm handles request' - def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] - and: 'a data node found' - def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] - when: 'cm handle properties is updated' - def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) - then: 'the update is delegated to cps data service with correct parameters' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> - { args -> - assert args[3].contains('alt-1') - } - and: 'one successful registration response' - response.size() == 1 - and: 'the response shows success for the given cm handle id' - assert response[0].status == Status.SUCCESS - assert response[0].cmHandle == cmHandleId - } - - def 'Update with rejected alternate id.'() { - given: 'cm handles request' - def updatedNcmpServiceCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] - and: 'a data node found' - def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) - mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] - when: 'attempt to update the cm handle' - def response = objectUnderTest.updateCmHandleProperties(updatedNcmpServiceCmHandles) - then: 'the update is NOT delegated to cps data service' - 0 * mockCpsDataService.updateNodeLeaves(*_) - and: 'the alternate id checker rejects the given cm handle (override default setup behavior)' - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [cmHandleId] - and: 'the response shows a failure for the given cm handle id' - assert response[0].status == Status.FAILURE - assert response[0].cmHandle == cmHandleId - } - - def 'Update CM Handle data producer identifier from #scenario'() { - given: 'an existing cm handle with no data producer identifier' - DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': oldDataProducerIdentifier]) - and: 'an update request with a new data producer identifier' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'New Data Producer Identifier') - when: 'data producer identifier updated' - objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) - then: 'the update node leaves method is invoked once' - 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> - assert args[3].contains('New Data Producer Identifier') - } - and: 'correct information is logged' - def lastLoggingEvent = logger.list[0] - assert lastLoggingEvent.level == Level.DEBUG - assert lastLoggingEvent.formattedMessage.contains('Updating data-producer-identifier') - where: 'the following scenarios are attempted' - scenario | oldDataProducerIdentifier - 'null to something' | null - 'blank to something' | '' - } - - def 'Update CM Handle data producer identifier with same value'() { - given: 'an existing cm handle with no data producer identifier' - DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': 'same id']) - and: 'an update request with a new data producer identifier' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'same id') - when: 'data producer identifier updated' - objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) - then: 'the update node leaves method is not invoked' - 0 * mockCpsDataService.updateNodeLeaves(*_) - } - - def 'Update CM Handle data producer identifier from some data producer identifier to another data producer identifier'() { - given: 'an existing cm handle with a data producer identifier' - DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId', 'data-producer-identifier': 'someDataProducerIdentifier']) - and: 'an update request with a new data producer identifier' - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'someNewDataProducerIdentifier') - when: 'update data producer identifier is called with the update request' - objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) - then: 'the update node leaves method is not invoked' - 0 * mockCpsDataService.updateNodeLeaves(*_) - and: 'correct information is logged' - def lastLoggingEvent = logger.list[0] - assert lastLoggingEvent.level == Level.WARN - assert lastLoggingEvent.formattedMessage.contains('Unable to update dataProducerIdentifier') - } - - def convertToProperties(expectedPropertiesAfterUpdateAsMap) { - def properties = [].withDefault { [:] } - expectedPropertiesAfterUpdateAsMap.forEach(property -> - property.forEach((key, val) -> { - properties.add(['name': key, 'value': val]) - })) - return properties - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy deleted file mode 100644 index 1c6e38420b..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/CmHandleRegistrationServiceSpec.groovy +++ /dev/null @@ -1,458 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl - -import com.hazelcast.map.IMap -import org.onap.cps.api.CpsDataService -import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler -import org.onap.cps.ncmp.api.impl.exception.DmiRequestException -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager -import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse -import org.onap.cps.ncmp.api.models.DmiPluginRegistration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.api.models.UpgradedCmHandles -import org.onap.cps.spi.exceptions.AlreadyDefinedException -import org.onap.cps.spi.exceptions.CpsException -import org.onap.cps.spi.exceptions.DataNodeNotFoundException -import org.onap.cps.spi.exceptions.DataValidationException -import org.onap.cps.spi.exceptions.SchemaSetNotFoundException -import spock.lang.Specification - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status - -class CmHandleRegistrationServiceSpec extends Specification { - - def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') - def mockCpsModuleService = Mock(CpsModuleService) - def mockNetworkCmProxyDataServicePropertyHandler = Mock(CmHandleRegistrationServicePropertyHandler) - def mockInventoryPersistence = Mock(InventoryPersistence) - def mockCmHandleQueries = Mock(CmHandleQueryService) - def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) - def mockCpsDataService = Mock(CpsDataService) - def mockModuleSyncStartedOnCmHandles = Mock(IMap) - def trustLevelPerDmiPlugin = [:] - def mockTrustLevelManager = Mock(TrustLevelManager) - def mockAlternateIdChecker = Mock(AlternateIdChecker) - - def objectUnderTest = Spy(new CmHandleRegistrationService( - mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, - mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin , mockTrustLevelManager, mockAlternateIdChecker)) - - def setup() { - // always accept all cm handles - mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] - - // always can find all cm handles in DB - mockInventoryPersistence.getYangModelCmHandles(_) >> { args -> args[0].collect { new YangModelCmHandle(id:it) } } - } - - def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'() { - given: 'a registration with operations of all types' - def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) - dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) - dmiRegistration.setRemovedCmHandles(['cmhandle-2']) - dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) - and: 'cm handles are persisted' - mockInventoryPersistence.getYangModelCmHandles(['cmhandle-2']) >> [new YangModelCmHandle()] - mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY)) - and: 'cm handle is in READY state' - mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true - when: 'registration is processed' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) - then: 'cm-handles are removed first' - 1 * objectUnderTest.processRemovedCmHandles(*_) - and: 'de-registered cm handle entry is removed from in progress map' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2') - then: 'cm-handles are created' - 1 * objectUnderTest.processCreatedCmHandles(*_) - then: 'cm-handles are updated' - 1 * objectUnderTest.processUpdatedCmHandles(*_) - 1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> [] - then: 'cm-handles are upgraded' - 1 * objectUnderTest.processUpgradedCmHandles(*_) - } - - def 'DMI Registration upgrade operation with upgrade node state #scenario'() { - given: 'a registration with upgrade operation' - def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) - and: 'exception while checking cm handle state' - mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) - when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) - then: 'upgrade operation contains expected error code' - assert result.upgradedCmHandles[0].status == expectedResponseStatus - where: 'the following parameters are used' - scenario | cmHandleState || expectedResponseStatus - 'READY' | CmHandleState.READY || Status.SUCCESS - 'Not READY' | CmHandleState.LOCKED || Status.FAILURE - } - - def 'DMI Registration upgrade with exception #scenario'() { - given: 'a registration with upgrade operation' - def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) - and: 'exception while checking cm handle state' - mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } - when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) - then: 'upgrade operation contains expected error code' - assert result.upgradedCmHandles.ncmpResponseStatus.code[0] == expectedErrorCode - where: 'the following parameters are used' - scenario | exception || expectedErrorCode - 'data node not found' | new DataNodeNotFoundException('some-dataspace-name', 'some-anchor-name') || '100' - 'cm handle is invalid' | new DataValidationException('some error message', 'some error details') || '110' - } - - def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() { - given: 'a registration ' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, - dmiDataPlugin: dmiDataPlugin) - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - when: 'update registration and sync module is called with correct DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'create cm handles registration and sync modules is called with the correct plugin information' - 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) - and: 'dmi is added to the dmi trustLevel map' - assert trustLevelPerDmiPlugin.size() == 1 - assert trustLevelPerDmiPlugin.containsKey(expectedDmiPluginRegisteredName) - where: - scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedDmiPluginRegisteredName - 'combined DMI plugin' | 'service1' | '' | '' || 'service1' - 'data & model DMI plugins' | '' | 'service1' | 'service2' || 'service2' - 'data & model using same service' | '' | 'service1' | 'service1' || 'service1' - } - - def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() { - given: 'a registration ' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, - dmiDataPlugin: dmiDataPlugin) - dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] - when: 'registration is called with incorrect DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a DMI Request Exception is thrown with correct message details' - def exceptionThrown = thrown(DmiRequestException.class) - assert exceptionThrown.getMessage().contains(expectedMessageDetails) - and: 'registration is not called' - 0 * objectUnderTest.processCreatedCmHandles(*_) - where: - scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails - 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names' - 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names' - 'null DMI plugins' | null | null | null || 'No DMI plugin service names' - 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names' - '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names' - '(combined)DMI and model Plugin' | 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names' - 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name' - 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name' - } - - def 'Create CM-Handle Successfully: #scenario.'() { - given: 'a registration without cm-handle properties' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] - when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a successful response is received' - response.createdCmHandles.size() == 1 - with(response.createdCmHandles[0]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle' - } - and: 'state handler is invoked with the expected parameters' - 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> { - args -> { - def yangModelCmHandles = args[0] - assert yangModelCmHandles.id == ['cmhandle'] - assert yangModelCmHandles.dmiServiceName == ['my-server'] - } - } - where: - scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties - 'with dmi & public properties' | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]' - 'with only public properties' | [:] | ['public-key': 'public-value'] || [:] | '[{"name":"public-key","value":"public-value"}]' - 'with only dmi properties' | ['dmi-key': 'dmi-value'] | [:] || '[{"name":"dmi-key","value":"dmi-value"}]' | [:] - 'without dmi & public properties' | [:] | [:] || [:] | [:] - } - - def 'Add CM-Handle #scenario.'() { - given: ' registration details for one cm handles' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - createdCmHandles:[new NcmpServiceCmHandle(cmHandleId: 'ch-1', registrationTrustLevel: registrationTrustLevel)]) - when: 'registration is updated' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'trustLevel is set for the created cm-handle' - 1 * mockTrustLevelManager.handleInitialRegistrationOfTrustLevels(expectedMapping) - where: - scenario | registrationTrustLevel || expectedMapping - 'with trusted cm handle' | TrustLevel.COMPLETE || [ 'ch-1' : TrustLevel.COMPLETE ] - 'without trust level' | null || [ 'ch-1' : null ] - } - - def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed with some failures'() { - given: 'a registration with three cm-handles to be created' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'), - new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), - new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) - and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' - def xpath = "somePathWithId[@id='cmhandle2']" - mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw AlreadyDefinedException.forDataNodes([xpath], 'some-context') } - when: 'registration is updated to create cm-handles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a response is received for all cm-handles' - response.createdCmHandles.size() == 1 - and: 'all cm-handles creation fails' - response.createdCmHandles.each { - assert it.cmHandle == 'cmhandle2' - assert it.status == Status.FAILURE - assert it.ncmpResponseStatus == CM_HANDLE_ALREADY_EXIST - assert it.errorText == 'cm-handle already exists' - } - } - - def 'Create CM-Handle Error Handling: Registration fails: #scenario'() { - given: 'a registration without cm-handle properties' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') - dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle')] - and: 'cm-handler registration fails: #scenario' - mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw exception } - when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a failure response is received' - response.createdCmHandles.size() == 1 - with(response.createdCmHandles[0]) { - assert it.status == Status.FAILURE - assert it.cmHandle == 'cmhandle' - assert it.ncmpResponseStatus == expectedError - assert it.errorText == expectedErrorText - } - where: - scenario | exception || expectedError | expectedErrorText - 'cm-handle already exist' | AlreadyDefinedException.forDataNodes(["path[@id='cmhandle']"], 'some-context') || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' - 'unknown exception while registering cm-handle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' - } - - def 'Update CM-Handle: Update Operation Response is added to the response'() { - given: 'a registration to update CmHandles' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', updatedCmHandles: [{}]) - and: 'cm-handle updates can be processed successfully' - def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'), - CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")), - CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLES_NOT_FOUND), - CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)] - mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse - when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'the response contains updateOperationResponse' - assert response.updatedCmHandles.size() == 4 - assert response.updatedCmHandles.containsAll(updateOperationResponse) - } - - def 'Remove CmHandle Successfully: #scenario'() { - given: 'a registration' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', removedCmHandles: ['cmhandle']) - and: '#scenario' - mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >> { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } } - when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'the cmHandle state is updated to "DELETING"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> - { args -> args[0].values()[0] == CmHandleState.DELETING } - then: 'method to delete relevant schema set is called once' - 1 * mockInventoryPersistence.deleteSchemaSetsWithCascade(_) - and: 'method to delete relevant list/list element is called once' - 1 * mockInventoryPersistence.deleteDataNodes(_) - and: 'successful response is received' - assert response.removedCmHandles.size() == 1 - with(response.removedCmHandles[0]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle' - } - and: 'the cmHandle state is updated to "DELETED"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> - { args -> args[0].values()[0] == CmHandleState.DELETED } - and: 'No cm handles state updates for "upgraded cm handles"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) - where: - scenario | schemaSetExist - 'schema-set exists and can be deleted successfully' | true - 'schema-set does not exist' | false - } - - def 'Remove CmHandle: Partial Success'() { - given: 'a registration with three cm-handles to be deleted' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3']) - and: 'cm-handle deletion fails on batch' - mockInventoryPersistence.deleteDataNodes(_) >> { throw new RuntimeException("Failed") } - and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' - mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") } - when: 'registration is updated to delete cmhandles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'the cmHandle states are all updated to "DELETING"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ assert it.every { entry -> entry.value == CmHandleState.DELETING } }) - and: 'a response is received for all cm-handles' - response.removedCmHandles.size() == 3 - and: 'successfully de-registered cm handle 1 is removed from in progress map' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle1') - and: 'successfully de-registered cm handle 3 is removed from in progress map even though it was already being removed' - 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle3') >> 'already in progress' - and: 'failed de-registered cm handle entries should NOT be removed from in progress map' - 0 * mockModuleSyncStartedOnCmHandles.remove('cmhandle2') - and: '1st and 3rd cm-handle deletes successfully' - with(response.removedCmHandles[0]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle1' - } - with(response.removedCmHandles[2]) { - assert it.status == Status.SUCCESS - assert it.cmHandle == 'cmhandle3' - } - and: '2nd cm-handle deletion fails' - with(response.removedCmHandles[1]) { - assert it.status == Status.FAILURE - assert it.ncmpResponseStatus == UNKNOWN_ERROR - assert it.errorText == 'Failed' - assert it.cmHandle == 'cmhandle2' - } - and: 'the cmHandle state is updated to DELETED for 1st and 3rd' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ - assert it.size() == 2 - assert it.every { entry -> entry.value == CmHandleState.DELETED } - }) - and: 'No cm handles state updates for "upgraded cm handles"' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) - - } - - def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() { - given: 'a registration' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - removedCmHandles: ['cmhandle']) - and: 'schema set batch deletion failed with unknown error' - mockInventoryPersistence.deleteSchemaSetsWithCascade(_) >> { throw new RuntimeException('Failed') } - and: 'schema set single deletion failed with unknown error' - mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') } - when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'no exception is thrown' - noExceptionThrown() - and: 'cm-handle is not deleted' - 0 * mockInventoryPersistence.deleteDataNodes(_) - and: 'the cmHandle state is not updated to "DELETED"' - 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([yangModelCmHandle: CmHandleState.DELETED]) - and: 'a failure response is received' - assert response.removedCmHandles.size() == 1 - with(response.removedCmHandles[0]) { - assert it.status == Status.FAILURE - assert it.cmHandle == 'cmhandle' - assert it.errorText == 'Failed' - assert it.ncmpResponseStatus == UNKNOWN_ERROR - } - } - - def 'Remove CmHandle Error Handling: #scenario'() { - given: 'a registration' - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', - removedCmHandles: ['cmhandle']) - and: 'cm-handle deletion fails on batch' - mockInventoryPersistence.deleteDataNodes(_) >> { throw deleteListElementException } - and: 'cm-handle deletion fails on individual delete' - mockInventoryPersistence.deleteDataNode(_) >> { throw deleteListElementException } - when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'a failure response is received' - assert response.removedCmHandles.size() == 1 - with(response.removedCmHandles[0]) { - assert it.status == Status.FAILURE - assert it.cmHandle == 'cmhandle' - assert it.ncmpResponseStatus == expectedError - assert it.errorText == expectedErrorText - } - and: 'the cm handle state is not updated to "DELETED"' - 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_, CmHandleState.DELETED) - where: - scenario | cmHandleId | deleteListElementException || expectedError | expectedErrorText - 'cm-handle does not exist' | 'cmhandle' | new DataNodeNotFoundException('', '', '') || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' - 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException('', '') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' - 'an unexpected exception' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' - } - - def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() { - given: 'an existing cm handle composite state' - def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag, - dataStores: CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder() - .dataStoreSyncState(initialDataSyncState) - .build()).build()) - and: 'get cm handle state returns the composite state for the given cm handle id' - mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState - when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag' - objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) - then: 'the data sync enabled flag is set to #dataSyncEnabled' - compositeState.dataSyncEnabled == dataSyncEnabledFlag - and: 'the data store sync state is set to #expectedDataStoreSyncState' - compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState - and: 'the cps data service to delete data nodes is invoked the expected number of times' - deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _) - and: 'the inventory persistence service to update node leaves is called with the correct values' - saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState) - where: 'the following data sync enabled flag is used' - scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations - 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1 - 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1 - 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1 - 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0 - } - - def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () { - given: 'a cm handle composite state' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false) - and: 'get cm handle state returns the composite state for the given cm handle id' - mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState - when: 'set data sync enabled is called with the data sync enabled flag set to true' - objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true) - then: 'the expected exception is thrown' - thrown(CpsException) - and: 'the inventory persistence service to update node leaves is not invoked' - 0 * mockInventoryPersistence.saveCmHandleState(_, _) - } - - - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyInventoryFacadeSpec.groovy deleted file mode 100644 index f704dbb3d6..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyInventoryFacadeSpec.groovy +++ /dev/null @@ -1,226 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. - * ================================================================================ - * 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 - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.ParameterizedCmHandleQueryService -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters -import org.onap.cps.ncmp.api.models.ConditionApiProperties -import org.onap.cps.ncmp.api.models.DmiPluginRegistration -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.spi.model.ConditionProperties -import org.onap.cps.utils.JsonObjectMapper -import spock.lang.Specification - -import java.util.stream.Collectors - -class NetworkCmProxyInventoryFacadeSpec extends Specification { - - def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService) - def mockCmHandleQueryService = Mock(CmHandleQueryService) - def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService) - def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - def mockInventoryPersistence = Mock(InventoryPersistence) - - def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper) - - def 'Update DMI Registration'() { - given: 'an (updated) dmi plugin registration' - def dmiPluginRegistration = Mock(DmiPluginRegistration) - when: 'the registration is submitted ' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - then: 'the call is delegated to the cm handle registration service' - 1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - } - - def 'Execute cm handle id search for inventory'() { - given: 'a ConditionApiProperties object' - def conditionProperties = new ConditionProperties() - conditionProperties.conditionName = 'hasAllProperties' - conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ] - def cmHandleQueryServiceParameters = new CmHandleQueryServiceParameters() - cmHandleQueryServiceParameters.cmHandleQueryParameters = [conditionProperties] as List - and: 'the system returns an set of cmHandle ids' - mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] - when: 'executing the search' - def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) - then: 'the result returns the correct 2 elements' - assert result.size() == 2 - assert result.contains('cmHandle1') - assert result.contains('cmHandle2') - } - - def 'Get all cm handle IDs by DMI plugin identifier.' () { - given: 'cm handle queries service returns cm handles' - 1 * mockCmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2'] - when: 'cm handle Ids are requested with dmi plugin identifier' - def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') - then: 'the result size is correct' - assert result.size() == 2 - and: 'the result returns the correct details' - assert result.containsAll('cm-handle-1','cm-handle-2') - } - - def 'Getting Yang Resources.'() { - when: 'yang resources is called' - objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') - then: 'CPS module services is invoked for the correct dataspace and cm handle' - 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle') - } - - def 'Get a cm handle.'() { - given: 'the system returns a yang modelled cm handle' - def dmiServiceName = 'some service name' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: 'some-timestamp', - dataSyncEnabled: false, - dataStores: dataStores()) - def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] - def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] - def moduleSetTag = 'some-module-set-tag' - def alternateId = 'some-alternate-id' - def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, - dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, - moduleSetTag: moduleSetTag, alternateId: alternateId) - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle details for a given cm handle id from ncmp service' - def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle') - then: 'the result is a ncmpServiceCmHandle' - result.class == NcmpServiceCmHandle.class - and: 'the cm handle contains the cm handle id' - result.cmHandleId == 'some-cm-handle' - and: 'the cm handle contains the alternate id' - result.alternateId == 'some-alternate-id' - and: 'the cm handle contains the module-set-tag' - result.moduleSetTag == 'some-module-set-tag' - and: 'the cm handle contains the DMI Properties' - result.dmiProperties ==[ Book:'Romance Novel' ] - and: 'the cm handle contains the public Properties' - result.publicProperties == [ "Public Book":'Public Romance Novel' ] - and: 'the cm handle contains the cm handle composite state' - result.compositeState == compositeState - } - - def 'Get cm handle public properties'() { - given: 'a yang modelled cm handle' - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) - and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle public properties for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle') - then: 'the result returns the correct data' - result == [ 'public prop' : 'some public prop' ] - } - - def 'Get cm handle composite state'() { - given: 'a yang modelled cm handle' - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: 'some-timestamp', - dataSyncEnabled: false, - dataStores: dataStores()) - def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] - def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] - def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) - and: 'the system returns this yang modelled cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle - when: 'getting cm handle composite state for a given cm handle id from ncmp service' - def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') - then: 'the result returns the correct data' - result == compositeState - } - - def 'Execute cm handle id search'() { - given: 'valid CmHandleQueryApiParameters input' - def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() - def conditionApiProperties = new ConditionApiProperties() - conditionApiProperties.conditionName = 'hasAllModules' - conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] - cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] - and: 'query cm handle method return with a data node list' - mockParameterizedCmHandleQueryService.queryCmHandleIds( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> ['cm-handle-id-1'] - when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) - then: 'result is the same collection as returned by the CPS Data Service' - assert result == ['cm-handle-id-1'] - } - - def 'Getting module definitions by module'() { - when: 'get module definitions is performed with module name' - objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') - then: 'ncmp inventory persistence service is invoked once with correct parameters' - 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') - } - - def 'Getting module definitions by cm handle id'() { - when: 'get module definitions is performed with cm handle id' - objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') - then: 'ncmp inventory persistence service is invoked once with correct parameter' - 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') - } - - def 'Execute cm handle search'() { - given: 'valid CmHandleQueryApiParameters input' - def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() - def conditionApiProperties = new ConditionApiProperties() - conditionApiProperties.conditionName = 'hasAllModules' - conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] - cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] - and: 'query cm handle method return with a data node list' - mockParameterizedCmHandleQueryService.queryCmHandles( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')] - when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) - then: 'result is the same collection as returned by the CPS Data Service' - assert result.stream().map(cmHandle -> cmHandle.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set - } - - def 'Set Cm Handle Data Sync flag.'() { - when: 'setting data sync enabled flag' - objectUnderTest.setDataSyncEnabled('ch-1',true) - then: 'call is delegated to the cm handle registration service' - mockCmHandleRegistrationService.setDataSyncEnabled('ch-1', true) - } - - def dataStores() { - CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder() - .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) - .lastSyncTime('some-timestamp').build()).build() - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy deleted file mode 100644 index 5f4f248c65..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyQueryServiceImplSpec.groovy +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ============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.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR - -import org.onap.cps.api.CpsQueryService -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.DataNode -import spock.lang.Specification - -class NetworkCmProxyQueryServiceImplSpec extends Specification { - - def mockCpsQueryService = Mock(CpsQueryService) - - def objectUnderTest = new NetworkCmProxyQueryServiceImpl(mockCpsQueryService) - - def 'Query resource data for operational from DMI.'() { - given: 'a list of datanodes' - def dataNodes = [new DataNode(xpath: '/cps/path'), new DataNode(xpath: '/cps/path/child')] - and: 'the list of datanodes is returned for query data node' - 1 * mockCpsQueryService.queryDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNodes - when: 'query resource data operational for cm-handle is called' - def response = objectUnderTest.queryResourceDataOperational(NCMP_DMI_REGISTRY_ANCHOR, - '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) - then: 'the expected datanodes are returned from the DMI' - response == dataNodes - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/ParameterizedCmHandleQueryServiceSpec.groovy deleted file mode 100644 index a928a0432c..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/ParameterizedCmHandleQueryServiceSpec.groovy +++ /dev/null @@ -1,222 +0,0 @@ -/* - * ============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.api.impl - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT - -import org.onap.cps.cpspath.parser.PathParsingException -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.exceptions.DataInUseException -import org.onap.cps.spi.exceptions.DataValidationException -import org.onap.cps.spi.model.ConditionProperties -import org.onap.cps.spi.model.DataNode -import spock.lang.Specification - -class ParameterizedCmHandleQueryServiceSpec extends Specification { - - def cmHandleQueries = Mock(CmHandleQueryService) - def partiallyMockedCmHandleQueries = Spy(CmHandleQueryService) - def mockInventoryPersistence = Mock(InventoryPersistence) - - def dmiRegistry = new DataNode(xpath: NCMP_DMI_REGISTRY_PARENT, childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'])) - - def objectUnderTest = new ParameterizedCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence) - def objectUnderTestWithPartiallyMockedQueries = new ParameterizedCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence) - - def 'Query cm handle ids with cpsPath.'() { - given: 'a cmHandleWithCpsPath condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'the query get the cm handle datanodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set - } - - def 'Cm handle ids query with error: #scenario.'() { - given: 'a cmHandleWithCpsPath condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'cmHandleQueries throws a path parsing exception' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException } - when: 'the query is executed for cm handle ids' - objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'a data validation exception is thrown' - thrown(expectedException) - where: 'the following data is used' - scenario | thrownException || expectedException - 'PathParsingException' | new PathParsingException('some message', 'some details') || DataValidationException - 'any other Exception' | new DataInUseException('some message', 'some details') || DataInUseException - } - - def 'Cm handle ids cpsPath query for private properties (not allowed).'() { - given: 'a CpsPath condition property for private properties' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'empty result is returned' - assert result.isEmpty() - } - - def 'Query cm handle ids with module names when #scenario from query.'() { - given: 'a modules condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService - and: 'the correct expected cm handles ids are returned' - assert result.size() == cmHandleIdsFromService.size() - assert result.containsAll(cmHandleIdsFromService) - where: 'the following data is used' - scenario | cmHandleIdsFromService - 'One anchor returned' | ['some-cmhandle-id'] - 'No anchors are returned' | [] - } - - def 'Query cm handles with some trust level query parameters'() { - given: 'a trust level condition property' - def trustLevelQueryParameters = new CmHandleQueryServiceParameters() - def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map]) - trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties]) - when: 'the query is being executed' - objectUnderTest.queryCmHandleIds(trustLevelQueryParameters) - then: 'the query is being delegated to the cm handle query service with correct parameter' - 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map) - } - - def 'Query cm handle details with module names when #scenario from query.'() { - given: 'a modules condition property' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) - then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1'] - and: 'the inventory service is called with teh correct if and returns a yang model cm handle' - 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> - [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] - and: 'the expected cm handle(s) are returned as NCMP Service cm handles' - assert result[0] instanceof NcmpServiceCmHandle - assert result.size() == 1 - assert result[0].dmiProperties == [name:'value'] - } - - def 'Query cm handle ids when the query is empty.'() { - given: 'We use an empty query' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - and: 'the inventory persistence returns the dmi registry datanode with just ids' - mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry] - when: 'the query is executed for both cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) - then: 'the correct expected cm handles are returned' - assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') - } - - def 'Query cm handle details when the query is empty.'() { - given: 'We use an empty query' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - and: 'the inventory persistence returns the dmi registry datanode with just ids' - mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT) >> [dmiRegistry] - when: 'the query is executed for both cm handle details' - def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) - then: 'the correct cm handles are returned' - assert result.size() == 4 - assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') - } - - def 'Query CMHandleId with #scenario.' () { - given: 'a query object created with #condition' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties(conditionName, [['some-key': 'some-value']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'the inventoryPersistence returns different CmHandleIds' - partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties - partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties - when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) - then: 'the expected number of results are returned.' - assert result.size() == expectedCmHandleIdsSize - where: 'the following data is used' - scenario | conditionName | cmHandlesWithMatchingPublicProperties | cmHandlesWithMatchingPrivateProperties || expectedCmHandleIdsSize - 'all properties, only public matching' | 'hasAllProperties' | ['h1', 'h2'] | null || 2 - 'all properties, no matching cm handles' | 'hasAllProperties' | [] | [] || 0 - 'additional properties, some matching cm handles' | 'hasAllAdditionalProperties' | [] | ['h1', 'h2'] || 2 - 'additional properties, no matching cm handles' | 'hasAllAdditionalProperties' | null | [] || 0 - } - - def 'Retrieve CMHandleIds by different DMI properties with #scenario.' () { - given: 'a query object created with dmi plugin as condition' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']]) - cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) - and: 'the inventoryPersistence returns different CmHandleIds' - partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult - when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) - then: 'the expected number of results are returned.' - assert result.size() == expectedCmHandleIdsSize - where: 'the following data is used' - scenario | cmHandleQueryResult || expectedCmHandleIdsSize - 'some matches' | ['h1','h2'] || 2 - 'no matches' | [] || 0 - } - - def 'Combine two query results where #scenario.'() { - when: 'two query results in the form of a map of NcmpServiceCmHandles are combined into a single query result' - def result = objectUnderTest.combineCmHandleQueryResults(firstQuery, secondQuery) - then: 'the returned result is the same as the expected result' - result == expectedResult - where: - scenario | firstQuery | secondQuery || expectedResult - 'two queries with unique and non unique entries exist' | ['PNFDemo', 'PNFDemo2'] | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo'] - 'the first query contains entries and second query is empty' | ['PNFDemo', 'PNFDemo2'] | [] || [] - 'the second query contains entries and first query is empty' | [] | ['PNFDemo', 'PNFDemo3'] || [] - 'the first query contains entries and second query is null' | ['PNFDemo', 'PNFDemo2'] | null || ['PNFDemo', 'PNFDemo2'] - 'the second query contains entries and first query is null' | null | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo', 'PNFDemo3'] - 'both queries are empty' | [] | [] || [] - 'both queries are null' | null | null || null - } - - def createConditionProperties(String conditionName, List> conditionParameters) { - return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters) - } - - def static createDataNodeList(dataNodeIds) { - def dataNodes =[] - dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) } - return dataNodes - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy index d5d6339422..84eb78b751 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/WriteRequestExaminerSpec.groovy @@ -3,7 +3,7 @@ package org.onap.cps.ncmp.impl.datajobs import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest import org.onap.cps.ncmp.api.datajobs.models.WriteOperation -import org.onap.cps.ncmp.utils.AlternateIdMatcher +import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher import org.onap.cps.spi.model.DataNode import spock.lang.Specification @@ -60,4 +60,4 @@ class WriteRequestExaminerSpec extends Specification { then: 'we get the operation ids in the expected order.' assert dmiWriteOperations.operationId == ['1', '2', '3'] } -} \ No newline at end of file +} 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 deleted file mode 100644 index 6d09df0d44..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy +++ /dev/null @@ -1,142 +0,0 @@ -/* - * ============LICENSE_START======================================================== - * Copyright (C) 2022-2024 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.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 -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration -import spock.lang.Specification -import spock.util.concurrent.PollingConditions -import java.util.concurrent.BlockingQueue -import java.util.concurrent.TimeUnit - -@SpringBootTest -@ContextConfiguration(classes = [SynchronizationCacheConfig]) -class SynchronizationCacheConfigSpec extends Specification { - - @Autowired - private BlockingQueue moduleSyncWorkQueue - - @Autowired - private IMap moduleSyncStartedOnCmHandles - - @Autowired - private IMap dataSyncSemaphores - - def 'Embedded (hazelcast) Caches for Module and Data Sync.'() { - expect: 'system is able to create an instance of the Module Sync Work Queue' - assert null != moduleSyncWorkQueue - and: 'system is able to create an instance of a map to hold cm handles which have started (and maybe finished) module sync' - assert null != moduleSyncStartedOnCmHandles - and: 'system is able to create an instance of a map to hold data sync semaphores' - assert null != dataSyncSemaphores - 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') - } - - def 'Verify configs for Distributed objects'(){ - given: 'the Module Sync Work Queue config' - def moduleSyncWorkQueueConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncWorkQueue').config - def moduleSyncDefaultWorkQueueConfig = moduleSyncWorkQueueConfig.queueConfigs.get('defaultQueueConfig') - and: 'the Module Sync Started Cm Handle Map config' - def moduleSyncStartedOnCmHandlesConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncStartedOnCmHandles').config - def moduleSyncStartedOnCmHandlesMapConfig = moduleSyncStartedOnCmHandlesConfig.mapConfigs.get('moduleSyncStartedConfig') - and: 'the Data Sync Semaphores Map config' - def dataSyncSemaphoresConfig = Hazelcast.getHazelcastInstanceByName('dataSyncSemaphores').config - def dataSyncSemaphoresMapConfig = dataSyncSemaphoresConfig.mapConfigs.get('dataSyncSemaphoresConfig') - expect: 'system created instance with correct config of Module Sync Work Queue' - assert moduleSyncDefaultWorkQueueConfig.backupCount == 1 - assert moduleSyncDefaultWorkQueueConfig.asyncBackupCount == 0 - and: 'Module Sync Started Cm Handle Map has the correct settings' - assert moduleSyncStartedOnCmHandlesMapConfig.backupCount == 1 - assert moduleSyncStartedOnCmHandlesMapConfig.asyncBackupCount == 0 - and: 'Data Sync Semaphore Map has the correct settings' - assert dataSyncSemaphoresMapConfig.backupCount == 1 - assert dataSyncSemaphoresMapConfig.asyncBackupCount == 0 - and: 'all instances are part of same cluster' - def testClusterName = 'cps-and-ncmp-test-caches' - assert moduleSyncWorkQueueConfig.clusterName == testClusterName - assert moduleSyncStartedOnCmHandlesConfig.clusterName == testClusterName - assert dataSyncSemaphoresConfig.clusterName == testClusterName - } - - 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) - then: 'the entry is present in the map' - assert moduleSyncStartedOnCmHandles.get('testKeyModuleSync') != null - and: 'the entry expires' - new PollingConditions().within(10) { - assert moduleSyncStartedOnCmHandles.get('testKeyModuleSync') == null - } - } - - def 'Time to Live Verify for Data Sync Semaphore'() { - when: 'the key is inserted with a TTL of 1 second' - dataSyncSemaphores.put('testKeyDataSync', Boolean.TRUE, 1, TimeUnit.SECONDS) - then: 'the entry is present in the map' - assert dataSyncSemaphores.get('testKeyDataSync') != null - and: 'the entry expires' - new PollingConditions().within(10) { - assert dataSyncSemaphores.get('testKeyDataSync') == null - } - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy index 8d7a4b9cf0..393432fc5e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/DmiCmNotificationSubscriptionCacheHandlerSpec.groovy @@ -27,10 +27,10 @@ import org.apache.kafka.clients.consumer.ConsumerRecord import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.CmNotificationSubscriptionStatus import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionDetails import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.events.cmnotificationsubscription_merge1_0_0.client_to_ncmp.CmNotificationSubscriptionNcmpInEvent +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean @@ -208,4 +208,4 @@ class DmiCmNotificationSubscriptionCacheHandlerSpec extends MessagingBaseSpec { >> [yangModelCmHandle3, yangModelCmHandle4] } -} \ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy index cf72b29254..0c9e3b6912 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/cmsubscription/mapper/CmNotificationSubscriptionDmiInEventMapperSpec.groovy @@ -22,8 +22,8 @@ package org.onap.cps.ncmp.api.impl.events.cmsubscription.mapper import org.onap.cps.ncmp.api.impl.events.cmsubscription.model.DmiCmNotificationSubscriptionPredicate -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL @@ -54,4 +54,4 @@ class CmNotificationSubscriptionDmiInEventMapperSpec extends Specification { assert result.data.predicates.targetFilter.containsAll([['ch-1'], ['ch-2']]) } -} \ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy index 3ae95209f2..1adf01c551 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy @@ -20,19 +20,19 @@ package org.onap.cps.ncmp.api.impl.events.lcm -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.LOCKED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED - -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.LOCKED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED + class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def mockInventoryPersistence = Mock(InventoryPersistence) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy index bef2963cff..36024b74ec 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/events/lcm/LcmEventsCreatorSpec.groovy @@ -20,17 +20,17 @@ package org.onap.cps.ncmp.api.impl.events.lcm -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.DELETING -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY - import org.mapstruct.factory.Mappers -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.events.lcm.v1.Values +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY + class LcmEventsCreatorSpec extends Specification { LcmEventHeaderMapper lcmEventsHeaderMapper = Mappers.getMapper(LcmEventHeaderMapper) @@ -190,4 +190,4 @@ class LcmEventsCreatorSpec extends Specification { 'blank target and existing values' | '' | '' | '' | '' | '' | '' 'new target value and blank existing values' | '' | 'someAlternateId' | '' | 'someAlternateId' | '' | 'someDataProducerIdentifier' } -} \ No newline at end of file +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy deleted file mode 100644 index bdf431855f..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CmHandleQueryServiceImplSpec.groovy +++ /dev/null @@ -1,206 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation - * Modifications Copyright (C) 2023 TechMahindra Ltd. - * ================================================================================ - * 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.inventory - -import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel -import org.onap.cps.spi.utils.CpsValidator -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS -import org.onap.cps.spi.CpsDataPersistenceService -import org.onap.cps.spi.model.DataNode -import spock.lang.Shared -import spock.lang.Specification - -class CmHandleQueryServiceImplSpec extends Specification { - - def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) - - def trustLevelPerDmiPlugin = [:] - - def trustLevelPerCmHandle = [ 'PNFDemo': TrustLevel.COMPLETE, 'PNFDemo2': TrustLevel.NONE, 'PNFDemo4': TrustLevel.NONE ] - - def mockCpsValidator = Mock(CpsValidator) - - def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) - - @Shared - def static sampleDataNodes = [new DataNode()] - - def dataNodeWithPrivateField = '//additional-properties[@name=\"Contact3\" and @value=\"newemailforstore3@bookstore.com\"]/ancestor::cm-handles' - - def static pnfDemo = createDataNode('PNFDemo') - def static pnfDemo2 = createDataNode('PNFDemo2') - def static pnfDemo3 = createDataNode('PNFDemo3') - def static pnfDemo4 = createDataNode('PNFDemo4') - def static pnfDemo5 = createDataNode('PNFDemo5') - - def 'Query CmHandles with public properties query pair.'() { - given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' - mockResponses() - when: 'a query on cmhandle public properties is performed with a public property pair' - def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs) - then: 'the correct cm handle data objects are returned' - result.containsAll(expectedCmHandleIds) - result.size() == expectedCmHandleIds.size() - where: 'the following data is used' - scenario | publicPropertyPairs || expectedCmHandleIds - 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] - 'public property does not match' | [wont_match: 'wont_match'] || [] - '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] - '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] || [] - } - - def 'Query cm handles on trust level'() { - given: 'query properties for trust level COMPLETE' - def trustLevelPropertyQueryPairs = ['trustLevel' : TrustLevel.COMPLETE.toString()] - and: 'the dmi cache has been initialised and "knows" about my-dmi-plugin-identifier' - trustLevelPerDmiPlugin.put('my-dmi-plugin-identifier', TrustLevel.COMPLETE) - and: 'the DataNodes queried for a given cpsPath are returned from the persistence service' - mockResponses() - when: 'the query is run' - def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs) - then: 'the result contain trusted PNFDemo' - assert result.size() == 1 - assert result[0] == 'PNFDemo' - } - - def 'Query CmHandles using empty public properties query pair.'() { - when: 'a query on CmHandle public properties is executed using an empty map' - def result = objectUnderTest.queryCmHandlePublicProperties([:]) - then: 'no cm handles are returned' - result.size() == 0 - } - - def 'Query CmHandles using empty private properties query pair.'() { - when: 'a query on CmHandle private properties is executed using an empty map' - def result = objectUnderTest.queryCmHandleAdditionalProperties([:]) - then: 'no cm handles are returned' - result.size() == 0 - } - - def 'Query CmHandles by a private field\'s value.'() { - given: 'a data node exists with a certain additional-property' - mockCpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] - when: 'a query on CmHandle private properties is executed using a map' - def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) - then: 'one cm handle is returned' - result.size() == 1 - } - - def 'Get CmHandles by it\'s state.'() { - given: 'a cm handle state to query' - def cmHandleState = CmHandleState.ADVISED - and: 'the persistence service returns a list of data nodes' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> sampleDataNodes - when: 'cm handles are fetched by state' - def result = objectUnderTest.queryCmHandlesByState(cmHandleState) - then: 'the returned result matches the result from the persistence service' - assert result == sampleDataNodes - } - - def 'Check the state of a cmHandle when #scenario.'() { - given: 'a cm handle state to compare' - def cmHandleState = state - and: 'the persistence service returns a list of data nodes' - mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', - OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] - when: 'cm handles are compared by state' - def result = objectUnderTest.cmHandleHasState('some-cm-handle', cmHandleState) - then: 'the returned result matches the expected result from the persistence service' - result == expectedResult - where: - scenario | state || expectedResult - 'the provided state matches' | CmHandleState.READY || true - 'the provided state does not match'| CmHandleState.DELETED || false - } - - def 'Get Cm Handles state by Cm-Handle Id'() { - given: 'a cm handle state to query' - def cmHandleState = CmHandleState.READY - and: 'cps data service returns a list of data nodes' - mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', - OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] - when: 'cm handles are fetched by state and id' - def result = objectUnderTest.getCmHandleState('some-cm-handle') - then: 'the returned result is a list of data nodes returned by cps data service' - assert result == new DataNode(leaves: ['cm-handle-state': 'READY']) - } - - def 'Retrieve Cm Handles By Operational Sync State : UNSYNCHRONIZED'() { - given: 'a cm handle state to query' - def cmHandleState = CmHandleState.READY - and: 'cps data service returns a list of data nodes' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes - when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state' - def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) - then: 'the returned result is a list of data nodes returned by cps data service' - assert result == sampleDataNodes - } - - def 'Retrieve cm handle by cps path '() { - given: 'a cm handle state to query based on the cps path' - def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED']) - def cpsPath = '//cps-path' - and: 'cps data service returns a valid data node' - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) - >> Arrays.asList(cmHandleDataNode) - when: 'get cm handles by cps path is invoked' - def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS) - then: 'the returned result is a list of data nodes returned by cps data service' - assert result.contains(cmHandleDataNode) - } - - def 'Get all cm handles by dmi plugin identifier'() { - given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' - mockResponses() - when: 'cm Handles are fetched for a given dmi plugin identifier' - def result = objectUnderTest.getCmHandleIdsByDmiPluginIdentifier('my-dmi-plugin-identifier') - then: 'result is the correct size' - assert result.size() == 3 - and: 'result contains the correct cm handles' - assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4') - } - - void mockResponses() { - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> [] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3] - mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4] - mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4] - } - - def static createDataNode(dataNodeId) { - return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId]) - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy deleted file mode 100644 index 777b505b46..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateBuilderSpec.groovy +++ /dev/null @@ -1,92 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022 Bell Canada - * 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. - * 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.inventory - -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder -import spock.lang.Specification - -import java.time.OffsetDateTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter - -class CompositeStateBuilderSpec extends Specification { - - def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") - .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) - - def static cmHandleId = 'myHandle1' - def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}/state']" - def static stateDataNodes = [new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/lock-reason") - .withLeaves(['reason': 'MODULE_SYNC_FAILED', 'details': 'lock details']).build(), - new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/datastores") - .withChildDataNodes(Arrays.asList(new DataNodeBuilder() - .withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/datastores/operational") - .withLeaves(['sync-state': 'UNSYNCHRONIZED']).build())).build()] - def static cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: stateDataNodes, leaves: ['cm-handle-state': 'ADVISED']) - - def "Composite State Specification"() { - when: 'using composite state builder ' - def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED) - .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED,"").withOperationalDataStores(DataStoreSyncState.UNSYNCHRONIZED, - formattedDateAndTime.toString()).withLastUpdatedTime(formattedDateAndTime).build() - then: 'it matches expected cm handle state and data store sync state' - assert compositeState.cmHandleState == CmHandleState.ADVISED - assert compositeState.dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.UNSYNCHRONIZED - } - - def "Build composite state from DataNode "() { - given: "a Data Node " - new DataNode(leaves: ['cm-handle-state': 'ADVISED']) - when: 'build from data node function is invoked' - def compositeState = new CompositeStateBuilder().fromDataNode(cmHandleDataNode).build() - then: 'it matches expected state model as JSON' - assert compositeState.cmHandleState == CmHandleState.ADVISED - } - - def 'CompositeStateBuilder build'() { - given: 'A CompositeStateBuilder with all private fields set' - def finalCompositeStateBuilder = new CompositeStateBuilder() - .withCmHandleState(CmHandleState.ADVISED) - .withLastUpdatedTime(formattedDateAndTime.toString()) - .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, 'locked details') - .withOperationalDataStores(DataStoreSyncState.SYNCHRONIZED, formattedDateAndTime) - when: 'build is called' - def result = finalCompositeStateBuilder.build() - then: 'result is of the correct type' - assert result.class == CompositeState.class - and: 'built result should have correct values' - assert !result.getDataSyncEnabled() - assert result.getLastUpdateTime() == formattedDateAndTime - assert result.getLockReason().getLockReasonCategory() == LockReasonCategory.MODULE_SYNC_FAILED - assert result.getLockReason().getDetails() == 'locked details' - assert result.getCmHandleState() == CmHandleState.ADVISED - assert result.getDataStores().getOperationalDataStore().getDataStoreSyncState() == DataStoreSyncState.SYNCHRONIZED - assert result.getDataStores().getOperationalDataStore().getLastSyncTime() == formattedDateAndTime - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy deleted file mode 100644 index d8beba8de0..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/CompositeStateSpec.groovy +++ /dev/null @@ -1,64 +0,0 @@ -/* - * ============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.api.impl.inventory - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import spock.lang.Specification -import java.time.OffsetDateTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter - -import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores -import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational -import static org.onap.cps.ncmp.utils.TestUtils.getResourceFileContent -import static org.springframework.util.StringUtils.trimAllWhitespace - -class CompositeStateSpec extends Specification { - - def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") - .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) - def objectMapper = new ObjectMapper() - - def "Composite State Specification"() { - given: "a Composite State" - def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, - lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), - lastUpdateTime: formattedDateAndTime.toString(), - dataSyncEnabled: false, - dataStores: dataStores()) - when: 'it is represented as JSON' - def resultJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(compositeState) - then: 'it matches expected state model as JSON' - def expectedJson = getResourceFileContent('expectedStateModel.json') - assert trimAllWhitespace(expectedJson) == trimAllWhitespace(resultJson) - } - - def dataStores() { - DataStores.builder().operationalDataStore(Operational.builder() - .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) - .lastSyncTime(formattedDateAndTime.toString()).build()) - .build() - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy deleted file mode 100644 index 775824bb07..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy +++ /dev/null @@ -1,342 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * Modifications Copyright (C) 2023 TechMahindra Ltd. - * ================================================================================ - * 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.inventory - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIMESTAMP -import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS -import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS - -import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsAnchorService -import org.onap.cps.api.CpsDataService -import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.spi.CascadeDeleteAllowed -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.exceptions.DataNodeNotFoundException -import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.ModuleDefinition -import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.spi.utils.CpsValidator -import org.onap.cps.utils.JsonObjectMapper -import spock.lang.Shared -import spock.lang.Specification -import java.time.OffsetDateTime -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter - -class InventoryPersistenceImplSpec extends Specification { - - def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) - - def mockCpsDataService = Mock(CpsDataService) - - def mockCpsModuleService = Mock(CpsModuleService) - - def mockCpsAnchorService = Mock(CpsAnchorService) - - def mockCpsValidator = Mock(CpsValidator) - - def mockCmHandleQueries = Mock(CmHandleQueryService) - - def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, - mockCpsValidator, mockCpsAnchorService, mockCmHandleQueries) - - def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") - .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) - - def cmHandleId = 'some-cm-handle' - def leaves = ["id":cmHandleId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] - def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']" - - def cmHandleId2 = 'another-cm-handle' - def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']" - - @Shared - def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]), - new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] - - @Shared - def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])] - - @Shared - def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] - - @Shared - def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])] - - def "Retrieve CmHandle using datanode with #scenario."() { - given: 'the cps data service returns a data node from the DMI registry' - def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves) - mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] - when: 'retrieving the yang modelled cm handle' - def result = objectUnderTest.getYangModelCmHandle(cmHandleId) - then: 'the result has the correct id and service names' - result.id == cmHandleId - result.dmiServiceName == 'common service name' - result.dmiDataServiceName == 'data service name' - result.dmiModelServiceName == 'model service name' - and: 'the expected DMI properties' - result.dmiProperties == expectedDmiProperties - result.publicProperties == expectedPublicProperties - and: 'the state details are returned' - result.compositeState.cmHandleState == expectedCompositeState - and: 'the CM Handle ID is validated' - 1 * mockCpsValidator.validateNameCharacters(cmHandleId) - where: 'the following parameters are used' - scenario | childDataNodes || expectedDmiProperties || expectedPublicProperties || expectedCompositeState - 'no properties' | [] || [] || [] || null - 'DMI and public properties' | childDataNodesForCmHandleWithAllProperties || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")] || null - 'just DMI properties' | childDataNodesForCmHandleWithDMIProperties || [new YangModelCmHandle.Property("name1", "value1")] || [] || null - 'just public properties' | childDataNodesForCmHandleWithPublicProperties || [] || [new YangModelCmHandle.Property("name2", "value2")] || null - 'with state details' | childDataNodesForCmHandleWithState || [] || [] || CmHandleState.ADVISED - } - - def "Handling missing service names as null."() { - given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes' - def dataNode = new DataNode(childDataNodes:[], leaves: ['id':cmHandleId]) - mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] - when: 'retrieving the yang modelled cm handle' - def result = objectUnderTest.getYangModelCmHandle(cmHandleId) - then: 'the service names are returned as null' - result.dmiServiceName == null - result.dmiDataServiceName == null - result.dmiModelServiceName == null - and: 'the CM Handle ID is validated' - 1 * mockCpsValidator.validateNameCharacters(cmHandleId) - } - - def "Retrieve multiple YangModelCmHandles"() { - given: 'the cps data service returns 2 data nodes from the DMI registry' - def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])] - mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes - when: 'retrieving the yang modelled cm handle' - def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2]) - then: 'verify both have returned and cmhandleIds are correct' - assert results.size() == 2 - assert results.id.containsAll([cmHandleId, cmHandleId2]) - } - - def 'Get a Cm Handle Composite State'() { - given: 'a valid cm handle id' - def cmHandleId = 'Some-Cm-Handle' - def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED']) - and: 'cps data service returns a valid data node' - mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode] - when: 'get cm handle state is invoked' - def result = objectUnderTest.getCmHandleState(cmHandleId) - then: 'result has returned the correct cm handle state' - result.cmHandleState == CmHandleState.ADVISED - and: 'the CM Handle ID is validated' - 1 * mockCpsValidator.validateNameCharacters(cmHandleId) - } - - def 'Update Cm Handle with #scenario State'() { - given: 'a cm handle and a composite state' - def cmHandleId = 'Some-Cm-Handle' - def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime) - when: 'update cm handle state is invoked with the #scenario state' - objectUnderTest.saveCmHandleState(cmHandleId, compositeState) - then: 'update node leaves is invoked with the correct params' - 1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime) - where: 'the following states are used' - scenario | cmHandleState || expectedJsonData - 'READY' | CmHandleState.READY || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}' - 'LOCKED' | CmHandleState.LOCKED || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}' - 'DELETING' | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}' - } - - def 'Update Cm Handles with #scenario States'() { - given: 'a map of cm handles composite states' - def compositeState1 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime) - def compositeState2 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime) - when: 'update cm handle state is invoked with the #scenario state' - def cmHandleStateMap = ['Some-Cm-Handle1' : compositeState1, 'Some-Cm-Handle2' : compositeState2] - objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap) - then: 'update node leaves is invoked with the correct params' - 1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime) - where: 'the following states are used' - scenario | cmHandleState || cmHandlesJsonDataMap - 'READY' | CmHandleState.READY || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] - 'LOCKED' | CmHandleState.LOCKED || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] - 'DELETING' | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] - } - - def 'Getting module definitions by module'() { - given: 'cps module service returns module definition for module name' - def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')] - mockCpsModuleService.getModuleDefinitionsByAnchorAndModule(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id', 'some-module', '2024-01-25') >> moduleDefinitions - when: 'get module definitions is invoked with module name' - def result = objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cmHandle-Id', 'some-module', '2024-01-25') - then: 'returned result are the same module definitions as returned from module service' - assert result == moduleDefinitions - and: 'cm handle id and module name validated' - 1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id', 'some-module') - } - - def 'Getting module definitions with cm handle id'() { - given: 'cps module service returns module definitions for cm handle id' - def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')] - mockCpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleDefinitions - when: 'get module definitions is invoked with cm handle id' - def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id') - then: 'the returned result are the same module definitions as returned from the module service' - assert result == moduleDefinitions - } - - def 'Get module references'() { - given: 'cps module service returns a collection of module references' - def moduleReferences = [new ModuleReference('moduleName','revision','namespace')] - mockCpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleReferences - when: 'get yang resources module references by cmHandle is invoked' - def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id') - then: 'the returned result is a collection of module definitions' - assert result == moduleReferences - and: 'the CM Handle ID is validated' - 1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id') - } - - def 'Save Cmhandle'() { - given: 'cmHandle represented as Yang Model' - def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', dmiProperties: [], publicProperties: []) - when: 'the method to save cmhandle is called' - objectUnderTest.saveCmHandle(yangModelCmHandle) - then: 'the data service method to save list elements is called once' - 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, - _,null) >> { - args -> { - assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}') - } - } - } - - def 'Save Multiple Cmhandles'() { - given: 'cm handles represented as Yang Model' - def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1') - def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2') - when: 'the cm handles are saved' - objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2]) - then: 'CPS Data Service persists both cm handles as a batch' - 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT, _,null) >> { - args -> { - def jsonData = (args[3] as String) - jsonData.contains('cmhandle1') - jsonData.contains('cmhandle2') - } - } - } - - def 'Delete list or list elements'() { - when: 'the method to delete list or list elements is called' - objectUnderTest.deleteListOrListElement('sample xPath') - then: 'the data service method to save list elements is called once' - 1 * mockCpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath',null) - } - - def 'Delete schema set with a valid schema set name'() { - when: 'the method to delete schema set is called with valid schema set name' - objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName') - then: 'the module service to delete schemaSet is invoked once' - 1 * mockCpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) - and: 'the schema set name is validated' - 1 * mockCpsValidator.validateNameCharacters('validSchemaSetName') - } - - def 'Delete multiple schema sets with valid schema set names'() { - when: 'the method to delete schema sets is called with valid schema set names' - objectUnderTest.deleteSchemaSetsWithCascade(['validSchemaSetName1', 'validSchemaSetName2']) - then: 'the module service to delete schema sets is invoked once' - 1 * mockCpsModuleService.deleteSchemaSetsWithCascade(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['validSchemaSetName1', 'validSchemaSetName2']) - and: 'the schema set names are validated' - 1 * mockCpsValidator.validateNameCharacters(['validSchemaSetName1', 'validSchemaSetName2']) - } - - def 'Get data node via xPath'() { - when: 'the method to get data nodes is called' - objectUnderTest.getDataNode('sample xPath') - then: 'the data persistence service method to get data node is invoked once' - 1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath', INCLUDE_ALL_DESCENDANTS) - } - - def 'Get cmHandle data node'() { - given: 'expected xPath to get cmHandle data node' - def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']' - when: 'the method to get data nodes is called' - objectUnderTest.getCmHandleDataNodeByCmHandleId('sample cmHandleId') - then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath' - 1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS) - } - - def 'Get cm handle data node'() { - given: 'expected xPath to get cmHandle data node' - def expectedXPath = '/dmi-registry/cm-handles[@alternate-id=\'alternate id\']' - and: 'query service is invoked with expected xpath' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS) >> [new DataNode()] - expect: 'getting the cm handle data node' - assert objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') == new DataNode() - } - - def 'Attempt to get non existing cm handle data node by alternate id'() { - given: 'query service is invoked and returns empty collection of data nodes' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] - when: 'getting the cm handle data node' - objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') - then: 'no data found exception thrown' - def thrownException = thrown(DataNodeNotFoundException) - assert thrownException.getMessage().contains('DataNode not found') - } - - def 'Get CM handles that has given module names'() { - when: 'the method to get cm handles is called' - objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name']) - then: 'the admin persistence service method to query anchors is invoked once with the same parameter' - 1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) - } - - def 'Replace list content'() { - when: 'replace list content method is called with xpath and data nodes collection' - objectUnderTest.replaceListContent('sample xpath', [new DataNode()]) - then: 'the cps data service method to replace list content is invoked once with same parameters' - 1 * mockCpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xpath', [new DataNode()], NO_TIMESTAMP); - } - - def 'Delete data node via xPath'() { - when: 'Delete data node method is called with xpath as parameter' - objectUnderTest.deleteDataNode('sample dataNode xpath') - then: 'the cps data service method to delete data node is invoked once with the same xPath' - 1 * mockCpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'sample dataNode xpath', NO_TIMESTAMP); - } - - def 'Delete multiple data nodes via xPath'() { - when: 'Delete data nodes method is called with multiple xpaths as parameters' - objectUnderTest.deleteDataNodes(['xpath1', 'xpath2']) - then: 'the cps data service method to delete data nodes is invoked once with the same xPaths' - 1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP); - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy deleted file mode 100644 index 0ffb567916..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/DataSyncWatchdogSpec.groovy +++ /dev/null @@ -1,116 +0,0 @@ -/* - * ============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.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME - -import com.hazelcast.map.IMap -import org.onap.cps.api.CpsDataService -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import spock.lang.Specification - -class DataSyncWatchdogSpec extends Specification { - - def mockInventoryPersistence = Mock(InventoryPersistence) - - def mockCpsDataService = Mock(CpsDataService) - - def mockSyncUtils = Mock(ModuleOperationsUtils) - - def mockDataSyncSemaphores = Mock(IMap) - - def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' - - def objectUnderTest = new DataSyncWatchdog(mockInventoryPersistence, mockCpsDataService, mockSyncUtils, mockDataSyncSemaphores) - - def compositeState = getCompositeState() - - def yangModelCmHandle1 = createSampleYangModelCmHandle('cm-handle-1') - - def yangModelCmHandle2 = createSampleYangModelCmHandle('cm-handle-2') - - def 'Data Sync for Cm Handle State in READY and Operational Sync State in UNSYNCHRONIZED.'() { - given: 'sample resource data' - def resourceData = jsonString - and: 'sync utilities returns a cm handle twice' - mockSyncUtils.getUnsynchronizedReadyCmHandles() >> [yangModelCmHandle1, yangModelCmHandle2] - when: 'data sync poll is executed' - objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() - then: 'the inventory persistence cm handle returns a composite state for the first cm handle' - 1 * mockInventoryPersistence.getCmHandleState('cm-handle-1') >> compositeState - and: 'the sync util returns first resource data' - 1 * mockSyncUtils.getResourceData('cm-handle-1') >> resourceData - and: 'the cm-handle data is saved' - 1 * mockCpsDataService.saveData(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cm-handle-1', jsonString, _) - and: 'the first cm handle operational sync state is updated' - 1 * mockInventoryPersistence.saveCmHandleState('cm-handle-1', compositeState) - then: 'the inventory persistence cm handle returns a composite state for the second cm handle' - 1 * mockInventoryPersistence.getCmHandleState('cm-handle-2') >> compositeState - and: 'the sync util returns first resource data' - 1 * mockSyncUtils.getResourceData('cm-handle-2') >> resourceData - and: 'the cm-handle data is saved' - 1 * mockCpsDataService.saveData(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cm-handle-2', jsonString, _) - and: 'the second cm handle operational sync state is updated from "UNSYNCHRONIZED" to "SYNCHRONIZED"' - 1 * mockInventoryPersistence.saveCmHandleState('cm-handle-2', compositeState) - } - - def 'Data Sync for Cm Handle State in READY and Operational Sync State in UNSYNCHRONIZED without resource data.'() { - given: 'sync utilities returns a cm handle' - mockSyncUtils.getUnsynchronizedReadyCmHandles() >> [yangModelCmHandle1] - when: 'data sync poll is executed' - objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() - then: 'the inventory persistence cm handle returns a composite state for the first cm handle' - 1 * mockInventoryPersistence.getCmHandleState('cm-handle-1') >> compositeState - and: 'the sync util returns no resource data' - 1 * mockSyncUtils.getResourceData('cm-handle-1') >> null - and: 'the cm-handle data is not saved' - 0 * mockCpsDataService.saveData(*_) - } - - def 'Data Sync for Cm Handle that is already being processed.'() { - given: 'sync utilities returns a cm handle' - mockSyncUtils.getUnsynchronizedReadyCmHandles() >> [yangModelCmHandle1] - and: 'the shared data sync semaphore indicate it is already being processed' - mockDataSyncSemaphores.putIfAbsent('cm-handle-1', _, _, _) >> 'something (not null)' - when: 'data sync poll is executed' - objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() - then: 'it is NOT processed e.g. state is not requested' - 0 * mockInventoryPersistence.getCmHandleState(*_) - } - - def createSampleYangModelCmHandle(cmHandleId) { - def compositeState = getCompositeState() - return new YangModelCmHandle(id: cmHandleId, compositeState: compositeState) - } - - def getCompositeState() { - def cmHandleState = CmHandleState.READY - def compositeState = new CompositeState(cmHandleState: cmHandleState) - compositeState.setDataStores(CompositeState.DataStores.builder() - .operationalDataStore(CompositeState.Operational.builder().dataStoreSyncState(DataStoreSyncState.SYNCHRONIZED) - .build()).build()) - return compositeState - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy deleted file mode 100644 index e9038ecfc9..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy +++ /dev/null @@ -1,225 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED - -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import ch.qos.logback.core.read.ListAppender -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.AnnotationConfigApplicationContext -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.inventory.CmHandleQueryService -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.DataNode -import org.onap.cps.utils.JsonObjectMapper -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import spock.lang.Specification -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter -import java.util.stream.Collectors - -class ModuleOperationsUtilsSpec extends Specification{ - - def mockCmHandleQueries = Mock(CmHandleQueryService) - - def mockDmiDataOperations = Mock(DmiDataOperations) - - def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - - def objectUnderTest = new ModuleOperationsUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper) - - def static neverUpdatedBefore = '1900-01-01T00:00:00.000+0100' - - def static now = OffsetDateTime.now() - - def static nowAsString = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now) - - def static dataNode = new DataNode(leaves: ['id': 'cm-handle-123']) - - def applicationContext = new AnnotationConfigApplicationContext() - - def logger = (Logger) LoggerFactory.getLogger(ModuleOperationsUtils) - def loggingListAppender - - void setup() { - logger.setLevel(Level.DEBUG) - loggingListAppender = new ListAppender() - logger.addAppender(loggingListAppender) - loggingListAppender.start() - applicationContext.refresh() - } - - void cleanup() { - ((Logger) LoggerFactory.getLogger(ModuleOperationsUtils.class)).detachAndStopAllAppenders() - applicationContext.close() - } - - def 'Get an advised Cm-Handle where ADVISED cm handle #scenario'() { - given: 'the inventory persistence service returns a collection of data nodes' - mockCmHandleQueries.queryCmHandlesByState(CmHandleState.ADVISED) >> dataNodeCollection - when: 'get advised cm handles are fetched' - def yangModelCmHandles = objectUnderTest.getAdvisedCmHandles() - then: 'the returned data node collection is the correct size' - yangModelCmHandles.size() == expectedDataNodeSize - where: 'the following scenarios are used' - scenario | dataNodeCollection || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize - 'exists' | [dataNode] || 1 | 1 - 'does not exist' | [] || 0 | 0 - } - - def 'Update Lock Reason, Details and Attempts where lock reason #scenario'() { - given: 'A locked state' - def compositeState = new CompositeState(lockReason: lockReason) - when: 'update cm handle details and attempts is called' - objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_SYNC_FAILED, 'new error message') - then: 'the composite state lock reason and details are updated' - assert compositeState.lockReason.lockReasonCategory == MODULE_SYNC_FAILED - assert compositeState.lockReason.details.contains(expectedDetails) - where: - scenario | lockReason || expectedDetails - 'does not exist' | null || 'Attempt #1 failed: new error message' - 'exists' | CompositeState.LockReason.builder().details("Attempt #2 failed: some error message").build() || 'Attempt #3 failed: new error message' - } - - def 'Update lock reason details that contains #scenario'() { - given: 'A locked state' - def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) - .withLockReason(MODULE_UPGRADE, "Upgrade to ModuleSetTag: " + moduleSetTag).build() - when: 'update cm handle details' - objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_UPGRADE_FAILED, 'new error message') - then: 'the composite state lock reason and details are updated' - assert compositeState.lockReason.lockReasonCategory == MODULE_UPGRADE_FAILED - assert compositeState.lockReason.details.contains("Upgrade to ModuleSetTag: " + expectedDetails) - where: - scenario | moduleSetTag || expectedDetails - 'a module set tag' | 'someModuleSetTag' || 'someModuleSetTag' - 'empty module set tag' | '' || '' - } - - def 'Get all locked cm-Handles where lock reasons are model sync failed or upgrade'() { - given: 'the cps (persistence service) returns a collection of data nodes' - mockCmHandleQueries.queryCmHandleAncestorsByCpsPath(ModuleOperationsUtils.CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE, - FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode] - when: 'get locked Misbehaving cm handle is called' - def result = objectUnderTest.getCmHandlesThatFailedModelSyncOrUpgrade() - then: 'the returned cm handle collection is the correct size' - result.size() == 1 - and: 'the correct cm handle is returned' - result[0].id == 'cm-handle-123' - } - - def 'Retry Locked Cm-Handle where the last update time is #scenario'() { - given: 'Last update was #lastUpdateMinutesAgo minutes ago (-1 means never)' - def lastUpdatedTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now.minusMinutes(lastUpdateMinutesAgo)) - if (lastUpdateMinutesAgo < 0 ) { - lastUpdatedTime = neverUpdatedBefore - } - when: 'checking to see if cm handle is ready for retry' - def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() - .withLockReason(MODULE_SYNC_FAILED, lockDetails) - .withLastUpdatedTime(lastUpdatedTime).build()) - then: 'retry is only attempted when expected' - assert result == retryExpected - and: 'logs contain related information' - def logs = loggingListAppender.list.toString() - assert logs.contains(logReason) - where: 'the following parameters are used' - scenario | lastUpdateMinutesAgo | lockDetails | logReason || retryExpected - 'never attempted before' | -1 | 'Fist attempt:' | 'First Attempt:' || true - '1st attempt, last attempt > 2 minute ago' | 3 | 'Attempt #1 failed: some error' | 'Retry due now' || true - '2nd attempt, last attempt < 4 minutes ago' | 1 | 'Attempt #2 failed: some error' | 'Time until next attempt is 3 minutes:' || false - '2nd attempt, last attempt > 4 minutes ago' | 5 | 'Attempt #2 failed: some error' | 'Retry due now' || true - } - - def 'Retry Locked Cm-Handle with lock reasons (category) #lockReasonCategory'() { - when: 'checking to see if cm handle is ready for retry' - def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() - .withLockReason(lockReasonCategory, 'some details') - .withLastUpdatedTime(nowAsString).build()) - then: 'verify retry attempts' - assert !result - and: 'logs contain related information' - def logs = loggingListAppender.list.toString() - assert logs.contains(logReason) - where: 'the following lock reasons occurred' - scenario | lockReasonCategory || logReason - 'module upgrade' | MODULE_UPGRADE_FAILED || 'First Attempt:' - 'module sync failed' | MODULE_SYNC_FAILED || 'First Attempt:' - 'lock misbehaving' | LOCKED_MISBEHAVING || 'Locked for other reason' - } - - def 'Get a Cm-Handle where #scenario'() { - given: 'the inventory persistence service returns a collection of data nodes' - mockCmHandleQueries.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes - mockCmHandleQueries.cmHandleHasState('cm-handle-123', CmHandleState.READY) >> cmHandleHasState - when: 'get advised cm handles are fetched' - def yangModelCollection = objectUnderTest.getUnsynchronizedReadyCmHandles() - then: 'the returned data node collection is the correct size' - yangModelCollection.size() == expectedDataNodeSize - and: 'the result contains the correct data' - yangModelCollection.stream().map(yangModel -> yangModel.id).collect(Collectors.toSet()) == expectedYangModelCollectionIds - where: 'the following scenarios are used' - scenario | unSynchronizedDataNodes | cmHandleHasState || expectedDataNodeSize | expectedYangModelCollectionIds - 'a Cm-Handle unsynchronized and ready' | [dataNode] | true || 1 | ['cm-handle-123'] as Set - 'a Cm-Handle unsynchronized but not ready' | [dataNode] | false || 0 | [] as Set - 'all Cm-Handle synchronized' | [] | false || 0 | [] as Set - } - - def 'Get resource data through DMI Operations #scenario'() { - given: 'the inventory persistence service returns a collection of data nodes' - def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' - JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString); - def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK) - mockDmiDataOperations.getAllResourceDataFromDmi('cm-handle-123', _) >> responseEntity - when: 'get resource data is called' - def result = objectUnderTest.getResourceData('cm-handle-123') - then: 'the returned data is correct' - result == jsonString - } - - def 'Extract module set tag and number of attempt when lock reason contains #scenario'() { - expect: 'lock reason details are extracted correctly' - def result = objectUnderTest.getLockedCompositeStateDetails(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, lockReasonDetails).build().lockReason) - and: 'the result contains the correct moduleSetTag' - assert result['moduleSetTag'] == expectedModuleSetTag - and: 'the result contains the correct number of attempts' - assert result['attempt'] == expectedNumberOfAttempts - where: 'the following scenarios are used' - scenario | lockReasonDetails || expectedModuleSetTag | expectedNumberOfAttempts - 'module set tag only' | 'Upgrade to ModuleSetTag: targetModuleSetTag' || 'targetModuleSetTag' | null - 'number of attempts only' | 'Attempt #1 failed: some error' || null | '1' - 'number of attempts and module set tag both' | 'Upgrade to ModuleSetTag: targetModuleSetTag Attempt #1 failed: some error' || 'targetModuleSetTag' | '1' - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy deleted file mode 100644 index 9f7b953003..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy +++ /dev/null @@ -1,171 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.inventory.sync - -import org.onap.cps.api.CpsAnchorService - -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE - -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.spi.model.DataNode -import org.onap.cps.api.CpsDataService -import org.onap.cps.api.CpsModuleService -import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import org.onap.cps.spi.CascadeDeleteAllowed -import org.onap.cps.spi.exceptions.SchemaSetNotFoundException -import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.utils.JsonObjectMapper -import spock.lang.Specification - -class ModuleSyncServiceSpec extends Specification { - - def mockCpsModuleService = Mock(CpsModuleService) - def mockDmiModelOperations = Mock(DmiModelOperations) - def mockCpsAnchorService = Mock(CpsAnchorService) - def mockCmHandleQueries = Mock(CmHandleQueryService) - def mockCpsDataService = Mock(CpsDataService) - def mockJsonObjectMapper = Mock(JsonObjectMapper) - - def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, - mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper) - - def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME - def static cmHandleWithModuleSetTag = new DataNodeBuilder() - .withXpath("/dmi-registry/cm-handles[@id='otherId']") - .withLeaves(['id': 'otherId', 'module-set-tag': 'tag-1']) - .withAnchor('otherId').build() - - def 'Sync model for a NEW cm handle using module set tags: #scenario.'() { - given: 'a cm handle state to be synced' - def ncmpServiceCmHandle = new NcmpServiceCmHandle() - ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).build()) - ncmpServiceCmHandle.cmHandleId = 'ch-1' - def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, moduleSetTag, '', '') - and: 'DMI operations returns some module references' - def moduleReferences = [ new ModuleReference('module1','1'), new ModuleReference('module2','2') ] - mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences - and: 'DMI-Plugin returns resource(s) for "new" module(s)' - mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferences) >> newModuleNameContentToMap - and: 'the module service identifies #identifiedNewModuleReferences.size() new modules' - mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> identifiedNewModuleReferences - and: 'system contains other cm handle with "same tag" (that is READY)' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag - when: 'module sync is triggered' - objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle) - then: 'create schema set from module is invoked with correct parameters' - 1 * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', newModuleNameContentToMap, moduleReferences) - and: 'anchor is created with the correct parameters' - 1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1') - where: 'the following parameters are used' - scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap | moduleSetTag | existingCmHandlesWithSameTag - 'one new module, new tag' | [['module2': '2'], ['module3': '3']] | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | '' | [] - 'no new module, new tag' | [['module1': '1'], ['module2': '2']] | [] | [:] | 'new-tag-1' | [] - 'same tag' | [['module1': '1'], ['module2': '2']] | [] | [:] | 'same-tag' | [cmHandleWithModuleSetTag] - } - - def 'Upgrade model for an existing cm handle with Module Set Tag where the modules are #scenario'() { - given: 'a cm handle being upgraded to module set tag: tag-1' - def ncmpServiceCmHandle = new NcmpServiceCmHandle() - ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: tag-1').build()) - def dmiServiceName = 'some service name' - ncmpServiceCmHandle.cmHandleId = 'upgraded-ch' - def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'tag-1', '', '') - and: 'some module references' - def moduleReferences = [ new ModuleReference('module1','1') ] - and: 'DMI operations returns some module references for upgraded cm handle' - mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences - mockDmiModelOperations.getNewYangResourcesFromDmi(_, []) >> [:] - and: 'none of these module references are new (unknown to the system)' - mockCpsModuleService.identifyNewModuleReferences(_) >> [] - and: 'CPS-Core returns list of existing module resources for TBD' - mockCpsModuleService.getYangResourcesModuleReferences(*_) >> [ new ModuleReference('module1','1') ] - and: 'system contains #existingCmHandlesWithSameTag.size() cm handles with same tag' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag - and: 'the other cm handle is a state ready' - mockCmHandleQueries.cmHandleHasState('otherId', CmHandleState.READY) >> true - when: 'module sync is triggered' - objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle) - then: 'update schema set from module is invoked for the upgraded cm handle' - 1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences) - and: 'create schema set from module is not invoked for the upgraded cm handle' - 0 * mockCpsModuleService.createSchemaSetFromModules(*_) - and: 'No anchor is created for the upgraded cm handle' - 0 * mockCpsAnchorService.createAnchor(*_) - where: 'the following parameters are used' - scenario | existingCmHandlesWithSameTag - 'new' | [] - 'in database' | [cmHandleWithModuleSetTag] - } - - def 'upgrade model for a existing cm handle'() { - given: 'a cm handle that is ready but locked for upgrade' - def ncmpServiceCmHandle = new NcmpServiceCmHandle() - ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder() - .withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: targetModuleSetTag').build()) - ncmpServiceCmHandle.setCmHandleId('cmHandleId-1') - def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, 'targetModuleSetTag', '', '') - mockCmHandleQueries.cmHandleHasState('cmHandleId-1', CmHandleState.READY) >> true - and: 'the module service returns some module references' - def moduleReferences = [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] - mockCpsModuleService.getYangResourcesModuleReferences(*_)>> moduleReferences - and: 'a cm handle with the same moduleSetTag can be found in the registry' - mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1'], - childDataNodes: [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']/state', leaves: ['cm-handle-state': 'READY'])])] - when: 'module upgrade is triggered' - objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle) - then: 'the upgrade is delegated to the module service (with the correct parameters)' - 1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', Collections.emptyMap(), moduleReferences) - } - - def 'Delete Schema Set for CmHandle'() { - when: 'delete schema set if exists is called' - objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id') - then: 'the module service is invoked to delete the correct schema set' - 1 * mockCpsModuleService.deleteSchemaSet(expectedDataspaceName, 'some-cmhandle-id', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) - } - - def 'Delete a non-existing Schema Set for CmHandle' () { - given: 'the DB throws an exception because its Schema Set does not exist' - mockCpsModuleService.deleteSchemaSet(*_) >> { throw new SchemaSetNotFoundException('some-dataspace-name', 'some-cmhandle-id') } - when: 'delete schema set if exists is called' - objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id') - then: 'the exception from the DB is ignored; there are no exceptions' - noExceptionThrown() - } - - def 'Delete Schema Set for CmHandle with other exception' () { - given: 'an exception other than SchemaSetNotFoundException is thrown' - UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException(); - 1 * mockCpsModuleService.deleteSchemaSet(*_) >> { throw unsupportedOperationException } - when: 'delete schema set if exists is called' - objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id') - then: 'an exception is thrown' - def result = thrown(UnsupportedOperationException) - result == unsupportedOperationException - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy deleted file mode 100644 index 6c0d6dfb87..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ /dev/null @@ -1,225 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.inventory.sync - -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_SYNC_FAILED -import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE_FAILED - -import ch.qos.logback.classic.Level -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.read.ListAppender -import com.hazelcast.config.Config -import com.hazelcast.instance.impl.HazelcastInstanceFactory -import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.spi.model.DataNode -import org.slf4j.LoggerFactory -import spock.lang.Specification -import java.util.concurrent.atomic.AtomicInteger - -class ModuleSyncTasksSpec extends Specification { - - def logger = Spy(ListAppender) - - void setup() { - ((Logger) LoggerFactory.getLogger(ModuleSyncTasks.class)).addAppender(logger) - logger.start() - } - - void cleanup() { - ((Logger) LoggerFactory.getLogger(ModuleSyncTasks.class)).detachAndStopAllAppenders() - } - - def mockInventoryPersistence = Mock(InventoryPersistence) - - def mockSyncUtils = Mock(ModuleOperationsUtils) - - def mockModuleSyncService = Mock(ModuleSyncService) - - def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) - - IMap moduleSyncStartedOnCmHandles = HazelcastInstanceFactory - .getOrCreateHazelcastInstance(new Config('hazelcastInstanceName')) - .getMap('mapInstanceName') - - def batchCount = new AtomicInteger(5) - - def objectUnderTest = new ModuleSyncTasks(mockInventoryPersistence, mockSyncUtils, mockModuleSyncService, - mockLcmEventsCmHandleStateHandler, moduleSyncStartedOnCmHandles) - - def 'Module Sync ADVISED cm handles.'() { - given: 'cm handles in an ADVISED state' - def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) - def cmHandle2 = cmHandleAsDataNodeByIdAndState('cm-handle-2', CmHandleState.ADVISED) - and: 'the inventory persistence cm handle returns a ADVISED state for the any handle' - mockInventoryPersistence.getCmHandleState(_) >> new CompositeState(cmHandleState: CmHandleState.ADVISED) - when: 'module sync poll is executed' - objectUnderTest.performModuleSync([cmHandle1, cmHandle2], batchCount) - then: 'module sync service deletes schemas set of each cm handle if it already exists' - 1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-1') - 1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-2') - and: 'module sync service is invoked for each cm handle' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-1' } - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-2' } - and: 'the state handler is called for the both cm handles' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> - assertBatch(args, ['cm-handle-1', 'cm-handle-2'], CmHandleState.READY) - } - and: 'batch count is decremented by one' - assert batchCount.get() == 4 - } - - def 'Module Sync ADVISED cm handle with failure during sync.'() { - given: 'a cm handle in an ADVISED state' - def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.ADVISED) - and: 'the inventory persistence cm handle returns a ADVISED state for the cm handle' - def cmHandleState = new CompositeState(cmHandleState: CmHandleState.ADVISED) - 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> cmHandleState - and: 'module sync service attempts to sync the cm handle and throws an exception' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } - when: 'module sync is executed' - objectUnderTest.performModuleSync([cmHandle], batchCount) - then: 'update lock reason, details and attempts is invoked' - 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, MODULE_SYNC_FAILED, 'some exception') - and: 'the state handler is called to update the state to LOCKED' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> - assertBatch(args, ['cm-handle'], CmHandleState.LOCKED) - } - and: 'batch count is decremented by one' - assert batchCount.get() == 4 - } - - def 'Failed cm handle during #scenario.'() { - given: 'a cm handle in LOCKED state' - def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED) - and: 'the inventory persistence cm handle returns a LOCKED state with reason for the cm handle' - def expectedCmHandleState = new CompositeState(cmHandleState: cmHandleState, lockReason: CompositeState - .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build()) - 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState - and: 'module sync service attempts to sync/upgrade the cm handle and throws an exception' - mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } - mockModuleSyncService.syncAndUpgradeSchemaSet(*_) >> { throw new Exception('some exception') } - when: 'module sync is executed' - objectUnderTest.performModuleSync([cmHandle], batchCount) - then: 'update lock reason, details and attempts is invoked' - 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(expectedCmHandleState, expectedLockReasonCategory, 'some exception') - where: - scenario | cmHandleState | lockReasonCategory | lockReasonDetails || expectedLockReasonCategory - 'module upgrade' | CmHandleState.LOCKED | MODULE_UPGRADE_FAILED | 'Upgrade to ModuleSetTag: some-module-set-tag' || MODULE_UPGRADE_FAILED - 'module sync' | CmHandleState.LOCKED | MODULE_SYNC_FAILED | 'some lock details' || MODULE_SYNC_FAILED - } - - def 'Reset failed CM Handles #scenario.'() { - given: 'cm handles in an locked state' - def lockedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) - .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, '').withLastUpdatedTimeNow().build() - def yangModelCmHandle1 = new YangModelCmHandle(id: 'cm-handle-1', compositeState: lockedState) - def yangModelCmHandle2 = new YangModelCmHandle(id: 'cm-handle-2', compositeState: lockedState) - def expectedCmHandleStatePerCmHandle = [(yangModelCmHandle1): CmHandleState.ADVISED] - and: 'clear in progress map' - resetModuleSyncStartedOnCmHandles(moduleSyncStartedOnCmHandles) - and: 'add cm handle entry into progress map' - moduleSyncStartedOnCmHandles.put('cm-handle-1', 'started') - moduleSyncStartedOnCmHandles.put('cm-handle-2', 'started') - and: 'sync utils retry locked cm handle returns #isReadyForRetry' - mockSyncUtils.needsModuleSyncRetryOrUpgrade(lockedState) >>> isReadyForRetry - when: 'resetting failed cm handles' - objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2]) - then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry' - expectedNumberOfInvocationsToUpdateCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle) - and: 'after reset performed size of in progress map' - assert moduleSyncStartedOnCmHandles.size() == inProgressMapSize - where: - scenario | isReadyForRetry | inProgressMapSize || expectedNumberOfInvocationsToUpdateCmHandleState - 'retry locked cm handle' | [true, false] | 1 || 1 - 'do not retry locked cm handle' | [false, false] | 2 || 0 - } - - def 'Module Sync ADVISED cm handle without entry in progress map.'() { - given: 'cm handles in an ADVISED state' - def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) - and: 'the inventory persistence cm handle returns a ADVISED state for the any handle' - mockInventoryPersistence.getCmHandleState(_) >> new CompositeState(cmHandleState: CmHandleState.ADVISED) - and: 'entry in progress map for other cm handle' - moduleSyncStartedOnCmHandles.put('other-cm-handle', 'started') - when: 'module sync poll is executed' - objectUnderTest.performModuleSync([cmHandle1], batchCount) - then: 'module sync service is invoked for cm handle' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } - and: 'the entry for other cm handle is still in the progress map' - assert moduleSyncStartedOnCmHandles.get('other-cm-handle') != null - } - - def 'Remove already processed cm handle id from hazelcast map'() { - given: 'hazelcast map contains cm handle id' - moduleSyncStartedOnCmHandles.put('ch-1', 'started') - when: 'remove cm handle entry' - objectUnderTest.removeResetCmHandleFromModuleSyncMap('ch-1') - then: 'an event is logged with level INFO' - def loggingEvent = getLoggingEvent() - assert loggingEvent.level == Level.INFO - and: 'the log indicates the cm handle entry is removed successfully' - assert loggingEvent.formattedMessage == 'ch-1 removed from in progress map' - } - - def 'Remove non-existing cm handle id from hazelcast map'() { - given: 'hazelcast map does not contains cm handle id' - def result = moduleSyncStartedOnCmHandles.get('non-existing-cm-handle') - assert result == null - when: 'remove cm handle entry from hazelcast map' - objectUnderTest.removeResetCmHandleFromModuleSyncMap('non-existing-cm-handle') - then: 'no event is logged' - def loggingEvent = getLoggingEvent() - assert loggingEvent == null - } - - def cmHandleAsDataNodeByIdAndState(cmHandleId, cmHandleState) { - return new DataNode(anchorName: cmHandleId, leaves: ['id': cmHandleId, 'cm-handle-state': cmHandleState]) - } - - def assertBatch(args, expectedCmHandleStatePerCmHandleIds, expectedCmHandleState) { - { - Map actualCmHandleStatePerCmHandle = args[0] - assert actualCmHandleStatePerCmHandle.size() == expectedCmHandleStatePerCmHandleIds.size() - actualCmHandleStatePerCmHandle.each { - assert expectedCmHandleStatePerCmHandleIds.contains(it.key.id) - assert it.value == expectedCmHandleState - } - } - return true - } - - def resetModuleSyncStartedOnCmHandles(moduleSyncStartedOnCmHandles) { - moduleSyncStartedOnCmHandles.clear(); - } - - def getLoggingEvent() { - return logger.list[0] - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy deleted file mode 100644 index 1752a17a19..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ /dev/null @@ -1,121 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation - * Modifications Copyright (C) 2022 Bell Canada - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.inventory.sync - -import com.hazelcast.map.IMap -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor -import java.util.concurrent.ArrayBlockingQueue -import org.onap.cps.spi.model.DataNode -import spock.lang.Specification - -class ModuleSyncWatchdogSpec extends Specification { - - def mockSyncUtils = Mock(ModuleOperationsUtils) - - def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE - - def moduleSyncWorkQueue = new ArrayBlockingQueue(testQueueCapacity) - - def mockModuleSyncStartedOnCmHandles = Mock(IMap) - - def mockModuleSyncTasks = Mock(ModuleSyncTasks) - - def spiedAsyncTaskExecutor = Spy(AsyncTaskExecutor) - - def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor) - - void setup() { - spiedAsyncTaskExecutor.setupThreadPool() - } - - def 'Module sync advised cm handles with #scenario.'() { - given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) - and: 'the executor has enough available threads' - spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3 - when: ' module sync is started' - objectUnderTest.moduleSyncAdvisedCmHandles() - then: 'it performs #expectedNumberOfTaskExecutions tasks' - expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_) - where: 'the following parameter are used' - scenario | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions - 'less then 1 batch' | 1 || 1 - 'exactly 1 batch' | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 1 - '2 batches' | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 2 - 'queue capacity' | testQueueCapacity || 3 - 'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3 - } - - def 'Module sync advised cm handles starts with no available threads.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) - and: 'the executor first has no threads but has one thread on the second attempt' - spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >>> [ 0, 1 ] - when: ' module sync is started' - objectUnderTest.moduleSyncAdvisedCmHandles() - then: 'it performs one task' - 1 * spiedAsyncTaskExecutor.executeTask(*_) - } - - def 'Module sync advised cm handles already handled.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) - and: 'the executor has a thread available' - spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 1 - and: 'the semaphore cache indicates the cm handle is already being processed' - mockModuleSyncStartedOnCmHandles.putIfAbsent(*_) >> 'Started' - when: ' module sync is started' - objectUnderTest.moduleSyncAdvisedCmHandles() - then: 'it does NOT execute a task to process the (empty) batch' - 0 * spiedAsyncTaskExecutor.executeTask(*_) - } - - def 'Module sync with previous cm handle(s) left in work queue.'() { - given: 'there is still a cm handle in the queue' - moduleSyncWorkQueue.offer(new DataNode()) - and: 'sync utilities returns many advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(500) - and: 'the executor has plenty threads available' - spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 10 - when: ' module sync is started' - objectUnderTest.moduleSyncAdvisedCmHandles() - then: 'it does executes only one task to process the remaining handle in the queue' - 1 * spiedAsyncTaskExecutor.executeTask(*_) - } - - def 'Reset failed cm handles.'() { - given: 'sync utilities returns failed cm handles' - def failedCmHandles = [new YangModelCmHandle()] - mockSyncUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles - when: 'reset failed cm handles is started' - objectUnderTest.resetPreviouslyFailedCmHandles() - then: 'it is delegated to the module sync task (service)' - 1 * mockModuleSyncTasks.resetFailedCmHandles(failedCmHandles) - } - - def createDataNodes(numberOfDataNodes) { - def dataNodes = [] - (1..numberOfDataNodes).each {dataNodes.add(new DataNode())} - return dataNodes - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy deleted file mode 100644 index f37d5a41eb..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/config/WatchdogSchedulingConfigurerSpec.groovy +++ /dev/null @@ -1,41 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.inventory.sync.config - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration -import spock.lang.Specification - -@SpringBootTest -@ContextConfiguration(classes = [WatchdogSchedulingConfigurer]) -class WatchdogSchedulingConfigurerSpec extends Specification { - - @Autowired - WatchdogSchedulingConfigurer watchdogSchedulingConfigurer - - def 'Validate watchdog scheduling configuration'() { - given: 'task scheduler configuration properties are loaded as map' - def linkedHashMap = watchdogSchedulingConfigurer.taskScheduler().getProperties() - expect: 'thread name prefix is mapped correctly' - assert linkedHashMap.'threadNamePrefix' == 'watchdog-th-' - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy deleted file mode 100644 index 64ecafefc7..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/executor/AsyncTaskExecutorSpec.groovy +++ /dev/null @@ -1,62 +0,0 @@ -/* - * ============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.api.impl.inventory.sync.executor - -import org.onap.cps.ncmp.api.impl.inventory.sync.executor.AsyncTaskExecutor -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import spock.lang.Specification -import java.util.concurrent.TimeoutException -import java.util.function.Supplier - -@SpringBootTest(classes = AsyncTaskExecutor) -class AsyncTaskExecutorSpec extends Specification { - - @Autowired - AsyncTaskExecutor objectUnderTest - def mockTaskSupplier = Mock(Supplier) - - def 'Parallelism level configuration.'() { - expect: 'Parallelism level is configured with the correct value' - assert objectUnderTest.getAsyncTaskParallelismLevel() == 3 - } - - def 'Task completion with #caseDescriptor.'() { - when: 'task completion is handled' - def irrelevantResponse = null - objectUnderTest.handleTaskCompletion(irrelevantResponse, exception); - then: 'any exception is swallowed by the task completion (logged)' - noExceptionThrown() - where: 'following cases are tested' - caseDescriptor | exception - 'no exception' | null - 'time out exception' | new TimeoutException("time-out") - 'unexpected exception' | new Exception("some exception") - } - - def 'Task execution.'() { - when: 'a task is submitted for execution' - objectUnderTest.executeTask(() -> mockTaskSupplier, 0) - then: 'the task submission is successful' - noExceptionThrown() - } - -} 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 a0c6d0027e..1e877f4a88 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 @@ -21,25 +21,16 @@ package org.onap.cps.ncmp.api.impl.operations -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL -import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING -import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE -import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ -import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR - import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.events.EventsPublisher import org.onap.cps.ncmp.api.impl.config.DmiProperties import org.onap.cps.ncmp.api.impl.exception.DmiClientRequestException import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.api.models.CmResourceAddress +import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.utils.TestUtils -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired @@ -47,8 +38,17 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.test.context.ContextConfiguration -import spock.lang.Shared import reactor.core.publisher.Mono +import spock.lang.Shared + +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL +import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING +import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE +import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ +import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA @SpringBootTest @ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations]) 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 136ff78324..f224a5cc0e 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 @@ -22,10 +22,10 @@ package org.onap.cps.ncmp.api.impl.operations import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.spockframework.spring.SpringBean import spock.lang.Shared import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy index 886648a7e9..65ab7a7fd2 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/TrustLevelManagerSpec.groovy @@ -21,8 +21,8 @@ package org.onap.cps.ncmp.api.impl.trustlevel import org.onap.cps.ncmp.api.impl.events.avc.ncmptoclient.AvcEventPublisher -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import spock.lang.Specification class TrustLevelManagerSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy index 53057573f3..f5835ff857 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/trustlevel/dmiavailability/DmiPluginWatchDogSpec.groovy @@ -21,9 +21,9 @@ package org.onap.cps.ncmp.api.impl.trustlevel.dmiavailability import org.onap.cps.ncmp.api.impl.client.DmiRestClient -import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueryService import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService import spock.lang.Specification class DmiPluginWatchDogSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy index 1e843676be..5be966b0fc 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/AlternateIdCheckerSpec.groovy @@ -20,9 +20,9 @@ package org.onap.cps.ncmp.api.impl.utils -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy deleted file mode 100644 index 100705ff57..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/CmHandleQueryConditionsSpec.groovy +++ /dev/null @@ -1,36 +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.api.impl.utils - -import spock.lang.Specification - -class CmHandleQueryConditionsSpec extends Specification { - - def 'CmHandle query condition names.'() { - expect: '3 conditions with the correct names' - assert CmHandleQueryConditions.ALL_CONDITION_NAMES.size() == 4 - assert CmHandleQueryConditions.ALL_CONDITION_NAMES.containsAll('hasAllProperties', - 'hasAllModules', - 'cmHandleWithCpsPath', - 'cmHandleWithTrustLevel') - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy index 9f84fb0739..2e04694301 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeBaseSpec.groovy @@ -20,11 +20,11 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME - import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Specification +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME + class DataNodeBaseSpec extends Specification { def leaves1 = [status:'PENDING', cmHandleId:'CMHandle3', details:'Subscription forwarded to dmi plugin'] as Map diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy index 05e5f58983..9481613d41 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/DataNodeHelperSpec.groovy @@ -20,10 +20,10 @@ package org.onap.cps.ncmp.api.impl.utils -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME - import org.onap.cps.spi.model.DataNodeBuilder +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME + class DataNodeHelperSpec extends DataNodeBaseSpec { def 'Get data node leaves as expected from a nested data node.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy deleted file mode 100644 index 00edb8d0da..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/InventoryQueryConditionsSpec.groovy +++ /dev/null @@ -1,36 +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.api.impl.utils - -import spock.lang.Specification - -class InventoryQueryConditionsSpec extends Specification { - - def 'Inventory query condition names.'() { - expect: '3 conditions with the correct names' - assert InventoryQueryConditions.ALL_CONDITION_NAMES.size() == 3 - assert InventoryQueryConditions.ALL_CONDITION_NAMES.containsAll('hasAllProperties', - 'hasAllAdditionalProperties', - 'cmHandleWithDmiPlugin') - } - - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy deleted file mode 100644 index 54befb4464..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy +++ /dev/null @@ -1,124 +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.api.impl.utils - -import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters -import org.onap.cps.spi.exceptions.DataValidationException -import org.onap.cps.spi.model.ConditionProperties -import spock.lang.Specification - -class RestQueryParametersValidatorSpec extends Specification { - - def 'CM Handle Query validation: empty query.'() { - given: 'a cm handle query' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, []) - then: 'data validation exception is not thrown' - noExceptionThrown() - } - - def 'CM Handle Query validation: normal query.'() { - given: 'a cm handle query' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def condition = new ConditionProperties() - condition.conditionName = 'validConditionName' - condition.conditionParameters = [['key':'value']] - cmHandleQueryParameters.cmHandleQueryParameters = [condition] - when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) - then: 'data validation exception is not thrown' - noExceptionThrown() - } - - def 'CM Handle Query validation: #scenario.'() { - given: 'a cm handle query' - def cmHandleQueryParameters = new CmHandleQueryServiceParameters() - def condition = new ConditionProperties() - condition.conditionName = conditionName - condition.conditionParameters = conditionParameters - cmHandleQueryParameters.cmHandleQueryParameters = [condition] - when: 'validator is invoked' - RestQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) - then: 'a data validation exception is thrown' - def thrown = thrown(DataValidationException) - and: 'the exception details contain the correct significant term ' - assert thrown.details.contains(expectedWordInDetails) - where: - scenario | conditionName | conditionParameters || expectedWordInDetails - 'unknown condition name' | 'unknownCondition' | [['key': 'value']] || 'conditionName' - 'no condition name' | '' | [['key': 'value']] || 'conditionName' - 'empty conditions' | 'validConditionName' | [] || 'conditionsParameters' - 'empty properties' | 'validConditionName' | [[:]] || 'conditionsParameter' - 'too many properties' | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter' - 'empty key' | 'validConditionName' | [['': 'wrong']] || 'conditionsParameter' - } - - def 'CM Handle Query validation: validate module name condition properties - valid query.'() { - given: 'a condition property' - def conditionProperty = [moduleName: 'value'] - when: 'validator is invoked' - RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) - then: 'data validation exception is not thrown' - noExceptionThrown() - } - - def 'CM Handle Query validation: validate module name condition properties - #scenario.'() { - when: 'validator is invoked' - RestQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) - then: 'a data validation exception is thrown' - thrown(DataValidationException) - where: - scenario | conditionProperty - 'invalid value' | [moduleName: ''] - 'invalid name' | [wrongName: 'value'] - } - - def 'Validate CmHandle where an exception is thrown due to #scenario.'() { - when: 'the validator is called on a cps path condition property' - RestQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty) - then: 'a data validation exception is thrown' - def e = thrown(DataValidationException) - and: 'exception message matches the expected message' - e.details.contains(exceptionMessage) - where: - scenario | conditionProperty || exceptionMessage - 'more than one condition is supplied' | ['cpsPath':'some-path', 'cpsPath2':'some-path'] || 'Only one condition property is allowed for the CPS path query.' - 'cpsPath key not supplied' | ['wrong-key':'some-path'] || 'Wrong CPS path condition property. - expecting "cpsPath" as the condition property.' - 'cpsPath not supplied' | ['cpsPath':''] || 'Wrong CPS path. - please supply a valid CPS path.' - } - - def 'No conditions.'() { - expect: 'no conditions always returns true' - RestQueryParametersValidator.validateCpsPathConditionProperties([:]) == true - } - - def 'Validate CmHandle where #scenario.'() { - when: 'the validator is called on a cps path condition property' - def result = RestQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath]) - then: 'the expected boolean value is returned' - result == expectedBoolean - where: - scenario | cpsPath || expectedBoolean - 'cpsPath is valid' | '/some/valid/path' || true - 'cpsPath attempts to query private properties' | "//additional-properties[@some-property='some-value']" || false - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy index 0ae348c074..3f0d98934c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/YangDataConverterSpec.groovy @@ -20,6 +20,7 @@ package org.onap.cps.ncmp.api.impl.utils +import org.onap.cps.ncmp.impl.utils.YangDataConverter import org.onap.cps.spi.model.DataNode import spock.lang.Specification @@ -34,7 +35,7 @@ class YangDataConverterSpec extends Specification{ def dataNodeCmHandle = new DataNode(leaves:['id':'sample-id'], childDataNodes:[dataNodeAdditionalProperties, dataNodePublicProperties]) when: 'the dataNode is converted' def yangModelCmHandle = - YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle) + YangDataConverter.convertCmHandleToYangModel(dataNodeCmHandle) then: 'the converted object has the correct id' assert yangModelCmHandle.id == 'sample-id' and: 'the additional (dmi, private) properties are included' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy index 6530685927..d8d04d44aa 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/data/operation/ResourceDataOperationRequestUtilsSpec.groovy @@ -20,10 +20,6 @@ package org.onap.cps.ncmp.api.impl.utils.data.operation -import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.ADVISED -import static org.onap.cps.ncmp.api.impl.inventory.CmHandleState.READY - import com.fasterxml.jackson.databind.ObjectMapper import io.cloudevents.CloudEvent import io.cloudevents.kafka.CloudEventDeserializer @@ -33,18 +29,23 @@ import org.onap.cps.events.EventsPublisher import org.onap.cps.ncmp.api.impl.operations.DmiDataOperation import org.onap.cps.ncmp.api.impl.operations.OperationType import org.onap.cps.ncmp.api.impl.utils.context.CpsApplicationContext -import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder import org.onap.cps.ncmp.api.kafka.MessagingBaseSpec import org.onap.cps.ncmp.api.models.DataOperationRequest import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean import org.springframework.test.context.ContextConfiguration import org.springframework.util.LinkedMultiValueMap + import java.time.Duration +import static org.onap.cps.ncmp.api.impl.events.mapper.CloudEventMapper.toTargetEvent +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED +import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.READY + @ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext]) class ResourceDataOperationRequestUtilsSpec extends MessagingBaseSpec { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy deleted file mode 100644 index cfc6ffda19..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandleSpec.groovy +++ /dev/null @@ -1,115 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021-2024 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.impl.yangmodels - -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder -import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState -import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle -import spock.lang.Specification - -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA -import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL - -class YangModelCmHandleSpec extends Specification { - - def 'Creating yang model cm handle from a service api cm handle.'() { - given: 'a cm handle with properties' - def ncmpServiceCmHandle = new NcmpServiceCmHandle() - ncmpServiceCmHandle.cmHandleId = 'cm-handle-id01' - ncmpServiceCmHandle.dmiProperties = [myDmiProperty:'value1'] - ncmpServiceCmHandle.publicProperties = [myPublicProperty:'value2'] - and: 'with a composite state' - def compositeState = new CompositeStateBuilder() - .withCmHandleState(CmHandleState.LOCKED) - .withLastUpdatedTime('some-update-time') - .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, 'locked details') - .withOperationalDataStores(DataStoreSyncState.SYNCHRONIZED, 'some-sync-time').build() - ncmpServiceCmHandle.setCompositeState(compositeState) - when: 'it is converted to a yang model cm handle' - def objectUnderTest = YangModelCmHandle.toYangModelCmHandle('', '', '', ncmpServiceCmHandle,'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier') - then: 'the result has the right size' - assert objectUnderTest.dmiProperties.size() == 1 - and: 'the result has the correct values for module set tag, alternate ID, and data producer identifier' - assert objectUnderTest.moduleSetTag == 'my-module-set-tag' - assert objectUnderTest.alternateId == 'my-alternate-id' - assert objectUnderTest.dataProducerIdentifier == 'my-data-producer-identifier' - and: 'the DMI property in the result has the correct name and value' - assert objectUnderTest.dmiProperties[0].name == 'myDmiProperty' - assert objectUnderTest.dmiProperties[0].value == 'value1' - and: 'the public property in the result has the correct name and value' - assert objectUnderTest.publicProperties[0].name == 'myPublicProperty' - assert objectUnderTest.publicProperties[0].value == 'value2' - and: 'the composite state matches the composite state of the ncmpServiceCmHandle' - objectUnderTest.getCompositeState().cmHandleState == CmHandleState.LOCKED - objectUnderTest.getCompositeState() == ncmpServiceCmHandle.getCompositeState() - } - - def 'Resolve DMI service name: #scenario and #requiredService service require.'() { - given: 'a yang model cm handle' - def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, - dmiModelServiceName, new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1'),'', '', '') - expect: - assert objectUnderTest.resolveDmiServiceName(requiredService) == expectedService - where: - scenario | dmiServiceName | dmiDataServiceName | dmiModelServiceName | requiredService || expectedService - 'common service registered' | 'common service' | 'does not matter' | 'does not matter' | DATA || 'common service' - 'common service registered' | 'common service' | 'does not matter' | 'does not matter' | MODEL || 'common service' - 'common service empty' | '' | 'data service' | 'does not matter' | DATA || 'data service' - 'common service empty' | '' | 'does not matter' | 'model service' | MODEL || 'model service' - 'common service blank' | ' ' | 'data service' | 'does not matter' | DATA || 'data service' - 'common service blank' | ' ' | 'does not matter' | 'model service' | MODEL || 'model service' - 'common service null ' | null | 'data service' | 'does not matter' | DATA || 'data service' - 'common service null' | null | 'does not matter' | 'model service' | MODEL || 'model service' - 'only model service registered' | null | null | 'does not matter' | DATA || null - 'only data service registered' | null | 'does not matter' | null | MODEL || null - } - - def 'Yang Model Cm Handle Deep Copy'() { - given: 'a yang model cm handle' - def currentYangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', - publicProperties: [new YangModelCmHandle.Property('publicProperty1', 'value1')], - dmiProperties: [new YangModelCmHandle.Property('dmiProperty1', 'value1')], - compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)) - when: 'a deep copy is created' - def yangModelCmhandleDeepCopy = YangModelCmHandle.deepCopyOf(currentYangModelCmHandle) - and: 'we try to mutate current yang model cm handle' - currentYangModelCmHandle.id = 'cmhandle-changed' - currentYangModelCmHandle.dmiProperties = [new YangModelCmHandle.Property('updatedPublicProperty1', 'value1')] - currentYangModelCmHandle.publicProperties = [new YangModelCmHandle.Property('updatedDmiProperty1', 'value1')] - currentYangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY - currentYangModelCmHandle.compositeState.dataSyncEnabled = true - then: 'there is no change in the deep copied object' - assert yangModelCmhandleDeepCopy.id == 'cmhandle' - assert yangModelCmhandleDeepCopy.dmiProperties == [new YangModelCmHandle.Property('dmiProperty1', 'value1')] - assert yangModelCmhandleDeepCopy.publicProperties == [new YangModelCmHandle.Property('publicProperty1', 'value1')] - assert yangModelCmhandleDeepCopy.compositeState.cmHandleState == CmHandleState.ADVISED - assert yangModelCmhandleDeepCopy.compositeState.dataSyncEnabled == false - and: 'equality on reference and hashcode behave as expected' - assert currentYangModelCmHandle.hashCode() != yangModelCmhandleDeepCopy.hashCode() - assert currentYangModelCmHandle != yangModelCmhandleDeepCopy - - } - - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy new file mode 100644 index 0000000000..055a6e7448 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CmHandleRegistrationResponseSpec.groovy @@ -0,0 +1,102 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada + * Modifications Copyright (C) 2023-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.api.inventory.models + +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import spock.lang.Specification + +import java.util.stream.Collectors + +import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR + +class CmHandleRegistrationResponseSpec extends Specification { + + def 'Successful cm-handle Registration Response'() { + when: 'cm-handle response is created' + def cmHandleRegistrationResponse = CmHandleRegistrationResponse.createSuccessResponse('cmHandle') + then: 'a success response is returned' + with(cmHandleRegistrationResponse) { + assert it.cmHandle == 'cmHandle' + assert it.status == Status.SUCCESS + } + and: 'error details are null' + cmHandleRegistrationResponse.ncmpResponseStatus == null + cmHandleRegistrationResponse.errorText == null + } + + def 'Failed cm-handle Registration Response: for unexpected exception'() { + when: 'cm-handle response is created for an unexpected exception' + def cmHandleRegistrationResponse = + CmHandleRegistrationResponse.createFailureResponse('cmHandle', new Exception('unexpected error')) + then: 'the response is created with expected value' + with(cmHandleRegistrationResponse) { + assert it.ncmpResponseStatus == UNKNOWN_ERROR + assert it.cmHandle == 'cmHandle' + assert errorText == 'unexpected error' + } + } + + def 'Failed cm-handle Registration Response'() { + when: 'cm-handle failure response is created' + def cmHandleRegistrationResponse = + CmHandleRegistrationResponse.createFailureResponse('cmHandle', CM_HANDLE_ALREADY_EXIST) + then: 'the response is created with expected value' + with(cmHandleRegistrationResponse) { + assert it.ncmpResponseStatus == CM_HANDLE_ALREADY_EXIST + assert it.cmHandle == 'cmHandle' + assert it.status == Status.FAILURE + assert errorText == CM_HANDLE_ALREADY_EXIST.message + } + } + + def 'Failed cm-handle Registration with multiple responses.'() { + when: 'cm-handle failure response is created for 2 xpaths' + def cmHandleRegistrationResponses = + CmHandleRegistrationResponse.createFailureResponsesFromXpaths(["somePathWithId[@id='123']", "somePathWithId[@id='456']"], CM_HANDLE_ALREADY_EXIST) + then: 'the response has the correct cm handle ids' + assert cmHandleRegistrationResponses.size() == 2 + assert cmHandleRegistrationResponses.stream().map(it -> it.cmHandle).collect(Collectors.toList()) + .containsAll(['123','456']) + } + + def 'Failed cm-handle Registration with multiple responses with an unexpected xpath.'() { + when: 'cm-handle failure response is created for one valid and one unexpected xpath' + def cmHandleRegistrationResponses = + CmHandleRegistrationResponse.createFailureResponsesFromXpaths(["somePathWithId[@id='123']", "valid/xpath/without-id[@key='123']"], CM_HANDLE_ALREADY_EXIST) + then: 'the response has only one entry' + assert cmHandleRegistrationResponses.size() == 1 + } + + def 'Failed cm-handle registration based on cm handle id and registration error'() { + when: 'the failure response is created with "alternate id already associated" error code for 1 cm handle' + def cmHandleRegistrationResponses = + CmHandleRegistrationResponse.createFailureResponses(['ch 1'], ALTERNATE_ID_ALREADY_ASSOCIATED) + then: 'the response with expected values' + assert cmHandleRegistrationResponses[0].cmHandle == 'ch 1' + assert cmHandleRegistrationResponses[0].status == Status.FAILURE + assert cmHandleRegistrationResponses[0].ncmpResponseStatus == ALTERNATE_ID_ALREADY_ASSOCIATED + assert cmHandleRegistrationResponses[0].errorText == 'alternate id already associated' + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy new file mode 100644 index 0000000000..dfed3c787d --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateBuilderSpec.groovy @@ -0,0 +1,91 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada + * 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. + * 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.inventory.models + + +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.DataNodeBuilder +import spock.lang.Specification + +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + +class CompositeStateBuilderSpec extends Specification { + + def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + + def static cmHandleId = 'myHandle1' + def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}/state']" + def static stateDataNodes = [new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/lock-reason") + .withLeaves(['reason': 'MODULE_SYNC_FAILED', 'details': 'lock details']).build(), + new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/datastores") + .withChildDataNodes(Arrays.asList(new DataNodeBuilder() + .withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/state/datastores/operational") + .withLeaves(['sync-state': 'UNSYNCHRONIZED']).build())).build()] + def static cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: stateDataNodes, leaves: ['cm-handle-state': 'ADVISED']) + + def "Composite State Specification"() { + when: 'using composite state builder ' + def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED) + .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED,"").withOperationalDataStores(DataStoreSyncState.UNSYNCHRONIZED, + formattedDateAndTime.toString()).withLastUpdatedTime(formattedDateAndTime).build() + then: 'it matches expected cm handle state and data store sync state' + assert compositeState.cmHandleState == CmHandleState.ADVISED + assert compositeState.dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.UNSYNCHRONIZED + } + + def "Build composite state from DataNode "() { + given: "a Data Node " + new DataNode(leaves: ['cm-handle-state': 'ADVISED']) + when: 'build from data node function is invoked' + def compositeState = new CompositeStateBuilder().fromDataNode(cmHandleDataNode).build() + then: 'it matches expected state model as JSON' + assert compositeState.cmHandleState == CmHandleState.ADVISED + } + + def 'CompositeStateBuilder build'() { + given: 'A CompositeStateBuilder with all private fields set' + def finalCompositeStateBuilder = new CompositeStateBuilder() + .withCmHandleState(CmHandleState.ADVISED) + .withLastUpdatedTime(formattedDateAndTime.toString()) + .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, 'locked details') + .withOperationalDataStores(DataStoreSyncState.SYNCHRONIZED, formattedDateAndTime) + when: 'build is called' + def result = finalCompositeStateBuilder.build() + then: 'result is of the correct type' + assert result.class == CompositeState.class + and: 'built result should have correct values' + assert !result.getDataSyncEnabled() + assert result.getLastUpdateTime() == formattedDateAndTime + assert result.getLockReason().getLockReasonCategory() == LockReasonCategory.MODULE_SYNC_FAILED + assert result.getLockReason().getDetails() == 'locked details' + assert result.getCmHandleState() == CmHandleState.ADVISED + assert result.getDataStores().getOperationalDataStore().getDataStoreSyncState() == DataStoreSyncState.SYNCHRONIZED + assert result.getDataStores().getOperationalDataStore().getLastSyncTime() == formattedDateAndTime + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy new file mode 100644 index 0000000000..f6e4c8870e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/CompositeStateSpec.groovy @@ -0,0 +1,64 @@ +/* + * ============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.api.inventory.models + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +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.models.CompositeState.DataStores +import static org.onap.cps.ncmp.api.inventory.models.CompositeState.Operational +import static org.onap.cps.ncmp.utils.TestUtils.getResourceFileContent +import static org.springframework.util.StringUtils.trimAllWhitespace + +class CompositeStateSpec extends Specification { + + def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + def objectMapper = new ObjectMapper() + + def "Composite State Specification"() { + given: "a Composite State" + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), + lastUpdateTime: formattedDateAndTime.toString(), + dataSyncEnabled: false, + dataStores: dataStores()) + when: 'it is represented as JSON' + def resultJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(compositeState) + then: 'it matches expected state model as JSON' + def expectedJson = getResourceFileContent('expectedStateModel.json') + assert trimAllWhitespace(expectedJson) == trimAllWhitespace(resultJson) + } + + def dataStores() { + DataStores.builder().operationalDataStore(Operational.builder() + .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) + .lastSyncTime(formattedDateAndTime.toString()).build()) + .build() + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy new file mode 100644 index 0000000000..068997591c --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/models/YangResourceTest.groovy @@ -0,0 +1,21 @@ +package org.onap.cps.ncmp.api.inventory.models + + +import spock.lang.Specification + +class YangResourceSpec extends Specification { + + YangResource objectUnderTest = new YangResource(moduleName: 'module name', + revision:'revision', + yangSource:'source') + + def 'Yang resource attributes'() { + expect: 'correct module name' + objectUnderTest.moduleName == 'module name' + and: 'correct revision (this property is not used in production code, hence the need for this test)' + objectUnderTest.revision == 'revision' + and: 'correct yang source' + objectUnderTest.yangSource == 'source' + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy deleted file mode 100644 index 7803ae319f..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy +++ /dev/null @@ -1,101 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022 Bell Canada - * Modifications Copyright (C) 2023-2024 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.api.models - -import static org.onap.cps.ncmp.api.NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED -import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST -import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR - -import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status -import spock.lang.Specification -import java.util.stream.Collectors - -class CmHandleRegistrationResponseSpec extends Specification { - - def 'Successful cm-handle Registration Response'() { - when: 'cm-handle response is created' - def cmHandleRegistrationResponse = CmHandleRegistrationResponse.createSuccessResponse('cmHandle') - then: 'a success response is returned' - with(cmHandleRegistrationResponse) { - assert it.cmHandle == 'cmHandle' - assert it.status == Status.SUCCESS - } - and: 'error details are null' - cmHandleRegistrationResponse.ncmpResponseStatus == null - cmHandleRegistrationResponse.errorText == null - } - - def 'Failed cm-handle Registration Response: for unexpected exception'() { - when: 'cm-handle response is created for an unexpected exception' - def cmHandleRegistrationResponse = - CmHandleRegistrationResponse.createFailureResponse('cmHandle', new Exception('unexpected error')) - then: 'the response is created with expected value' - with(cmHandleRegistrationResponse) { - assert it.ncmpResponseStatus == UNKNOWN_ERROR - assert it.cmHandle == 'cmHandle' - assert errorText == 'unexpected error' - } - } - - def 'Failed cm-handle Registration Response'() { - when: 'cm-handle failure response is created' - def cmHandleRegistrationResponse = - CmHandleRegistrationResponse.createFailureResponse('cmHandle', CM_HANDLE_ALREADY_EXIST) - then: 'the response is created with expected value' - with(cmHandleRegistrationResponse) { - assert it.ncmpResponseStatus == CM_HANDLE_ALREADY_EXIST - assert it.cmHandle == 'cmHandle' - assert it.status == Status.FAILURE - assert errorText == CM_HANDLE_ALREADY_EXIST.message - } - } - - def 'Failed cm-handle Registration with multiple responses.'() { - when: 'cm-handle failure response is created for 2 xpaths' - def cmHandleRegistrationResponses = - CmHandleRegistrationResponse.createFailureResponsesFromXpaths(["somePathWithId[@id='123']", "somePathWithId[@id='456']"], CM_HANDLE_ALREADY_EXIST) - then: 'the response has the correct cm handle ids' - assert cmHandleRegistrationResponses.size() == 2 - assert cmHandleRegistrationResponses.stream().map(it -> it.cmHandle).collect(Collectors.toList()) - .containsAll(['123','456']) - } - - def 'Failed cm-handle Registration with multiple responses with an unexpected xpath.'() { - when: 'cm-handle failure response is created for one valid and one unexpected xpath' - def cmHandleRegistrationResponses = - CmHandleRegistrationResponse.createFailureResponsesFromXpaths(["somePathWithId[@id='123']", "valid/xpath/without-id[@key='123']"], CM_HANDLE_ALREADY_EXIST) - then: 'the response has only one entry' - assert cmHandleRegistrationResponses.size() == 1 - } - - def 'Failed cm-handle registration based on cm handle id and registration error'() { - when: 'the failure response is created with "alternate id already associated" error code for 1 cm handle' - def cmHandleRegistrationResponses = - CmHandleRegistrationResponse.createFailureResponses(['ch 1'], ALTERNATE_ID_ALREADY_ASSOCIATED) - then: 'the response with expected values' - assert cmHandleRegistrationResponses[0].cmHandle == 'ch 1' - assert cmHandleRegistrationResponses[0].status == Status.FAILURE - assert cmHandleRegistrationResponses[0].ncmpResponseStatus == ALTERNATE_ID_ALREADY_ASSOCIATED - assert cmHandleRegistrationResponses[0].errorText == 'alternate id already associated' - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy deleted file mode 100644 index 0a0c84e256..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/YangResourceTest.groovy +++ /dev/null @@ -1,20 +0,0 @@ -package org.onap.cps.ncmp.api.models - -import spock.lang.Specification - -class YangResourceSpec extends Specification { - - YangResource objectUnderTest = new YangResource(moduleName: 'module name', - revision:'revision', - yangSource:'source') - - def 'Yang resource attributes'() { - expect: 'correct module name' - objectUnderTest.moduleName == 'module name' - and: 'correct revision (this property is not used in production code, hence the need for this test)' - objectUnderTest.revision == 'revision' - and: 'correct yang source' - objectUnderTest.yangSource == 'source' - } - -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy new file mode 100644 index 0000000000..15526a0c86 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryParametersValidatorSpec.groovy @@ -0,0 +1,124 @@ +/* + * ============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.impl.inventory + +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.model.ConditionProperties +import spock.lang.Specification + +class CmHandleQueryParametersValidatorSpec extends Specification { + + def 'CM Handle Query validation: empty query.'() { + given: 'a cm handle query' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + when: 'validator is invoked' + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, []) + then: 'data validation exception is not thrown' + noExceptionThrown() + } + + def 'CM Handle Query validation: normal query.'() { + given: 'a cm handle query' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def condition = new ConditionProperties() + condition.conditionName = 'validConditionName' + condition.conditionParameters = [['key':'value']] + cmHandleQueryParameters.cmHandleQueryParameters = [condition] + when: 'validator is invoked' + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) + then: 'data validation exception is not thrown' + noExceptionThrown() + } + + def 'CM Handle Query validation: #scenario.'() { + given: 'a cm handle query' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def condition = new ConditionProperties() + condition.conditionName = conditionName + condition.conditionParameters = conditionParameters + cmHandleQueryParameters.cmHandleQueryParameters = [condition] + when: 'validator is invoked' + CmHandleQueryParametersValidator.validateCmHandleQueryParameters(cmHandleQueryParameters, ['validConditionName']) + then: 'a data validation exception is thrown' + def thrown = thrown(DataValidationException) + and: 'the exception details contain the correct significant term ' + assert thrown.details.contains(expectedWordInDetails) + where: + scenario | conditionName | conditionParameters || expectedWordInDetails + 'unknown condition name' | 'unknownCondition' | [['key': 'value']] || 'conditionName' + 'no condition name' | '' | [['key': 'value']] || 'conditionName' + 'empty conditions' | 'validConditionName' | [] || 'conditionsParameters' + 'empty properties' | 'validConditionName' | [[:]] || 'conditionsParameter' + 'too many properties' | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter' + 'empty key' | 'validConditionName' | [['': 'wrong']] || 'conditionsParameter' + } + + def 'CM Handle Query validation: validate module name condition properties - valid query.'() { + given: 'a condition property' + def conditionProperty = [moduleName: 'value'] + when: 'validator is invoked' + CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) + then: 'data validation exception is not thrown' + noExceptionThrown() + } + + def 'CM Handle Query validation: validate module name condition properties - #scenario.'() { + when: 'validator is invoked' + CmHandleQueryParametersValidator.validateModuleNameConditionProperties(conditionProperty) + then: 'a data validation exception is thrown' + thrown(DataValidationException) + where: + scenario | conditionProperty + 'invalid value' | [moduleName: ''] + 'invalid name' | [wrongName: 'value'] + } + + def 'Validate CmHandle where an exception is thrown due to #scenario.'() { + when: 'the validator is called on a cps path condition property' + CmHandleQueryParametersValidator.validateCpsPathConditionProperties(conditionProperty) + then: 'a data validation exception is thrown' + def e = thrown(DataValidationException) + and: 'exception message matches the expected message' + e.details.contains(exceptionMessage) + where: + scenario | conditionProperty || exceptionMessage + 'more than one condition is supplied' | ['cpsPath':'some-path', 'cpsPath2':'some-path'] || 'Only one condition property is allowed for the CPS path query.' + 'cpsPath key not supplied' | ['wrong-key':'some-path'] || 'Wrong CPS path condition property. - expecting "cpsPath" as the condition property.' + 'cpsPath not supplied' | ['cpsPath':''] || 'Wrong CPS path. - please supply a valid CPS path.' + } + + def 'No conditions.'() { + expect: 'no conditions always returns true' + CmHandleQueryParametersValidator.validateCpsPathConditionProperties([:]) == true + } + + def 'Validate CmHandle where #scenario.'() { + when: 'the validator is called on a cps path condition property' + def result = CmHandleQueryParametersValidator.validateCpsPathConditionProperties(['cpsPath':cpsPath]) + then: 'the expected boolean value is returned' + result == expectedBoolean + where: + scenario | cpsPath || expectedBoolean + 'cpsPath is valid' | '/some/valid/path' || true + 'cpsPath attempts to query private properties' | "//additional-properties[@some-property='some-value']" || false + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy new file mode 100644 index 0000000000..52ddcfb6c7 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -0,0 +1,208 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2023 Nordix Foundation + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * 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.impl.inventory + +import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.spi.CpsDataPersistenceService +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.utils.CpsValidator +import spock.lang.Shared +import spock.lang.Specification + +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + +class CmHandleQueryServiceImplSpec extends Specification { + + def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) + + def trustLevelPerDmiPlugin = [:] + + def trustLevelPerCmHandle = [ 'PNFDemo': TrustLevel.COMPLETE, 'PNFDemo2': TrustLevel.NONE, 'PNFDemo4': TrustLevel.NONE ] + + def mockCpsValidator = Mock(CpsValidator) + + def objectUnderTest = new CmHandleQueryServiceImpl(mockCpsDataPersistenceService, trustLevelPerDmiPlugin, trustLevelPerCmHandle, mockCpsValidator) + + @Shared + def static sampleDataNodes = [new DataNode()] + + def dataNodeWithPrivateField = '//additional-properties[@name=\"Contact3\" and @value=\"newemailforstore3@bookstore.com\"]/ancestor::cm-handles' + + def static pnfDemo = createDataNode('PNFDemo') + def static pnfDemo2 = createDataNode('PNFDemo2') + def static pnfDemo3 = createDataNode('PNFDemo3') + def static pnfDemo4 = createDataNode('PNFDemo4') + def static pnfDemo5 = createDataNode('PNFDemo5') + + def 'Query CmHandles with public properties query pair.'() { + given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' + mockResponses() + when: 'a query on cmhandle public properties is performed with a public property pair' + def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs) + then: 'the correct cm handle data objects are returned' + result.containsAll(expectedCmHandleIds) + result.size() == expectedCmHandleIds.size() + where: 'the following data is used' + scenario | publicPropertyPairs || expectedCmHandleIds + 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] + 'public property does not match' | [wont_match: 'wont_match'] || [] + '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] + '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] || [] + } + + def 'Query cm handles on trust level'() { + given: 'query properties for trust level COMPLETE' + def trustLevelPropertyQueryPairs = ['trustLevel' : TrustLevel.COMPLETE.toString()] + and: 'the dmi cache has been initialised and "knows" about my-dmi-plugin-identifier' + trustLevelPerDmiPlugin.put('my-dmi-plugin-identifier', TrustLevel.COMPLETE) + and: 'the DataNodes queried for a given cpsPath are returned from the persistence service' + mockResponses() + when: 'the query is run' + def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs) + then: 'the result contain trusted PNFDemo' + assert result.size() == 1 + assert result[0] == 'PNFDemo' + } + + def 'Query CmHandles using empty public properties query pair.'() { + when: 'a query on CmHandle public properties is executed using an empty map' + def result = objectUnderTest.queryCmHandlePublicProperties([:]) + then: 'no cm handles are returned' + result.size() == 0 + } + + def 'Query CmHandles using empty private properties query pair.'() { + when: 'a query on CmHandle private properties is executed using an empty map' + def result = objectUnderTest.queryCmHandleAdditionalProperties([:]) + then: 'no cm handles are returned' + result.size() == 0 + } + + def 'Query CmHandles by a private field\'s value.'() { + given: 'a data node exists with a certain additional-property' + mockCpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] + when: 'a query on CmHandle private properties is executed using a map' + def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) + then: 'one cm handle is returned' + result.size() == 1 + } + + def 'Get CmHandles by it\'s state.'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.ADVISED + and: 'the persistence service returns a list of data nodes' + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + '//state[@cm-handle-state="ADVISED"]/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) >> sampleDataNodes + when: 'cm handles are fetched by state' + def result = objectUnderTest.queryCmHandlesByState(cmHandleState) + then: 'the returned result matches the result from the persistence service' + assert result == sampleDataNodes + } + + def 'Check the state of a cmHandle when #scenario.'() { + given: 'a cm handle state to compare' + def cmHandleState = state + and: 'the persistence service returns a list of data nodes' + mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', + OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] + when: 'cm handles are compared by state' + def result = objectUnderTest.cmHandleHasState('some-cm-handle', cmHandleState) + then: 'the returned result matches the expected result from the persistence service' + result == expectedResult + where: + scenario | state || expectedResult + 'the provided state matches' | CmHandleState.READY || true + 'the provided state does not match'| CmHandleState.DELETED || false + } + + def 'Get Cm Handles state by Cm-Handle Id'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.READY + and: 'cps data service returns a list of data nodes' + mockCpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT + '/cm-handles[@id=\'some-cm-handle\']/state', + OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])] + when: 'cm handles are fetched by state and id' + def result = objectUnderTest.getCmHandleState('some-cm-handle') + then: 'the returned result is a list of data nodes returned by cps data service' + assert result == new DataNode(leaves: ['cm-handle-state': 'READY']) + } + + def 'Retrieve Cm Handles By Operational Sync State : UNSYNCHRONIZED'() { + given: 'a cm handle state to query' + def cmHandleState = CmHandleState.READY + and: 'cps data service returns a list of data nodes' + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + '//state/datastores/operational[@sync-state="'+'UNSYNCHRONIZED'+'"]/ancestor::cm-handles', OMIT_DESCENDANTS) >> sampleDataNodes + when: 'cm handles are fetched by the UNSYNCHRONIZED operational sync state' + def result = objectUnderTest.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) + then: 'the returned result is a list of data nodes returned by cps data service' + assert result == sampleDataNodes + } + + def 'Retrieve cm handle by cps path '() { + given: 'a cm handle state to query based on the cps path' + def cmHandleDataNode = new DataNode(xpath: 'xpath', leaves: ['cm-handle-state': 'LOCKED']) + def cpsPath = '//cps-path' + and: 'cps data service returns a valid data node' + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + cpsPath + '/ancestor::cm-handles', INCLUDE_ALL_DESCENDANTS) + >> Arrays.asList(cmHandleDataNode) + when: 'get cm handles by cps path is invoked' + def result = objectUnderTest.queryCmHandleAncestorsByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS) + then: 'the returned result is a list of data nodes returned by cps data service' + assert result.contains(cmHandleDataNode) + } + + def 'Get all cm handles by dmi plugin identifier'() { + given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' + mockResponses() + when: 'cm Handles are fetched for a given dmi plugin identifier' + def result = objectUnderTest.getCmHandleIdsByDmiPluginIdentifier('my-dmi-plugin-identifier') + then: 'result is the correct size' + assert result.size() == 3 + and: 'result contains the correct cm handles' + assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4') + } + + void mockResponses() { + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"newemailforstore2@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//public-properties[@name=\"Contact2\" and @value=\"\"]/ancestor::cm-handles', _) >> [] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"READY\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo3] + mockCpsDataPersistenceService.queryDataNodes(_, _, '//state[@cm-handle-state=\"LOCKED\"]/ancestor::cm-handles', _) >> [pnfDemo2, pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo2] + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-data-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo, pnfDemo4] + mockCpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@dmi-model-service-name=\'my-dmi-plugin-identifier\']', OMIT_DESCENDANTS) >> [pnfDemo2, pnfDemo4] + } + + def static createDataNode(dataNodeId) { + return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId]) + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy new file mode 100644 index 0000000000..0a5b4f4ecc --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServicePropertyHandlerSpec.groovy @@ -0,0 +1,295 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * Modifications Copyright (C) 2024 TechMahindra Ltd. + * ================================================================================ + * 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.impl.inventory + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.spi.exceptions.DataNodeNotFoundException +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.DataNodeBuilder +import org.onap.cps.utils.ContentType +import org.onap.cps.utils.JsonObjectMapper +import org.slf4j.LoggerFactory +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR + +class CmHandleRegistrationServicePropertyHandlerSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + def mockCpsDataService = Mock(CpsDataService) + def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + def mockAlternateIdChecker = Mock(AlternateIdChecker) + + def objectUnderTest = new CmHandleRegistrationServicePropertyHandler(mockInventoryPersistence, mockCpsDataService, jsonObjectMapper, mockAlternateIdChecker) + def logger = Spy(ListAppender) + + void setup() { + def setupLogger = ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)) + setupLogger.addAppender(logger) + setupLogger.setLevel(Level.DEBUG) + logger.start() + // Always accept all alternate IDs + mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(CmHandleRegistrationServicePropertyHandler.class)).detachAndStopAllAppenders() + } + + def static cmHandleId = 'myHandle1' + def static cmHandleXpath = "/dmi-registry/cm-handles[@id='${cmHandleId}']" + + def static propertyDataNodes = [new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp1']").withLeaves(['name': 'additionalProp1', 'value': 'additionalValue1']).build(), + new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(), + new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(), + new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()] + def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes, leaves: ['id': cmHandleId])] + + def 'Update CM Handle Public Properties: #scenario'() { + given: 'the CPS service return a CM handle' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection + and: 'an update cm handle request with public properties updates' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)] + when: 'update data node leaves is called with the update request' + objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'the replace list method is called with correct params' + 1 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> + { + assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() + assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) + } + } + where: 'following public properties updates are made' + scenario | updatedPublicProperties || expectedPropertiesAfterUpdate + 'property added' | ['newPubProp1': 'pub-val'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4'], ['newPubProp1': 'pub-val']] + 'property updated' | ['publicProp4': 'newPubVal'] || [['publicProp3': 'publicValue3'], ['publicProp4': 'newPubVal']] + 'property removed' | ['publicProp4': null] || [['publicProp3': 'publicValue3']] + 'property ignored(value is null)' | ['pub-prop': null] || [['publicProp3': 'publicValue3'], ['publicProp4': 'publicValue4']] + } + + def 'Update DMI Properties: #scenario'() { + given: 'the CPS service return a CM handle' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> cmHandleDataNodeAsCollection + and: 'an update cm handle request with DMI properties updates' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)] + when: 'update data node leaves is called with the update request' + objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'replace list method should is called with correct params' + expectedCallsToReplaceMethod * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) >> { args -> + { + assert args[1].leaves.size() == expectedPropertiesAfterUpdate.size() + assert args[1].leaves.containsAll(convertToProperties(expectedPropertiesAfterUpdate)) + } + } + where: 'following DMI properties updates are made' + scenario | updatedDmiProperties || expectedPropertiesAfterUpdate | expectedCallsToReplaceMethod + 'property added' | ['newAdditionalProp1': 'add-value'] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2'], ['newAdditionalProp1': 'add-value']] | 1 + 'property updated' | ['additionalProp1': 'newValue'] || [['additionalProp2': 'additionalValue2'], ['additionalProp1': 'newValue']] | 1 + 'property removed' | ['additionalProp1': null] || [['additionalProp2': 'additionalValue2']] | 1 + 'property ignored(value is null)' | ['new-prop': null] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 1 + 'no property changes' | [:] || [['additionalProp1': 'additionalValue1'], ['additionalProp2': 'additionalValue2']] | 0 + } + + def 'Update CM Handle Properties, remove all properties: #scenario'() { + given: 'the CPS service return a CM handle' + def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId], childDataNodes: originalPropertyDataNodes) + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [cmHandleDataNode] + and: 'an update cm handle request that removes all public properties(existing and non-existing)' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])] + when: 'update data node leaves is called with the update request' + objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'the replace list method is not called' + 0 * mockInventoryPersistence.replaceListContent(*_) + then: 'delete data node will be called for any existing property' + expectedCallsToDeleteDataNode * mockInventoryPersistence.deleteDataNode(_) >> { arg -> + { + assert arg[0].contains("@name='publicProp") + } + } + where: 'following public properties updates are made' + scenario | originalPropertyDataNodes || expectedCallsToDeleteDataNode + '2 original properties, both removed' | propertyDataNodes || 2 + 'no original properties' | [] || 0 + } + + def '#scenario error leads to #exception when we try to update cmHandle'() { + given: 'cm handles request' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: [:], dmiProperties: [:])] + and: 'data node cannot be found' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> { throw exception } + when: 'update data node leaves is called using correct parameters' + def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'one failed registration response' + response.size() == 1 + and: 'it has expected error details' + with(response.get(0)) { + assert it.status == Status.FAILURE + assert it.cmHandle == cmHandleId + assert it.ncmpResponseStatus == expectedError + assert it.errorText == expectedErrorText + } + where: + scenario | cmHandleId | exception || expectedError | expectedErrorText + 'Cm Handle does not exist' | 'cmHandleId' | new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' + 'Unknown' | 'cmHandleId' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + 'Invalid cm handle id' | 'cmHandleId with spaces' | new DataValidationException('Name Validation Error.', cmHandleId + 'contains an invalid character') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' + } + + def 'Multiple update operations in a single request'() { + given: 'cm handles request' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]), + new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])] + and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle' + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(*_) >> cmHandleDataNodeAsCollection >> { + throw new DataNodeNotFoundException(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) } >> cmHandleDataNodeAsCollection + when: 'update data node leaves is called using correct parameters' + def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'response has 3 values' + cmHandleResponseList.size() == 3 + and: 'the 1st and 3rd requests were processed successfully' + with(cmHandleResponseList.get(0)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == cmHandleId + } + with(cmHandleResponseList.get(2)) { + assert it.status == Status.SUCCESS + assert it.cmHandle == cmHandleId + } + and: 'the 2nd request failed with correct error code' + with(cmHandleResponseList.get(1)) { + assert it.status == Status.FAILURE + assert it.cmHandle == cmHandleId + assert it.ncmpResponseStatus == CM_HANDLES_NOT_FOUND + assert it.errorText == 'cm handle id(s) not found' + } + then: 'the replace list method is called twice' + 2 * mockInventoryPersistence.replaceListContent(cmHandleXpath, _) + } + + def 'Update alternate id of existing CM Handle.'() { + given: 'cm handles request' + def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] + and: 'a data node found' + def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] + when: 'cm handle properties is updated' + def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest) + then: 'the update is delegated to cps data service with correct parameters' + 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> + { args -> + assert args[3].contains('alt-1') + } + and: 'one successful registration response' + response.size() == 1 + and: 'the response shows success for the given cm handle id' + assert response[0].status == Status.SUCCESS + assert response[0].cmHandle == cmHandleId + } + + def 'Update with rejected alternate id.'() { + given: 'cm handles request' + def updatedNcmpServiceCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, alternateId: 'alt-1')] + and: 'a data node found' + def dataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': cmHandleId, 'alternate-id': 'alt-1']) + mockInventoryPersistence.getCmHandleDataNodeByCmHandleId(cmHandleId) >> [dataNode] + when: 'attempt to update the cm handle' + def response = objectUnderTest.updateCmHandleProperties(updatedNcmpServiceCmHandles) + then: 'the update is NOT delegated to cps data service' + 0 * mockCpsDataService.updateNodeLeaves(*_) + and: 'the alternate id checker rejects the given cm handle (override default setup behavior)' + mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [cmHandleId] + and: 'the response shows a failure for the given cm handle id' + assert response[0].status == Status.FAILURE + assert response[0].cmHandle == cmHandleId + } + + def 'Update CM Handle data producer identifier from #scenario'() { + given: 'an existing cm handle with no data producer identifier' + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': oldDataProducerIdentifier]) + and: 'an update request with a new data producer identifier' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'New Data Producer Identifier') + when: 'data producer identifier updated' + objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) + then: 'the update node leaves method is invoked once' + 1 * mockCpsDataService.updateNodeLeaves('NCMP-Admin', 'ncmp-dmi-registry', '/dmi-registry', _, _, ContentType.JSON) >> { args -> + assert args[3].contains('New Data Producer Identifier') + } + and: 'correct information is logged' + def lastLoggingEvent = logger.list[0] + assert lastLoggingEvent.level == Level.DEBUG + assert lastLoggingEvent.formattedMessage.contains('Updating data-producer-identifier') + where: 'the following scenarios are attempted' + scenario | oldDataProducerIdentifier + 'null to something' | null + 'blank to something' | '' + } + + def 'Update CM Handle data producer identifier with same value'() { + given: 'an existing cm handle with no data producer identifier' + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId','data-producer-identifier': 'same id']) + and: 'an update request with a new data producer identifier' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'same id') + when: 'data producer identifier updated' + objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) + then: 'the update node leaves method is not invoked' + 0 * mockCpsDataService.updateNodeLeaves(*_) + } + + def 'Update CM Handle data producer identifier from some data producer identifier to another data producer identifier'() { + given: 'an existing cm handle with a data producer identifier' + DataNode existingCmHandleDataNode = new DataNode(xpath: cmHandleXpath, leaves: ['id': 'cmHandleId', 'data-producer-identifier': 'someDataProducerIdentifier']) + and: 'an update request with a new data producer identifier' + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dataProducerIdentifier: 'someNewDataProducerIdentifier') + when: 'update data producer identifier is called with the update request' + objectUnderTest.updateDataProducerIdentifier(existingCmHandleDataNode, ncmpServiceCmHandle) + then: 'the update node leaves method is not invoked' + 0 * mockCpsDataService.updateNodeLeaves(*_) + and: 'correct information is logged' + def lastLoggingEvent = logger.list[0] + assert lastLoggingEvent.level == Level.WARN + assert lastLoggingEvent.formattedMessage.contains('Unable to update dataProducerIdentifier') + } + + def convertToProperties(expectedPropertiesAfterUpdateAsMap) { + def properties = [].withDefault { [:] } + expectedPropertiesAfterUpdateAsMap.forEach(property -> + property.forEach((key, val) -> { + properties.add(['name': key, 'value': val]) + })) + return properties + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy new file mode 100644 index 0000000000..da3116283a --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -0,0 +1,455 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory + +import com.hazelcast.map.IMap +import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsModuleService +import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.ncmp.api.impl.exception.DmiRequestException +import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel +import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager +import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.api.inventory.models.UpgradedCmHandles +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.CpsException +import org.onap.cps.spi.exceptions.DataNodeNotFoundException +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLES_NOT_FOUND +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST +import static org.onap.cps.ncmp.api.NcmpResponseStatus.CM_HANDLE_INVALID_ID +import static org.onap.cps.ncmp.api.NcmpResponseStatus.UNKNOWN_ERROR +import static org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse.Status +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + +class CmHandleRegistrationServiceSpec extends Specification { + + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'some-cm-handle-id') + def mockCpsModuleService = Mock(CpsModuleService) + def mockNetworkCmProxyDataServicePropertyHandler = Mock(CmHandleRegistrationServicePropertyHandler) + def mockInventoryPersistence = Mock(InventoryPersistence) + def mockCmHandleQueries = Mock(CmHandleQueryService) + def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) + def mockCpsDataService = Mock(CpsDataService) + def mockModuleSyncStartedOnCmHandles = Mock(IMap) + def trustLevelPerDmiPlugin = [:] + def mockTrustLevelManager = Mock(TrustLevelManager) + def mockAlternateIdChecker = Mock(AlternateIdChecker) + + def objectUnderTest = Spy(new CmHandleRegistrationService( + mockNetworkCmProxyDataServicePropertyHandler, mockInventoryPersistence, mockCpsDataService, mockLcmEventsCmHandleStateHandler, + mockModuleSyncStartedOnCmHandles, trustLevelPerDmiPlugin , mockTrustLevelManager, mockAlternateIdChecker)) + + def setup() { + // always accept all cm handles + mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] + + // always can find all cm handles in DB + mockInventoryPersistence.getYangModelCmHandles(_) >> { args -> args[0].collect { new YangModelCmHandle(id:it) } } + } + + def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'() { + given: 'a registration with operations of all types' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleId: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])]) + dmiRegistration.setRemovedCmHandles(['cmhandle-2']) + dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) + and: 'cm handles are persisted' + mockInventoryPersistence.getYangModelCmHandles(['cmhandle-2']) >> [new YangModelCmHandle()] + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: CmHandleState.READY)) + and: 'cm handle is in READY state' + mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true + when: 'registration is processed' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + then: 'cm-handles are removed first' + 1 * objectUnderTest.processRemovedCmHandles(*_) + and: 'de-registered cm handle entry is removed from in progress map' + 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle-2') + then: 'cm-handles are created' + 1 * objectUnderTest.processCreatedCmHandles(*_) + then: 'cm-handles are updated' + 1 * objectUnderTest.processUpdatedCmHandles(*_) + 1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> [] + then: 'cm-handles are upgraded' + 1 * objectUnderTest.processUpgradedCmHandles(*_) + } + + def 'DMI Registration upgrade operation with upgrade node state #scenario'() { + given: 'a registration with upgrade operation' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) + and: 'exception while checking cm handle state' + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) + when: 'registration is processed' + def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + then: 'upgrade operation contains expected error code' + assert result.upgradedCmHandles[0].status == expectedResponseStatus + where: 'the following parameters are used' + scenario | cmHandleState || expectedResponseStatus + 'READY' | CmHandleState.READY || Status.SUCCESS + 'Not READY' | CmHandleState.LOCKED || Status.FAILURE + } + + def 'DMI Registration upgrade with exception #scenario'() { + given: 'a registration with upgrade operation' + def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiRegistration.setUpgradedCmHandles(new UpgradedCmHandles(cmHandles: ['cmhandle-3'], moduleSetTag: 'some-module-set-tag')) + and: 'exception while checking cm handle state' + mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } + when: 'registration is processed' + def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + then: 'upgrade operation contains expected error code' + assert result.upgradedCmHandles.ncmpResponseStatus.code[0] == expectedErrorCode + where: 'the following parameters are used' + scenario | exception || expectedErrorCode + 'data node not found' | new DataNodeNotFoundException('some-dataspace-name', 'some-anchor-name') || '100' + 'cm handle is invalid' | new DataValidationException('some error message', 'some error details') || '110' + } + + def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() { + given: 'a registration ' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, + dmiDataPlugin: dmiDataPlugin) + dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] + when: 'update registration and sync module is called with correct DMI plugin information' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'create cm handles registration and sync modules is called with the correct plugin information' + 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) + and: 'dmi is added to the dmi trustLevel map' + assert trustLevelPerDmiPlugin.size() == 1 + assert trustLevelPerDmiPlugin.containsKey(expectedDmiPluginRegisteredName) + where: + scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedDmiPluginRegisteredName + 'combined DMI plugin' | 'service1' | '' | '' || 'service1' + 'data & model DMI plugins' | '' | 'service1' | 'service2' || 'service2' + 'data & model using same service' | '' | 'service1' | 'service1' || 'service1' + } + + def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() { + given: 'a registration ' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin, + dmiDataPlugin: dmiDataPlugin) + dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] + when: 'registration is called with incorrect DMI plugin information' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a DMI Request Exception is thrown with correct message details' + def exceptionThrown = thrown(DmiRequestException.class) + assert exceptionThrown.getMessage().contains(expectedMessageDetails) + and: 'registration is not called' + 0 * objectUnderTest.processCreatedCmHandles(*_) + where: + scenario | dmiPlugin | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails + 'empty DMI plugins' | '' | '' | '' || 'No DMI plugin service names' + 'blank DMI plugins' | ' ' | ' ' | ' ' || 'No DMI plugin service names' + 'null DMI plugins' | null | null | null || 'No DMI plugin service names' + 'all DMI plugins' | 'service1' | 'service2' | 'service3' || 'Cannot register combined plugin service name and other service names' + '(combined)DMI and Data Plugin' | 'service1' | '' | 'service2' || 'Cannot register combined plugin service name and other service names' + '(combined)DMI and model Plugin' | 'service1' | 'service2' | '' || 'Cannot register combined plugin service name and other service names' + 'only model DMI plugin' | '' | 'service1' | '' || 'Cannot register just a Data or Model plugin service name' + 'only data DMI plugin' | '' | '' | 'service1' || 'Cannot register just a Data or Model plugin service name' + } + + def 'Create CM-Handle Successfully: #scenario.'() { + given: 'a registration without cm-handle properties' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a successful response is received' + response.createdCmHandles.size() == 1 + with(response.createdCmHandles[0]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle' + } + and: 'state handler is invoked with the expected parameters' + 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> { + args -> { + def yangModelCmHandles = args[0] + assert yangModelCmHandles.id == ['cmhandle'] + assert yangModelCmHandles.dmiServiceName == ['my-server'] + } + } + where: + scenario | dmiProperties | publicProperties || expectedDmiProperties | expectedPublicProperties + 'with dmi & public properties' | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]' + 'with only public properties' | [:] | ['public-key': 'public-value'] || [:] | '[{"name":"public-key","value":"public-value"}]' + 'with only dmi properties' | ['dmi-key': 'dmi-value'] | [:] || '[{"name":"dmi-key","value":"dmi-value"}]' | [:] + 'without dmi & public properties' | [:] | [:] || [:] | [:] + } + + def 'Add CM-Handle #scenario.'() { + given: ' registration details for one cm handles' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + createdCmHandles:[new NcmpServiceCmHandle(cmHandleId: 'ch-1', registrationTrustLevel: registrationTrustLevel)]) + when: 'registration is updated' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'trustLevel is set for the created cm-handle' + 1 * mockTrustLevelManager.handleInitialRegistrationOfTrustLevels(expectedMapping) + where: + scenario | registrationTrustLevel || expectedMapping + 'with trusted cm handle' | TrustLevel.COMPLETE || [ 'ch-1' : TrustLevel.COMPLETE ] + 'without trust level' | null || [ 'ch-1' : null ] + } + + def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed with some failures'() { + given: 'a registration with three cm-handles to be created' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + createdCmHandles: [new NcmpServiceCmHandle(cmHandleId: 'cmhandle1'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'), + new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')]) + and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd' + def xpath = "somePathWithId[@id='cmhandle2']" + mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw AlreadyDefinedException.forDataNodes([xpath], 'some-context') } + when: 'registration is updated to create cm-handles' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a response is received for all cm-handles' + response.createdCmHandles.size() == 1 + and: 'all cm-handles creation fails' + response.createdCmHandles.each { + assert it.cmHandle == 'cmhandle2' + assert it.status == Status.FAILURE + assert it.ncmpResponseStatus == CM_HANDLE_ALREADY_EXIST + assert it.errorText == 'cm-handle already exists' + } + } + + def 'Create CM-Handle Error Handling: Registration fails: #scenario'() { + given: 'a registration without cm-handle properties' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') + dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle')] + and: 'cm-handler registration fails: #scenario' + mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw exception } + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a failure response is received' + response.createdCmHandles.size() == 1 + with(response.createdCmHandles[0]) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.ncmpResponseStatus == expectedError + assert it.errorText == expectedErrorText + } + where: + scenario | exception || expectedError | expectedErrorText + 'cm-handle already exist' | AlreadyDefinedException.forDataNodes(["path[@id='cmhandle']"], 'some-context') || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists' + 'unknown exception while registering cm-handle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + } + + def 'Update CM-Handle: Update Operation Response is added to the response'() { + given: 'a registration to update CmHandles' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', updatedCmHandles: [{}]) + and: 'cm-handle updates can be processed successfully' + def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'), + CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")), + CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLES_NOT_FOUND), + CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)] + mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse + when: 'registration is updated' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the response contains updateOperationResponse' + assert response.updatedCmHandles.size() == 4 + assert response.updatedCmHandles.containsAll(updateOperationResponse) + } + + def 'Remove CmHandle Successfully: #scenario'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', removedCmHandles: ['cmhandle']) + and: '#scenario' + mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >> { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the cmHandle state is updated to "DELETING"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> + { args -> args[0].values()[0] == CmHandleState.DELETING } + then: 'method to delete relevant schema set is called once' + 1 * mockInventoryPersistence.deleteSchemaSetsWithCascade(_) + and: 'method to delete relevant list/list element is called once' + 1 * mockInventoryPersistence.deleteDataNodes(_) + and: 'successful response is received' + assert response.removedCmHandles.size() == 1 + with(response.removedCmHandles[0]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle' + } + and: 'the cmHandle state is updated to "DELETED"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> + { args -> args[0].values()[0] == CmHandleState.DELETED } + and: 'No cm handles state updates for "upgraded cm handles"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) + where: + scenario | schemaSetExist + 'schema-set exists and can be deleted successfully' | true + 'schema-set does not exist' | false + } + + def 'Remove CmHandle: Partial Success'() { + given: 'a registration with three cm-handles to be deleted' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3']) + and: 'cm-handle deletion fails on batch' + mockInventoryPersistence.deleteDataNodes(_) >> { throw new RuntimeException("Failed") } + and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' + mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") } + when: 'registration is updated to delete cmhandles' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the cmHandle states are all updated to "DELETING"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ assert it.every { entry -> entry.value == CmHandleState.DELETING } }) + and: 'a response is received for all cm-handles' + response.removedCmHandles.size() == 3 + and: 'successfully de-registered cm handle 1 is removed from in progress map' + 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle1') + and: 'successfully de-registered cm handle 3 is removed from in progress map even though it was already being removed' + 1 * mockModuleSyncStartedOnCmHandles.remove('cmhandle3') >> 'already in progress' + and: 'failed de-registered cm handle entries should NOT be removed from in progress map' + 0 * mockModuleSyncStartedOnCmHandles.remove('cmhandle2') + and: '1st and 3rd cm-handle deletes successfully' + with(response.removedCmHandles[0]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle1' + } + with(response.removedCmHandles[2]) { + assert it.status == Status.SUCCESS + assert it.cmHandle == 'cmhandle3' + } + and: '2nd cm-handle deletion fails' + with(response.removedCmHandles[1]) { + assert it.status == Status.FAILURE + assert it.ncmpResponseStatus == UNKNOWN_ERROR + assert it.errorText == 'Failed' + assert it.cmHandle == 'cmhandle2' + } + and: 'the cmHandle state is updated to DELETED for 1st and 3rd' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ + assert it.size() == 2 + assert it.every { entry -> entry.value == CmHandleState.DELETED } + }) + and: 'No cm handles state updates for "upgraded cm handles"' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([:]) + + } + + def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle']) + and: 'schema set batch deletion failed with unknown error' + mockInventoryPersistence.deleteSchemaSetsWithCascade(_) >> { throw new RuntimeException('Failed') } + and: 'schema set single deletion failed with unknown error' + mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'no exception is thrown' + noExceptionThrown() + and: 'cm-handle is not deleted' + 0 * mockInventoryPersistence.deleteDataNodes(_) + and: 'the cmHandle state is not updated to "DELETED"' + 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch([yangModelCmHandle: CmHandleState.DELETED]) + and: 'a failure response is received' + assert response.removedCmHandles.size() == 1 + with(response.removedCmHandles[0]) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.errorText == 'Failed' + assert it.ncmpResponseStatus == UNKNOWN_ERROR + } + } + + def 'Remove CmHandle Error Handling: #scenario'() { + given: 'a registration' + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', + removedCmHandles: ['cmhandle']) + and: 'cm-handle deletion fails on batch' + mockInventoryPersistence.deleteDataNodes(_) >> { throw deleteListElementException } + and: 'cm-handle deletion fails on individual delete' + mockInventoryPersistence.deleteDataNode(_) >> { throw deleteListElementException } + when: 'registration is updated to delete cmhandle' + def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'a failure response is received' + assert response.removedCmHandles.size() == 1 + with(response.removedCmHandles[0]) { + assert it.status == Status.FAILURE + assert it.cmHandle == 'cmhandle' + assert it.ncmpResponseStatus == expectedError + assert it.errorText == expectedErrorText + } + and: 'the cm handle state is not updated to "DELETED"' + 0 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_, CmHandleState.DELETED) + where: + scenario | cmHandleId | deleteListElementException || expectedError | expectedErrorText + 'cm-handle does not exist' | 'cmhandle' | new DataNodeNotFoundException('', '', '') || CM_HANDLES_NOT_FOUND | 'cm handle id(s) not found' + 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException('', '') || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id' + 'an unexpected exception' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed' + } + + def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() { + given: 'an existing cm handle composite state' + def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag, + dataStores: CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder() + .dataStoreSyncState(initialDataSyncState) + .build()).build()) + and: 'get cm handle state returns the composite state for the given cm handle id' + mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState + when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag' + objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag) + then: 'the data sync enabled flag is set to #dataSyncEnabled' + compositeState.dataSyncEnabled == dataSyncEnabledFlag + and: 'the data store sync state is set to #expectedDataStoreSyncState' + compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState + and: 'the cps data service to delete data nodes is invoked the expected number of times' + deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _) + and: 'the inventory persistence service to update node leaves is called with the correct values' + saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState) + where: 'the following data sync enabled flag is used' + scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations + 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1 + 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1 + 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1 + 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0 + } + + def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () { + given: 'a cm handle composite state' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false) + and: 'get cm handle state returns the composite state for the given cm handle id' + mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState + when: 'set data sync enabled is called with the data sync enabled flag set to true' + objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true) + then: 'the expected exception is thrown' + thrown(CpsException) + and: 'the inventory persistence service to update node leaves is not invoked' + 0 * mockInventoryPersistence.saveCmHandleState(_, _) + } + + + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy new file mode 100644 index 0000000000..d58a1dfa06 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -0,0 +1,345 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * 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.impl.inventory + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.api.CpsAnchorService +import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsModuleService +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.CascadeDeleteAllowed +import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.exceptions.DataNodeNotFoundException +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.ModuleDefinition +import org.onap.cps.spi.model.ModuleReference +import org.onap.cps.spi.utils.CpsValidator +import org.onap.cps.utils.JsonObjectMapper +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.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NO_TIMESTAMP +import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS +import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS + +class InventoryPersistenceImplSpec extends Specification { + + def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + + def mockCpsDataService = Mock(CpsDataService) + + def mockCpsModuleService = Mock(CpsModuleService) + + def mockCpsAnchorService = Mock(CpsAnchorService) + + def mockCpsValidator = Mock(CpsValidator) + + def mockCmHandleQueries = Mock(CmHandleQueryService) + + def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, + mockCpsValidator, mockCpsAnchorService, mockCmHandleQueries) + + def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) + + def cmHandleId = 'some-cm-handle' + def leaves = ["id":cmHandleId,"dmi-service-name":"common service name","dmi-data-service-name":"data service name","dmi-model-service-name":"model service name"] + def xpath = "/dmi-registry/cm-handles[@id='some-cm-handle']" + + def cmHandleId2 = 'another-cm-handle' + def xpath2 = "/dmi-registry/cm-handles[@id='another-cm-handle']" + + @Shared + def childDataNodesForCmHandleWithAllProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"]), + new DataNode(xpath: "/dmi-registry/cm-handles[@id='some cm handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] + + @Shared + def childDataNodesForCmHandleWithDMIProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/additional-properties[@name='name1']", leaves: ["name":"name1", "value":"value1"])] + + @Shared + def childDataNodesForCmHandleWithPublicProperties = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/public-properties[@name='name2']", leaves: ["name":"name2","value":"value2"])] + + @Shared + def childDataNodesForCmHandleWithState = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='some-cm-handle']/state", leaves: ['cm-handle-state': 'ADVISED'])] + + def "Retrieve CmHandle using datanode with #scenario."() { + given: 'the cps data service returns a data node from the DMI registry' + def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves) + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] + when: 'retrieving the yang modelled cm handle' + def result = objectUnderTest.getYangModelCmHandle(cmHandleId) + then: 'the result has the correct id and service names' + result.id == cmHandleId + result.dmiServiceName == 'common service name' + result.dmiDataServiceName == 'data service name' + result.dmiModelServiceName == 'model service name' + and: 'the expected DMI properties' + result.dmiProperties == expectedDmiProperties + result.publicProperties == expectedPublicProperties + and: 'the state details are returned' + result.compositeState.cmHandleState == expectedCompositeState + and: 'the CM Handle ID is validated' + 1 * mockCpsValidator.validateNameCharacters(cmHandleId) + where: 'the following parameters are used' + scenario | childDataNodes || expectedDmiProperties || expectedPublicProperties || expectedCompositeState + 'no properties' | [] || [] || [] || null + 'DMI and public properties' | childDataNodesForCmHandleWithAllProperties || [new YangModelCmHandle.Property("name1", "value1")] || [new YangModelCmHandle.Property("name2", "value2")] || null + 'just DMI properties' | childDataNodesForCmHandleWithDMIProperties || [new YangModelCmHandle.Property("name1", "value1")] || [] || null + 'just public properties' | childDataNodesForCmHandleWithPublicProperties || [] || [new YangModelCmHandle.Property("name2", "value2")] || null + 'with state details' | childDataNodesForCmHandleWithState || [] || [] || CmHandleState.ADVISED + } + + def "Handling missing service names as null."() { + given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes' + def dataNode = new DataNode(childDataNodes:[], leaves: ['id':cmHandleId]) + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode] + when: 'retrieving the yang modelled cm handle' + def result = objectUnderTest.getYangModelCmHandle(cmHandleId) + then: 'the service names are returned as null' + result.dmiServiceName == null + result.dmiDataServiceName == null + result.dmiModelServiceName == null + and: 'the CM Handle ID is validated' + 1 * mockCpsValidator.validateNameCharacters(cmHandleId) + } + + def "Retrieve multiple YangModelCmHandles"() { + given: 'the cps data service returns 2 data nodes from the DMI registry' + def dataNodes = [new DataNode(xpath: xpath, leaves: ['id': cmHandleId]), new DataNode(xpath: xpath2, leaves: ['id': cmHandleId2])] + mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes + when: 'retrieving the yang modelled cm handle' + def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2]) + then: 'verify both have returned and cmhandleIds are correct' + assert results.size() == 2 + assert results.id.containsAll([cmHandleId, cmHandleId2]) + } + + def 'Get a Cm Handle Composite State'() { + given: 'a valid cm handle id' + def cmHandleId = 'Some-Cm-Handle' + def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED']) + and: 'cps data service returns a valid data node' + mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode] + when: 'get cm handle state is invoked' + def result = objectUnderTest.getCmHandleState(cmHandleId) + then: 'result has returned the correct cm handle state' + result.cmHandleState == CmHandleState.ADVISED + and: 'the CM Handle ID is validated' + 1 * mockCpsValidator.validateNameCharacters(cmHandleId) + } + + def 'Update Cm Handle with #scenario State'() { + given: 'a cm handle and a composite state' + def cmHandleId = 'Some-Cm-Handle' + def compositeState = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime) + when: 'update cm handle state is invoked with the #scenario state' + objectUnderTest.saveCmHandleState(cmHandleId, compositeState) + then: 'update node leaves is invoked with the correct params' + 1 * mockCpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']', expectedJsonData, _ as OffsetDateTime) + where: 'the following states are used' + scenario | cmHandleState || expectedJsonData + 'READY' | CmHandleState.READY || '{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}' + 'LOCKED' | CmHandleState.LOCKED || '{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}' + 'DELETING' | CmHandleState.DELETING || '{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}' + } + + def 'Update Cm Handles with #scenario States'() { + given: 'a map of cm handles composite states' + def compositeState1 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime) + def compositeState2 = new CompositeState(cmHandleState: cmHandleState, lastUpdateTime: formattedDateAndTime) + when: 'update cm handle state is invoked with the #scenario state' + def cmHandleStateMap = ['Some-Cm-Handle1' : compositeState1, 'Some-Cm-Handle2' : compositeState2] + objectUnderTest.saveCmHandleStateBatch(cmHandleStateMap) + then: 'update node leaves is invoked with the correct params' + 1 * mockCpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandlesJsonDataMap, _ as OffsetDateTime) + where: 'the following states are used' + scenario | cmHandleState || cmHandlesJsonDataMap + 'READY' | CmHandleState.READY || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"READY","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] + 'LOCKED' | CmHandleState.LOCKED || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"LOCKED","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] + 'DELETING' | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}'] + } + + def 'Getting module definitions by module'() { + given: 'cps module service returns module definition for module name' + def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')] + mockCpsModuleService.getModuleDefinitionsByAnchorAndModule(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id', 'some-module', '2024-01-25') >> moduleDefinitions + when: 'get module definitions is invoked with module name' + def result = objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cmHandle-Id', 'some-module', '2024-01-25') + then: 'returned result are the same module definitions as returned from module service' + assert result == moduleDefinitions + and: 'cm handle id and module name validated' + 1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id', 'some-module') + } + + def 'Getting module definitions with cm handle id'() { + given: 'cps module service returns module definitions for cm handle id' + def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')] + mockCpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleDefinitions + when: 'get module definitions is invoked with cm handle id' + def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id') + then: 'the returned result are the same module definitions as returned from the module service' + assert result == moduleDefinitions + } + + def 'Get module references'() { + given: 'cps module service returns a collection of module references' + def moduleReferences = [new ModuleReference('moduleName','revision','namespace')] + mockCpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleReferences + when: 'get yang resources module references by cmHandle is invoked' + def result = objectUnderTest.getYangResourcesModuleReferences('some-cmHandle-Id') + then: 'the returned result is a collection of module definitions' + assert result == moduleReferences + and: 'the CM Handle ID is validated' + 1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id') + } + + def 'Save Cmhandle'() { + given: 'cmHandle represented as Yang Model' + def yangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', dmiProperties: [], publicProperties: []) + when: 'the method to save cmhandle is called' + objectUnderTest.saveCmHandle(yangModelCmHandle) + then: 'the data service method to save list elements is called once' + 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT, + _,null) >> { + args -> { + assert args[3].startsWith('{"cm-handles":[{"id":"cmhandle","additional-properties":[],"public-properties":[]}]}') + } + } + } + + def 'Save Multiple Cmhandles'() { + given: 'cm handles represented as Yang Model' + def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1') + def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2') + when: 'the cm handles are saved' + objectUnderTest.saveCmHandleBatch([yangModelCmHandle1, yangModelCmHandle2]) + then: 'CPS Data Service persists both cm handles as a batch' + 1 * mockCpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + NCMP_DMI_REGISTRY_PARENT, _,null) >> { + args -> { + def jsonData = (args[3] as String) + jsonData.contains('cmhandle1') + jsonData.contains('cmhandle2') + } + } + } + + def 'Delete list or list elements'() { + when: 'the method to delete list or list elements is called' + objectUnderTest.deleteListOrListElement('sample xPath') + then: 'the data service method to save list elements is called once' + 1 * mockCpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath',null) + } + + def 'Delete schema set with a valid schema set name'() { + when: 'the method to delete schema set is called with valid schema set name' + objectUnderTest.deleteSchemaSetWithCascade('validSchemaSetName') + then: 'the module service to delete schemaSet is invoked once' + 1 * mockCpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'validSchemaSetName', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) + and: 'the schema set name is validated' + 1 * mockCpsValidator.validateNameCharacters('validSchemaSetName') + } + + def 'Delete multiple schema sets with valid schema set names'() { + when: 'the method to delete schema sets is called with valid schema set names' + objectUnderTest.deleteSchemaSetsWithCascade(['validSchemaSetName1', 'validSchemaSetName2']) + then: 'the module service to delete schema sets is invoked once' + 1 * mockCpsModuleService.deleteSchemaSetsWithCascade(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['validSchemaSetName1', 'validSchemaSetName2']) + and: 'the schema set names are validated' + 1 * mockCpsValidator.validateNameCharacters(['validSchemaSetName1', 'validSchemaSetName2']) + } + + def 'Get data node via xPath'() { + when: 'the method to get data nodes is called' + objectUnderTest.getDataNode('sample xPath') + then: 'the data persistence service method to get data node is invoked once' + 1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xPath', INCLUDE_ALL_DESCENDANTS) + } + + def 'Get cmHandle data node'() { + given: 'expected xPath to get cmHandle data node' + def expectedXPath = '/dmi-registry/cm-handles[@id=\'sample cmHandleId\']' + when: 'the method to get data nodes is called' + objectUnderTest.getCmHandleDataNodeByCmHandleId('sample cmHandleId') + then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath' + 1 * mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, expectedXPath, INCLUDE_ALL_DESCENDANTS) + } + + def 'Get cm handle data node'() { + given: 'expected xPath to get cmHandle data node' + def expectedXPath = '/dmi-registry/cm-handles[@alternate-id=\'alternate id\']' + and: 'query service is invoked with expected xpath' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(expectedXPath, OMIT_DESCENDANTS) >> [new DataNode()] + expect: 'getting the cm handle data node' + assert objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') == new DataNode() + } + + def 'Attempt to get non existing cm handle data node by alternate id'() { + given: 'query service is invoked and returns empty collection of data nodes' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [] + when: 'getting the cm handle data node' + objectUnderTest.getCmHandleDataNodeByAlternateId('alternate id') + then: 'no data found exception thrown' + def thrownException = thrown(DataNodeNotFoundException) + assert thrownException.getMessage().contains('DataNode not found') + } + + def 'Get CM handles that has given module names'() { + when: 'the method to get cm handles is called' + objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name']) + then: 'the admin persistence service method to query anchors is invoked once with the same parameter' + 1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) + } + + def 'Replace list content'() { + when: 'replace list content method is called with xpath and data nodes collection' + objectUnderTest.replaceListContent('sample xpath', [new DataNode()]) + then: 'the cps data service method to replace list content is invoked once with same parameters' + 1 * mockCpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,'sample xpath', [new DataNode()], NO_TIMESTAMP); + } + + def 'Delete data node via xPath'() { + when: 'Delete data node method is called with xpath as parameter' + objectUnderTest.deleteDataNode('sample dataNode xpath') + then: 'the cps data service method to delete data node is invoked once with the same xPath' + 1 * mockCpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'sample dataNode xpath', NO_TIMESTAMP); + } + + def 'Delete multiple data nodes via xPath'() { + when: 'Delete data nodes method is called with multiple xpaths as parameters' + objectUnderTest.deleteDataNodes(['xpath1', 'xpath2']) + then: 'the cps data service method to delete data nodes is invoked once with the same xPaths' + 1 * mockCpsDataService.deleteDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ['xpath1', 'xpath2'], NO_TIMESTAMP); + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy new file mode 100644 index 0000000000..49d4c39a11 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -0,0 +1,223 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2022 Bell Canada + * Modifications Copyright (C) 2023 TechMahindra Ltd. + * ================================================================================ + * 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.impl.inventory + +import com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.ConditionApiProperties +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.model.ConditionProperties +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +import java.util.stream.Collectors + +class NetworkCmProxyInventoryFacadeSpec extends Specification { + + def mockCmHandleRegistrationService = Mock(CmHandleRegistrationService) + def mockCmHandleQueryService = Mock(CmHandleQueryService) + def mockParameterizedCmHandleQueryService = Mock(ParameterizedCmHandleQueryService) + def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper())) + def mockInventoryPersistence = Mock(InventoryPersistence) + + def objectUnderTest = new NetworkCmProxyInventoryFacade(mockCmHandleRegistrationService, mockCmHandleQueryService, mockParameterizedCmHandleQueryService, mockInventoryPersistence, spiedJsonObjectMapper) + + def 'Update DMI Registration'() { + given: 'an (updated) dmi plugin registration' + def dmiPluginRegistration = Mock(DmiPluginRegistration) + when: 'the registration is submitted ' + objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + then: 'the call is delegated to the cm handle registration service' + 1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + } + + def 'Execute cm handle id search for inventory'() { + given: 'a ConditionApiProperties object' + def conditionProperties = new ConditionProperties() + conditionProperties.conditionName = 'hasAllProperties' + conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ] + def cmHandleQueryServiceParameters = new CmHandleQueryServiceParameters() + cmHandleQueryServiceParameters.cmHandleQueryParameters = [conditionProperties] as List + and: 'the system returns an set of cmHandle ids' + mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] + when: 'executing the search' + def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) + then: 'the result returns the correct 2 elements' + assert result.size() == 2 + assert result.contains('cmHandle1') + assert result.contains('cmHandle2') + } + + def 'Get all cm handle IDs by DMI plugin identifier.' () { + given: 'cm handle queries service returns cm handles' + 1 * mockCmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2'] + when: 'cm handle Ids are requested with dmi plugin identifier' + def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') + then: 'the result size is correct' + assert result.size() == 2 + and: 'the result returns the correct details' + assert result.containsAll('cm-handle-1','cm-handle-2') + } + + def 'Getting Yang Resources.'() { + when: 'yang resources is called' + objectUnderTest.getYangResourcesModuleReferences('some-cm-handle') + then: 'CPS module services is invoked for the correct dataspace and cm handle' + 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle') + } + + def 'Get a cm handle.'() { + given: 'the system returns a yang modelled cm handle' + def dmiServiceName = 'some service name' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) + def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')] + def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')] + def moduleSetTag = 'some-module-set-tag' + def alternateId = 'some-alternate-id' + def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName, + dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState, + moduleSetTag: moduleSetTag, alternateId: alternateId) + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle details for a given cm handle id from ncmp service' + def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle') + then: 'the result is a ncmpServiceCmHandle' + result.class == NcmpServiceCmHandle.class + and: 'the cm handle contains the cm handle id' + result.cmHandleId == 'some-cm-handle' + and: 'the cm handle contains the alternate id' + result.alternateId == 'some-alternate-id' + and: 'the cm handle contains the module-set-tag' + result.moduleSetTag == 'some-module-set-tag' + and: 'the cm handle contains the DMI Properties' + result.dmiProperties ==[ Book:'Romance Novel' ] + and: 'the cm handle contains the public Properties' + result.publicProperties == [ "Public Book":'Public Romance Novel' ] + and: 'the cm handle contains the cm handle composite state' + result.compositeState == compositeState + } + + def 'Get cm handle public properties'() { + given: 'a yang modelled cm handle' + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] + def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties) + and: 'the system returns this yang modelled cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle public properties for a given cm handle id from ncmp service' + def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle') + then: 'the result returns the correct data' + result == [ 'public prop' : 'some public prop' ] + } + + def 'Get cm handle composite state'() { + given: 'a yang modelled cm handle' + def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, + lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(), + lastUpdateTime: 'some-timestamp', + dataSyncEnabled: false, + dataStores: dataStores()) + def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')] + def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')] + def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState) + and: 'the system returns this yang modelled cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle + when: 'getting cm handle composite state for a given cm handle id from ncmp service' + def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle') + then: 'the result returns the correct data' + result == compositeState + } + + def 'Execute cm handle id search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'query cm handle method return with a data node list' + mockParameterizedCmHandleQueryService.queryCmHandleIds( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> ['cm-handle-id-1'] + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) + then: 'result is the same collection as returned by the CPS Data Service' + assert result == ['cm-handle-id-1'] + } + + def 'Getting module definitions by module'() { + when: 'get module definitions is performed with module name' + objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') + then: 'ncmp inventory persistence service is invoked once with correct parameters' + 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04') + } + + def 'Getting module definitions by cm handle id'() { + when: 'get module definitions is performed with cm handle id' + objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle') + then: 'ncmp inventory persistence service is invoked once with correct parameter' + 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle') + } + + def 'Execute cm handle search'() { + given: 'valid CmHandleQueryApiParameters input' + def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() + def conditionApiProperties = new ConditionApiProperties() + conditionApiProperties.conditionName = 'hasAllModules' + conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] + cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] + and: 'query cm handle method return with a data node list' + mockParameterizedCmHandleQueryService.queryCmHandles( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')] + when: 'execute cm handle search is called' + def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters) + then: 'result is the same collection as returned by the CPS Data Service' + assert result.stream().map(cmHandle -> cmHandle.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set + } + + def 'Set Cm Handle Data Sync flag.'() { + when: 'setting data sync enabled flag' + objectUnderTest.setDataSyncEnabled('ch-1',true) + then: 'call is delegated to the cm handle registration service' + mockCmHandleRegistrationService.setDataSyncEnabled('ch-1', true) + } + + def dataStores() { + CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder() + .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED) + .lastSyncTime('some-timestamp').build()).build() + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy new file mode 100644 index 0000000000..d8bb559bf9 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyQueryServiceImplSpec.groovy @@ -0,0 +1,50 @@ +/* + * ============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.impl.inventory + +import org.onap.cps.api.CpsQueryService +import org.onap.cps.ncmp.api.impl.NetworkCmProxyQueryServiceImpl +import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + +class NetworkCmProxyQueryServiceImplSpec extends Specification { + + def mockCpsQueryService = Mock(CpsQueryService) + + def objectUnderTest = new NetworkCmProxyQueryServiceImpl(mockCpsQueryService) + + def 'Query resource data for operational from DMI.'() { + given: 'a list of datanodes' + def dataNodes = [new DataNode(xpath: '/cps/path'), new DataNode(xpath: '/cps/path/child')] + and: 'the list of datanodes is returned for query data node' + 1 * mockCpsQueryService.queryDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, + '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNodes + when: 'query resource data operational for cm-handle is called' + def response = objectUnderTest.queryResourceDataOperational(NCMP_DMI_REGISTRY_ANCHOR, + '//cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) + then: 'the expected datanodes are returned from the DMI' + response == dataNodes + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy new file mode 100644 index 0000000000..dfd549e63e --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -0,0 +1,220 @@ +/* + * ============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.impl.inventory + +import org.onap.cps.cpspath.parser.PathParsingException +import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.exceptions.DataInUseException +import org.onap.cps.spi.exceptions.DataValidationException +import org.onap.cps.spi.model.ConditionProperties +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT + +class ParameterizedCmHandleQueryServiceSpec extends Specification { + + def cmHandleQueries = Mock(CmHandleQueryService) + def partiallyMockedCmHandleQueries = Spy(CmHandleQueryService) + def mockInventoryPersistence = Mock(InventoryPersistence) + + def dmiRegistry = new DataNode(xpath: NCMP_DMI_REGISTRY_PARENT, childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'])) + + def objectUnderTest = new ParameterizedCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence) + def objectUnderTestWithPartiallyMockedQueries = new ParameterizedCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence) + + def 'Query cm handle ids with cpsPath.'() { + given: 'a cmHandleWithCpsPath condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'the query get the cm handle datanodes excluding all descendants returns a datanode' + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'the correct expected cm handles ids are returned' + assert result == ['some-cmhandle-id'] as Set + } + + def 'Cm handle ids query with error: #scenario.'() { + given: 'a cmHandleWithCpsPath condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'cmHandleQueries throws a path parsing exception' + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException } + when: 'the query is executed for cm handle ids' + objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'a data validation exception is thrown' + thrown(expectedException) + where: 'the following data is used' + scenario | thrownException || expectedException + 'PathParsingException' | new PathParsingException('some message', 'some details') || DataValidationException + 'any other Exception' | new DataInUseException('some message', 'some details') || DataInUseException + } + + def 'Cm handle ids cpsPath query for private properties (not allowed).'() { + given: 'a CpsPath condition property for private properties' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'empty result is returned' + assert result.isEmpty() + } + + def 'Query cm handle ids with module names when #scenario from query.'() { + given: 'a modules condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'the inventory service is called with the correct module names' + 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService + and: 'the correct expected cm handles ids are returned' + assert result.size() == cmHandleIdsFromService.size() + assert result.containsAll(cmHandleIdsFromService) + where: 'the following data is used' + scenario | cmHandleIdsFromService + 'One anchor returned' | ['some-cmhandle-id'] + 'No anchors are returned' | [] + } + + def 'Query cm handles with some trust level query parameters'() { + given: 'a trust level condition property' + def trustLevelQueryParameters = new CmHandleQueryServiceParameters() + def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map]) + trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties]) + when: 'the query is being executed' + objectUnderTest.queryCmHandleIds(trustLevelQueryParameters) + then: 'the query is being delegated to the cm handle query service with correct parameter' + 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map) + } + + def 'Query cm handle details with module names when #scenario from query.'() { + given: 'a modules condition property' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + when: 'the query is executed for cm handle ids' + def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the inventory service is called with the correct module names' + 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1'] + and: 'the inventory service is called with teh correct if and returns a yang model cm handle' + 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> + [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] + and: 'the expected cm handle(s) are returned as NCMP Service cm handles' + assert result[0] instanceof NcmpServiceCmHandle + assert result.size() == 1 + assert result[0].dmiProperties == [name:'value'] + } + + def 'Query cm handle ids when the query is empty.'() { + given: 'We use an empty query' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + and: 'the inventory persistence returns the dmi registry datanode with just ids' + mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry] + when: 'the query is executed for both cm handle ids' + def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + then: 'the correct expected cm handles are returned' + assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + } + + def 'Query cm handle details when the query is empty.'() { + given: 'We use an empty query' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + and: 'the inventory persistence returns the dmi registry datanode with just ids' + mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT) >> [dmiRegistry] + when: 'the query is executed for both cm handle details' + def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) + then: 'the correct cm handles are returned' + assert result.size() == 4 + assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + } + + def 'Query CMHandleId with #scenario.' () { + given: 'a query object created with #condition' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties(conditionName, [['some-key': 'some-value']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'the inventoryPersistence returns different CmHandleIds' + partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties + partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties + when: 'the query executed' + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + then: 'the expected number of results are returned.' + assert result.size() == expectedCmHandleIdsSize + where: 'the following data is used' + scenario | conditionName | cmHandlesWithMatchingPublicProperties | cmHandlesWithMatchingPrivateProperties || expectedCmHandleIdsSize + 'all properties, only public matching' | 'hasAllProperties' | ['h1', 'h2'] | null || 2 + 'all properties, no matching cm handles' | 'hasAllProperties' | [] | [] || 0 + 'additional properties, some matching cm handles' | 'hasAllAdditionalProperties' | [] | ['h1', 'h2'] || 2 + 'additional properties, no matching cm handles' | 'hasAllAdditionalProperties' | null | [] || 0 + } + + def 'Retrieve CMHandleIds by different DMI properties with #scenario.' () { + given: 'a query object created with dmi plugin as condition' + def cmHandleQueryParameters = new CmHandleQueryServiceParameters() + def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']]) + cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) + and: 'the inventoryPersistence returns different CmHandleIds' + partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult + when: 'the query executed' + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + then: 'the expected number of results are returned.' + assert result.size() == expectedCmHandleIdsSize + where: 'the following data is used' + scenario | cmHandleQueryResult || expectedCmHandleIdsSize + 'some matches' | ['h1','h2'] || 2 + 'no matches' | [] || 0 + } + + def 'Combine two query results where #scenario.'() { + when: 'two query results in the form of a map of NcmpServiceCmHandles are combined into a single query result' + def result = objectUnderTest.combineCmHandleQueryResults(firstQuery, secondQuery) + then: 'the returned result is the same as the expected result' + result == expectedResult + where: + scenario | firstQuery | secondQuery || expectedResult + 'two queries with unique and non unique entries exist' | ['PNFDemo', 'PNFDemo2'] | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo'] + 'the first query contains entries and second query is empty' | ['PNFDemo', 'PNFDemo2'] | [] || [] + 'the second query contains entries and first query is empty' | [] | ['PNFDemo', 'PNFDemo3'] || [] + 'the first query contains entries and second query is null' | ['PNFDemo', 'PNFDemo2'] | null || ['PNFDemo', 'PNFDemo2'] + 'the second query contains entries and first query is null' | null | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo', 'PNFDemo3'] + 'both queries are empty' | [] | [] || [] + 'both queries are null' | null | null || null + } + + def createConditionProperties(String conditionName, List> conditionParameters) { + return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters) + } + + def static createDataNodeList(dataNodeIds) { + def dataNodes =[] + dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) } + return dataNodes + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy new file mode 100644 index 0000000000..26541de64b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/CmHandleQueryConditionsSpec.groovy @@ -0,0 +1,37 @@ +/* + * ============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.impl.inventory.models + + +import spock.lang.Specification + +class CmHandleQueryConditionsSpec extends Specification { + + def 'CmHandle query condition names.'() { + expect: '3 conditions with the correct names' + assert CmHandleQueryConditions.ALL_CONDITION_NAMES.size() == 4 + assert CmHandleQueryConditions.ALL_CONDITION_NAMES.containsAll('hasAllProperties', + 'hasAllModules', + 'cmHandleWithCpsPath', + 'cmHandleWithTrustLevel') + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy new file mode 100644 index 0000000000..51c1f4bb13 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/InventoryQueryConditionsSpec.groovy @@ -0,0 +1,37 @@ +/* + * ============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.impl.inventory.models + + +import spock.lang.Specification + +class InventoryQueryConditionsSpec extends Specification { + + def 'Inventory query condition names.'() { + expect: '3 conditions with the correct names' + assert InventoryQueryConditions.ALL_CONDITION_NAMES.size() == 3 + assert InventoryQueryConditions.ALL_CONDITION_NAMES.containsAll('hasAllProperties', + 'hasAllAdditionalProperties', + 'cmHandleWithDmiPlugin') + } + + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy new file mode 100644 index 0000000000..3f379b0b8b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/models/YangModelCmHandleSpec.groovy @@ -0,0 +1,113 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory.models + +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA +import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.MODEL + +class YangModelCmHandleSpec extends Specification { + + def 'Creating yang model cm handle from a service api cm handle.'() { + given: 'a cm handle with properties' + def ncmpServiceCmHandle = new NcmpServiceCmHandle() + ncmpServiceCmHandle.cmHandleId = 'cm-handle-id01' + ncmpServiceCmHandle.dmiProperties = [myDmiProperty:'value1'] + ncmpServiceCmHandle.publicProperties = [myPublicProperty:'value2'] + and: 'with a composite state' + def compositeState = new CompositeStateBuilder() + .withCmHandleState(CmHandleState.LOCKED) + .withLastUpdatedTime('some-update-time') + .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, 'locked details') + .withOperationalDataStores(DataStoreSyncState.SYNCHRONIZED, 'some-sync-time').build() + ncmpServiceCmHandle.setCompositeState(compositeState) + when: 'it is converted to a yang model cm handle' + def objectUnderTest = YangModelCmHandle.toYangModelCmHandle('', '', '', ncmpServiceCmHandle,'my-module-set-tag', 'my-alternate-id', 'my-data-producer-identifier') + then: 'the result has the right size' + assert objectUnderTest.dmiProperties.size() == 1 + and: 'the result has the correct values for module set tag, alternate ID, and data producer identifier' + assert objectUnderTest.moduleSetTag == 'my-module-set-tag' + assert objectUnderTest.alternateId == 'my-alternate-id' + assert objectUnderTest.dataProducerIdentifier == 'my-data-producer-identifier' + and: 'the DMI property in the result has the correct name and value' + assert objectUnderTest.dmiProperties[0].name == 'myDmiProperty' + assert objectUnderTest.dmiProperties[0].value == 'value1' + and: 'the public property in the result has the correct name and value' + assert objectUnderTest.publicProperties[0].name == 'myPublicProperty' + assert objectUnderTest.publicProperties[0].value == 'value2' + and: 'the composite state matches the composite state of the ncmpServiceCmHandle' + objectUnderTest.getCompositeState().cmHandleState == CmHandleState.LOCKED + objectUnderTest.getCompositeState() == ncmpServiceCmHandle.getCompositeState() + } + + def 'Resolve DMI service name: #scenario and #requiredService service require.'() { + given: 'a yang model cm handle' + def objectUnderTest = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, dmiDataServiceName, + dmiModelServiceName, new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1'),'', '', '') + expect: + assert objectUnderTest.resolveDmiServiceName(requiredService) == expectedService + where: + scenario | dmiServiceName | dmiDataServiceName | dmiModelServiceName | requiredService || expectedService + 'common service registered' | 'common service' | 'does not matter' | 'does not matter' | DATA || 'common service' + 'common service registered' | 'common service' | 'does not matter' | 'does not matter' | MODEL || 'common service' + 'common service empty' | '' | 'data service' | 'does not matter' | DATA || 'data service' + 'common service empty' | '' | 'does not matter' | 'model service' | MODEL || 'model service' + 'common service blank' | ' ' | 'data service' | 'does not matter' | DATA || 'data service' + 'common service blank' | ' ' | 'does not matter' | 'model service' | MODEL || 'model service' + 'common service null ' | null | 'data service' | 'does not matter' | DATA || 'data service' + 'common service null' | null | 'does not matter' | 'model service' | MODEL || 'model service' + 'only model service registered' | null | null | 'does not matter' | DATA || null + 'only data service registered' | null | 'does not matter' | null | MODEL || null + } + + def 'Yang Model Cm Handle Deep Copy'() { + given: 'a yang model cm handle' + def currentYangModelCmHandle = new YangModelCmHandle(id: 'cmhandle', + publicProperties: [new YangModelCmHandle.Property('publicProperty1', 'value1')], + dmiProperties: [new YangModelCmHandle.Property('dmiProperty1', 'value1')], + compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)) + when: 'a deep copy is created' + def yangModelCmhandleDeepCopy = YangModelCmHandle.deepCopyOf(currentYangModelCmHandle) + and: 'we try to mutate current yang model cm handle' + currentYangModelCmHandle.id = 'cmhandle-changed' + currentYangModelCmHandle.dmiProperties = [new YangModelCmHandle.Property('updatedPublicProperty1', 'value1')] + currentYangModelCmHandle.publicProperties = [new YangModelCmHandle.Property('updatedDmiProperty1', 'value1')] + currentYangModelCmHandle.compositeState.cmHandleState = CmHandleState.READY + currentYangModelCmHandle.compositeState.dataSyncEnabled = true + then: 'there is no change in the deep copied object' + assert yangModelCmhandleDeepCopy.id == 'cmhandle' + assert yangModelCmhandleDeepCopy.dmiProperties == [new YangModelCmHandle.Property('dmiProperty1', 'value1')] + assert yangModelCmhandleDeepCopy.publicProperties == [new YangModelCmHandle.Property('publicProperty1', 'value1')] + assert yangModelCmhandleDeepCopy.compositeState.cmHandleState == CmHandleState.ADVISED + assert yangModelCmhandleDeepCopy.compositeState.dataSyncEnabled == false + and: 'equality on reference and hashcode behave as expected' + assert currentYangModelCmHandle.hashCode() != yangModelCmhandleDeepCopy.hashCode() + assert currentYangModelCmHandle != yangModelCmhandleDeepCopy + + } + + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy new file mode 100644 index 0000000000..751c97a4d0 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutorSpec.groovy @@ -0,0 +1,63 @@ +/* + * ============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.impl.inventory.sync + + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import spock.lang.Specification + +import java.util.concurrent.TimeoutException +import java.util.function.Supplier + +@SpringBootTest(classes = AsyncTaskExecutor) +class AsyncTaskExecutorSpec extends Specification { + + @Autowired + AsyncTaskExecutor objectUnderTest + def mockTaskSupplier = Mock(Supplier) + + def 'Parallelism level configuration.'() { + expect: 'Parallelism level is configured with the correct value' + assert objectUnderTest.getAsyncTaskParallelismLevel() == 3 + } + + def 'Task completion with #caseDescriptor.'() { + when: 'task completion is handled' + def irrelevantResponse = null + objectUnderTest.handleTaskCompletion(irrelevantResponse, exception); + then: 'any exception is swallowed by the task completion (logged)' + noExceptionThrown() + where: 'following cases are tested' + caseDescriptor | exception + 'no exception' | null + 'time out exception' | new TimeoutException("time-out") + 'unexpected exception' | new Exception("some exception") + } + + def 'Task execution.'() { + when: 'a task is submitted for execution' + objectUnderTest.executeTask(() -> mockTaskSupplier, 0) + then: 'the task submission is successful' + noExceptionThrown() + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy new file mode 100644 index 0000000000..2ee5226dc2 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/DataSyncWatchdogSpec.groovy @@ -0,0 +1,116 @@ +/* + * ============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.impl.inventory.sync + +import com.hazelcast.map.IMap +import org.onap.cps.api.CpsDataService +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import spock.lang.Specification + +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + +class DataSyncWatchdogSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + + def mockCpsDataService = Mock(CpsDataService) + + def mockSyncUtils = Mock(ModuleOperationsUtils) + + def mockDataSyncSemaphores = Mock(IMap) + + def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' + + def objectUnderTest = new DataSyncWatchdog(mockInventoryPersistence, mockCpsDataService, mockSyncUtils, mockDataSyncSemaphores) + + def compositeState = getCompositeState() + + def yangModelCmHandle1 = createSampleYangModelCmHandle('cm-handle-1') + + def yangModelCmHandle2 = createSampleYangModelCmHandle('cm-handle-2') + + def 'Data Sync for Cm Handle State in READY and Operational Sync State in UNSYNCHRONIZED.'() { + given: 'sample resource data' + def resourceData = jsonString + and: 'sync utilities returns a cm handle twice' + mockSyncUtils.getUnsynchronizedReadyCmHandles() >> [yangModelCmHandle1, yangModelCmHandle2] + when: 'data sync poll is executed' + objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() + then: 'the inventory persistence cm handle returns a composite state for the first cm handle' + 1 * mockInventoryPersistence.getCmHandleState('cm-handle-1') >> compositeState + and: 'the sync util returns first resource data' + 1 * mockSyncUtils.getResourceData('cm-handle-1') >> resourceData + and: 'the cm-handle data is saved' + 1 * mockCpsDataService.saveData(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cm-handle-1', jsonString, _) + and: 'the first cm handle operational sync state is updated' + 1 * mockInventoryPersistence.saveCmHandleState('cm-handle-1', compositeState) + then: 'the inventory persistence cm handle returns a composite state for the second cm handle' + 1 * mockInventoryPersistence.getCmHandleState('cm-handle-2') >> compositeState + and: 'the sync util returns first resource data' + 1 * mockSyncUtils.getResourceData('cm-handle-2') >> resourceData + and: 'the cm-handle data is saved' + 1 * mockCpsDataService.saveData(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cm-handle-2', jsonString, _) + and: 'the second cm handle operational sync state is updated from "UNSYNCHRONIZED" to "SYNCHRONIZED"' + 1 * mockInventoryPersistence.saveCmHandleState('cm-handle-2', compositeState) + } + + def 'Data Sync for Cm Handle State in READY and Operational Sync State in UNSYNCHRONIZED without resource data.'() { + given: 'sync utilities returns a cm handle' + mockSyncUtils.getUnsynchronizedReadyCmHandles() >> [yangModelCmHandle1] + when: 'data sync poll is executed' + objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() + then: 'the inventory persistence cm handle returns a composite state for the first cm handle' + 1 * mockInventoryPersistence.getCmHandleState('cm-handle-1') >> compositeState + and: 'the sync util returns no resource data' + 1 * mockSyncUtils.getResourceData('cm-handle-1') >> null + and: 'the cm-handle data is not saved' + 0 * mockCpsDataService.saveData(*_) + } + + def 'Data Sync for Cm Handle that is already being processed.'() { + given: 'sync utilities returns a cm handle' + mockSyncUtils.getUnsynchronizedReadyCmHandles() >> [yangModelCmHandle1] + and: 'the shared data sync semaphore indicate it is already being processed' + mockDataSyncSemaphores.putIfAbsent('cm-handle-1', _, _, _) >> 'something (not null)' + when: 'data sync poll is executed' + objectUnderTest.executeUnSynchronizedReadyCmHandlePoll() + then: 'it is NOT processed e.g. state is not requested' + 0 * mockInventoryPersistence.getCmHandleState(*_) + } + + def createSampleYangModelCmHandle(cmHandleId) { + def compositeState = getCompositeState() + return new YangModelCmHandle(id: cmHandleId, compositeState: compositeState) + } + + def getCompositeState() { + def cmHandleState = CmHandleState.READY + def compositeState = new CompositeState(cmHandleState: cmHandleState) + compositeState.setDataStores(CompositeState.DataStores.builder() + .operationalDataStore(CompositeState.Operational.builder().dataStoreSyncState(DataStoreSyncState.SYNCHRONIZED) + .build()).build()) + return compositeState + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy new file mode 100644 index 0000000000..52b48a366a --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -0,0 +1,225 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory.sync + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.core.read.ListAppender +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.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.model.DataNode +import org.onap.cps.utils.JsonObjectMapper +import org.slf4j.LoggerFactory +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import spock.lang.Specification + +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter +import java.util.stream.Collectors + +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.LOCKED_MISBEHAVING +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED + +class ModuleOperationsUtilsSpec extends Specification{ + + def mockCmHandleQueries = Mock(CmHandleQueryService) + + def mockDmiDataOperations = Mock(DmiDataOperations) + + def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + + def objectUnderTest = new ModuleOperationsUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper) + + def static neverUpdatedBefore = '1900-01-01T00:00:00.000+0100' + + def static now = OffsetDateTime.now() + + def static nowAsString = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now) + + def static dataNode = new DataNode(leaves: ['id': 'cm-handle-123']) + + def applicationContext = new AnnotationConfigApplicationContext() + + def logger = (Logger) LoggerFactory.getLogger(ModuleOperationsUtils) + def loggingListAppender + + void setup() { + logger.setLevel(Level.DEBUG) + loggingListAppender = new ListAppender() + logger.addAppender(loggingListAppender) + loggingListAppender.start() + applicationContext.refresh() + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(ModuleOperationsUtils.class)).detachAndStopAllAppenders() + applicationContext.close() + } + + def 'Get an advised Cm-Handle where ADVISED cm handle #scenario'() { + given: 'the inventory persistence service returns a collection of data nodes' + mockCmHandleQueries.queryCmHandlesByState(CmHandleState.ADVISED) >> dataNodeCollection + when: 'get advised cm handles are fetched' + def yangModelCmHandles = objectUnderTest.getAdvisedCmHandles() + then: 'the returned data node collection is the correct size' + yangModelCmHandles.size() == expectedDataNodeSize + where: 'the following scenarios are used' + scenario | dataNodeCollection || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize + 'exists' | [dataNode] || 1 | 1 + 'does not exist' | [] || 0 | 0 + } + + def 'Update Lock Reason, Details and Attempts where lock reason #scenario'() { + given: 'A locked state' + def compositeState = new CompositeState(lockReason: lockReason) + when: 'update cm handle details and attempts is called' + objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_SYNC_FAILED, 'new error message') + then: 'the composite state lock reason and details are updated' + assert compositeState.lockReason.lockReasonCategory == MODULE_SYNC_FAILED + assert compositeState.lockReason.details.contains(expectedDetails) + where: + scenario | lockReason || expectedDetails + 'does not exist' | null || 'Attempt #1 failed: new error message' + 'exists' | CompositeState.LockReason.builder().details("Attempt #2 failed: some error message").build() || 'Attempt #3 failed: new error message' + } + + def 'Update lock reason details that contains #scenario'() { + given: 'A locked state' + def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) + .withLockReason(MODULE_UPGRADE, "Upgrade to ModuleSetTag: " + moduleSetTag).build() + when: 'update cm handle details' + objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_UPGRADE_FAILED, 'new error message') + then: 'the composite state lock reason and details are updated' + assert compositeState.lockReason.lockReasonCategory == MODULE_UPGRADE_FAILED + assert compositeState.lockReason.details.contains("Upgrade to ModuleSetTag: " + expectedDetails) + where: + scenario | moduleSetTag || expectedDetails + 'a module set tag' | 'someModuleSetTag' || 'someModuleSetTag' + 'empty module set tag' | '' || '' + } + + def 'Get all locked cm-Handles where lock reasons are model sync failed or upgrade'() { + given: 'the cps (persistence service) returns a collection of data nodes' + mockCmHandleQueries.queryCmHandleAncestorsByCpsPath(ModuleOperationsUtils.CPS_PATH_CM_HANDLES_MODEL_SYNC_FAILED_OR_UPGRADE, + FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode] + when: 'get locked Misbehaving cm handle is called' + def result = objectUnderTest.getCmHandlesThatFailedModelSyncOrUpgrade() + then: 'the returned cm handle collection is the correct size' + result.size() == 1 + and: 'the correct cm handle is returned' + result[0].id == 'cm-handle-123' + } + + def 'Retry Locked Cm-Handle where the last update time is #scenario'() { + given: 'Last update was #lastUpdateMinutesAgo minutes ago (-1 means never)' + def lastUpdatedTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now.minusMinutes(lastUpdateMinutesAgo)) + if (lastUpdateMinutesAgo < 0 ) { + lastUpdatedTime = neverUpdatedBefore + } + when: 'checking to see if cm handle is ready for retry' + def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() + .withLockReason(MODULE_SYNC_FAILED, lockDetails) + .withLastUpdatedTime(lastUpdatedTime).build()) + then: 'retry is only attempted when expected' + assert result == retryExpected + and: 'logs contain related information' + def logs = loggingListAppender.list.toString() + assert logs.contains(logReason) + where: 'the following parameters are used' + scenario | lastUpdateMinutesAgo | lockDetails | logReason || retryExpected + 'never attempted before' | -1 | 'Fist attempt:' | 'First Attempt:' || true + '1st attempt, last attempt > 2 minute ago' | 3 | 'Attempt #1 failed: some error' | 'Retry due now' || true + '2nd attempt, last attempt < 4 minutes ago' | 1 | 'Attempt #2 failed: some error' | 'Time until next attempt is 3 minutes:' || false + '2nd attempt, last attempt > 4 minutes ago' | 5 | 'Attempt #2 failed: some error' | 'Retry due now' || true + } + + def 'Retry Locked Cm-Handle with lock reasons (category) #lockReasonCategory'() { + when: 'checking to see if cm handle is ready for retry' + def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() + .withLockReason(lockReasonCategory, 'some details') + .withLastUpdatedTime(nowAsString).build()) + then: 'verify retry attempts' + assert !result + and: 'logs contain related information' + def logs = loggingListAppender.list.toString() + assert logs.contains(logReason) + where: 'the following lock reasons occurred' + scenario | lockReasonCategory || logReason + 'module upgrade' | MODULE_UPGRADE_FAILED || 'First Attempt:' + 'module sync failed' | MODULE_SYNC_FAILED || 'First Attempt:' + 'lock misbehaving' | LOCKED_MISBEHAVING || 'Locked for other reason' + } + + def 'Get a Cm-Handle where #scenario'() { + given: 'the inventory persistence service returns a collection of data nodes' + mockCmHandleQueries.queryCmHandlesByOperationalSyncState(DataStoreSyncState.UNSYNCHRONIZED) >> unSynchronizedDataNodes + mockCmHandleQueries.cmHandleHasState('cm-handle-123', CmHandleState.READY) >> cmHandleHasState + when: 'get advised cm handles are fetched' + def yangModelCollection = objectUnderTest.getUnsynchronizedReadyCmHandles() + then: 'the returned data node collection is the correct size' + yangModelCollection.size() == expectedDataNodeSize + and: 'the result contains the correct data' + yangModelCollection.stream().map(yangModel -> yangModel.id).collect(Collectors.toSet()) == expectedYangModelCollectionIds + where: 'the following scenarios are used' + scenario | unSynchronizedDataNodes | cmHandleHasState || expectedDataNodeSize | expectedYangModelCollectionIds + 'a Cm-Handle unsynchronized and ready' | [dataNode] | true || 1 | ['cm-handle-123'] as Set + 'a Cm-Handle unsynchronized but not ready' | [dataNode] | false || 0 | [] as Set + 'all Cm-Handle synchronized' | [] | false || 0 | [] as Set + } + + def 'Get resource data through DMI Operations #scenario'() { + given: 'the inventory persistence service returns a collection of data nodes' + def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' + JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString); + def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK) + mockDmiDataOperations.getAllResourceDataFromDmi('cm-handle-123', _) >> responseEntity + when: 'get resource data is called' + def result = objectUnderTest.getResourceData('cm-handle-123') + then: 'the returned data is correct' + result == jsonString + } + + def 'Extract module set tag and number of attempt when lock reason contains #scenario'() { + expect: 'lock reason details are extracted correctly' + def result = objectUnderTest.getLockedCompositeStateDetails(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, lockReasonDetails).build().lockReason) + and: 'the result contains the correct moduleSetTag' + assert result['moduleSetTag'] == expectedModuleSetTag + and: 'the result contains the correct number of attempts' + assert result['attempt'] == expectedNumberOfAttempts + where: 'the following scenarios are used' + scenario | lockReasonDetails || expectedModuleSetTag | expectedNumberOfAttempts + 'module set tag only' | 'Upgrade to ModuleSetTag: targetModuleSetTag' || 'targetModuleSetTag' | null + 'number of attempts only' | 'Attempt #1 failed: some error' || null | '1' + 'number of attempts and module set tag both' | 'Upgrade to ModuleSetTag: targetModuleSetTag Attempt #1 failed: some error' || 'targetModuleSetTag' | '1' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy new file mode 100644 index 0000000000..685a65b15a --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -0,0 +1,170 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory.sync + +import org.onap.cps.api.CpsAnchorService +import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsModuleService +import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.impl.inventory.CmHandleQueryService +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.CascadeDeleteAllowed +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException +import org.onap.cps.spi.model.DataNode +import org.onap.cps.spi.model.DataNodeBuilder +import org.onap.cps.spi.model.ModuleReference +import org.onap.cps.utils.JsonObjectMapper +import spock.lang.Specification + +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE + +class ModuleSyncServiceSpec extends Specification { + + def mockCpsModuleService = Mock(CpsModuleService) + def mockDmiModelOperations = Mock(DmiModelOperations) + def mockCpsAnchorService = Mock(CpsAnchorService) + def mockCmHandleQueries = Mock(CmHandleQueryService) + def mockCpsDataService = Mock(CpsDataService) + def mockJsonObjectMapper = Mock(JsonObjectMapper) + + def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, + mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper) + + def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME + def static cmHandleWithModuleSetTag = new DataNodeBuilder() + .withXpath("/dmi-registry/cm-handles[@id='otherId']") + .withLeaves(['id': 'otherId', 'module-set-tag': 'tag-1']) + .withAnchor('otherId').build() + + def 'Sync model for a NEW cm handle using module set tags: #scenario.'() { + given: 'a cm handle state to be synced' + def ncmpServiceCmHandle = new NcmpServiceCmHandle() + ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withCmHandleState(CmHandleState.ADVISED).build()) + ncmpServiceCmHandle.cmHandleId = 'ch-1' + def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, moduleSetTag, '', '') + and: 'DMI operations returns some module references' + def moduleReferences = [ new ModuleReference('module1','1'), new ModuleReference('module2','2') ] + mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences + and: 'DMI-Plugin returns resource(s) for "new" module(s)' + mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, identifiedNewModuleReferences) >> newModuleNameContentToMap + and: 'the module service identifies #identifiedNewModuleReferences.size() new modules' + mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> identifiedNewModuleReferences + and: 'system contains other cm handle with "same tag" (that is READY)' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag + when: 'module sync is triggered' + objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle) + then: 'create schema set from module is invoked with correct parameters' + 1 * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', newModuleNameContentToMap, moduleReferences) + and: 'anchor is created with the correct parameters' + 1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1') + where: 'the following parameters are used' + scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap | moduleSetTag | existingCmHandlesWithSameTag + 'one new module, new tag' | [['module2': '2'], ['module3': '3']] | [new ModuleReference('module1', '1')] | [module1: 'some yang source'] | '' | [] + 'no new module, new tag' | [['module1': '1'], ['module2': '2']] | [] | [:] | 'new-tag-1' | [] + 'same tag' | [['module1': '1'], ['module2': '2']] | [] | [:] | 'same-tag' | [cmHandleWithModuleSetTag] + } + + def 'Upgrade model for an existing cm handle with Module Set Tag where the modules are #scenario'() { + given: 'a cm handle being upgraded to module set tag: tag-1' + def ncmpServiceCmHandle = new NcmpServiceCmHandle() + ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: tag-1').build()) + def dmiServiceName = 'some service name' + ncmpServiceCmHandle.cmHandleId = 'upgraded-ch' + def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'tag-1', '', '') + and: 'some module references' + def moduleReferences = [ new ModuleReference('module1','1') ] + and: 'DMI operations returns some module references for upgraded cm handle' + mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences + mockDmiModelOperations.getNewYangResourcesFromDmi(_, []) >> [:] + and: 'none of these module references are new (unknown to the system)' + mockCpsModuleService.identifyNewModuleReferences(_) >> [] + and: 'CPS-Core returns list of existing module resources for TBD' + mockCpsModuleService.getYangResourcesModuleReferences(*_) >> [ new ModuleReference('module1','1') ] + and: 'system contains #existingCmHandlesWithSameTag.size() cm handles with same tag' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> existingCmHandlesWithSameTag + and: 'the other cm handle is a state ready' + mockCmHandleQueries.cmHandleHasState('otherId', CmHandleState.READY) >> true + when: 'module sync is triggered' + objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle) + then: 'update schema set from module is invoked for the upgraded cm handle' + 1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences) + and: 'create schema set from module is not invoked for the upgraded cm handle' + 0 * mockCpsModuleService.createSchemaSetFromModules(*_) + and: 'No anchor is created for the upgraded cm handle' + 0 * mockCpsAnchorService.createAnchor(*_) + where: 'the following parameters are used' + scenario | existingCmHandlesWithSameTag + 'new' | [] + 'in database' | [cmHandleWithModuleSetTag] + } + + def 'upgrade model for a existing cm handle'() { + given: 'a cm handle that is ready but locked for upgrade' + def ncmpServiceCmHandle = new NcmpServiceCmHandle() + ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder() + .withLockReason(MODULE_UPGRADE, 'Upgrade to ModuleSetTag: targetModuleSetTag').build()) + ncmpServiceCmHandle.setCmHandleId('cmHandleId-1') + def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, 'targetModuleSetTag', '', '') + mockCmHandleQueries.cmHandleHasState('cmHandleId-1', CmHandleState.READY) >> true + and: 'the module service returns some module references' + def moduleReferences = [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')] + mockCpsModuleService.getYangResourcesModuleReferences(*_)>> moduleReferences + and: 'a cm handle with the same moduleSetTag can be found in the registry' + mockCmHandleQueries.queryNcmpRegistryByCpsPath(*_) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1'], + childDataNodes: [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']/state', leaves: ['cm-handle-state': 'READY'])])] + when: 'module upgrade is triggered' + objectUnderTest.syncAndUpgradeSchemaSet(yangModelCmHandle) + then: 'the upgrade is delegated to the module service (with the correct parameters)' + 1 * mockCpsModuleService.upgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', Collections.emptyMap(), moduleReferences) + } + + def 'Delete Schema Set for CmHandle'() { + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id') + then: 'the module service is invoked to delete the correct schema set' + 1 * mockCpsModuleService.deleteSchemaSet(expectedDataspaceName, 'some-cmhandle-id', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) + } + + def 'Delete a non-existing Schema Set for CmHandle' () { + given: 'the DB throws an exception because its Schema Set does not exist' + mockCpsModuleService.deleteSchemaSet(*_) >> { throw new SchemaSetNotFoundException('some-dataspace-name', 'some-cmhandle-id') } + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id') + then: 'the exception from the DB is ignored; there are no exceptions' + noExceptionThrown() + } + + def 'Delete Schema Set for CmHandle with other exception' () { + given: 'an exception other than SchemaSetNotFoundException is thrown' + UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException(); + 1 * mockCpsModuleService.deleteSchemaSet(*_) >> { throw unsupportedOperationException } + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id') + then: 'an exception is thrown' + def result = thrown(UnsupportedOperationException) + result == unsupportedOperationException + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy new file mode 100644 index 0000000000..a9dbf07e2b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -0,0 +1,226 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory.sync + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender +import com.hazelcast.config.Config +import com.hazelcast.instance.impl.HazelcastInstanceFactory +import com.hazelcast.map.IMap +import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.ncmp.api.inventory.models.CompositeState +import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.model.DataNode +import org.slf4j.LoggerFactory +import spock.lang.Specification + +import java.util.concurrent.atomic.AtomicInteger + +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED + +class ModuleSyncTasksSpec extends Specification { + + def logger = Spy(ListAppender) + + void setup() { + ((Logger) LoggerFactory.getLogger(ModuleSyncTasks.class)).addAppender(logger) + logger.start() + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(ModuleSyncTasks.class)).detachAndStopAllAppenders() + } + + def mockInventoryPersistence = Mock(InventoryPersistence) + + def mockSyncUtils = Mock(ModuleOperationsUtils) + + def mockModuleSyncService = Mock(ModuleSyncService) + + def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) + + IMap moduleSyncStartedOnCmHandles = HazelcastInstanceFactory + .getOrCreateHazelcastInstance(new Config('hazelcastInstanceName')) + .getMap('mapInstanceName') + + def batchCount = new AtomicInteger(5) + + def objectUnderTest = new ModuleSyncTasks(mockInventoryPersistence, mockSyncUtils, mockModuleSyncService, + mockLcmEventsCmHandleStateHandler, moduleSyncStartedOnCmHandles) + + def 'Module Sync ADVISED cm handles.'() { + given: 'cm handles in an ADVISED state' + def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) + def cmHandle2 = cmHandleAsDataNodeByIdAndState('cm-handle-2', CmHandleState.ADVISED) + and: 'the inventory persistence cm handle returns a ADVISED state for the any handle' + mockInventoryPersistence.getCmHandleState(_) >> new CompositeState(cmHandleState: CmHandleState.ADVISED) + when: 'module sync poll is executed' + objectUnderTest.performModuleSync([cmHandle1, cmHandle2], batchCount) + then: 'module sync service deletes schemas set of each cm handle if it already exists' + 1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-1') + 1 * mockModuleSyncService.deleteSchemaSetIfExists('cm-handle-2') + and: 'module sync service is invoked for each cm handle' + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-1' } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-2' } + and: 'the state handler is called for the both cm handles' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> + assertBatch(args, ['cm-handle-1', 'cm-handle-2'], CmHandleState.READY) + } + and: 'batch count is decremented by one' + assert batchCount.get() == 4 + } + + def 'Module Sync ADVISED cm handle with failure during sync.'() { + given: 'a cm handle in an ADVISED state' + def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.ADVISED) + and: 'the inventory persistence cm handle returns a ADVISED state for the cm handle' + def cmHandleState = new CompositeState(cmHandleState: CmHandleState.ADVISED) + 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> cmHandleState + and: 'module sync service attempts to sync the cm handle and throws an exception' + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } + when: 'module sync is executed' + objectUnderTest.performModuleSync([cmHandle], batchCount) + then: 'update lock reason, details and attempts is invoked' + 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, MODULE_SYNC_FAILED, 'some exception') + and: 'the state handler is called to update the state to LOCKED' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> + assertBatch(args, ['cm-handle'], CmHandleState.LOCKED) + } + and: 'batch count is decremented by one' + assert batchCount.get() == 4 + } + + def 'Failed cm handle during #scenario.'() { + given: 'a cm handle in LOCKED state' + def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED) + and: 'the inventory persistence cm handle returns a LOCKED state with reason for the cm handle' + def expectedCmHandleState = new CompositeState(cmHandleState: cmHandleState, lockReason: CompositeState + .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build()) + 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState + and: 'module sync service attempts to sync/upgrade the cm handle and throws an exception' + mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } + mockModuleSyncService.syncAndUpgradeSchemaSet(*_) >> { throw new Exception('some exception') } + when: 'module sync is executed' + objectUnderTest.performModuleSync([cmHandle], batchCount) + then: 'update lock reason, details and attempts is invoked' + 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(expectedCmHandleState, expectedLockReasonCategory, 'some exception') + where: + scenario | cmHandleState | lockReasonCategory | lockReasonDetails || expectedLockReasonCategory + 'module upgrade' | CmHandleState.LOCKED | MODULE_UPGRADE_FAILED | 'Upgrade to ModuleSetTag: some-module-set-tag' || MODULE_UPGRADE_FAILED + 'module sync' | CmHandleState.LOCKED | MODULE_SYNC_FAILED | 'some lock details' || MODULE_SYNC_FAILED + } + + def 'Reset failed CM Handles #scenario.'() { + given: 'cm handles in an locked state' + def lockedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) + .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, '').withLastUpdatedTimeNow().build() + def yangModelCmHandle1 = new YangModelCmHandle(id: 'cm-handle-1', compositeState: lockedState) + def yangModelCmHandle2 = new YangModelCmHandle(id: 'cm-handle-2', compositeState: lockedState) + def expectedCmHandleStatePerCmHandle = [(yangModelCmHandle1): CmHandleState.ADVISED] + and: 'clear in progress map' + resetModuleSyncStartedOnCmHandles(moduleSyncStartedOnCmHandles) + and: 'add cm handle entry into progress map' + moduleSyncStartedOnCmHandles.put('cm-handle-1', 'started') + moduleSyncStartedOnCmHandles.put('cm-handle-2', 'started') + and: 'sync utils retry locked cm handle returns #isReadyForRetry' + mockSyncUtils.needsModuleSyncRetryOrUpgrade(lockedState) >>> isReadyForRetry + when: 'resetting failed cm handles' + objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2]) + then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry' + expectedNumberOfInvocationsToUpdateCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle) + and: 'after reset performed size of in progress map' + assert moduleSyncStartedOnCmHandles.size() == inProgressMapSize + where: + scenario | isReadyForRetry | inProgressMapSize || expectedNumberOfInvocationsToUpdateCmHandleState + 'retry locked cm handle' | [true, false] | 1 || 1 + 'do not retry locked cm handle' | [false, false] | 2 || 0 + } + + def 'Module Sync ADVISED cm handle without entry in progress map.'() { + given: 'cm handles in an ADVISED state' + def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) + and: 'the inventory persistence cm handle returns a ADVISED state for the any handle' + mockInventoryPersistence.getCmHandleState(_) >> new CompositeState(cmHandleState: CmHandleState.ADVISED) + and: 'entry in progress map for other cm handle' + moduleSyncStartedOnCmHandles.put('other-cm-handle', 'started') + when: 'module sync poll is executed' + objectUnderTest.performModuleSync([cmHandle1], batchCount) + then: 'module sync service is invoked for cm handle' + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } + and: 'the entry for other cm handle is still in the progress map' + assert moduleSyncStartedOnCmHandles.get('other-cm-handle') != null + } + + def 'Remove already processed cm handle id from hazelcast map'() { + given: 'hazelcast map contains cm handle id' + moduleSyncStartedOnCmHandles.put('ch-1', 'started') + when: 'remove cm handle entry' + objectUnderTest.removeResetCmHandleFromModuleSyncMap('ch-1') + then: 'an event is logged with level INFO' + def loggingEvent = getLoggingEvent() + assert loggingEvent.level == Level.INFO + and: 'the log indicates the cm handle entry is removed successfully' + assert loggingEvent.formattedMessage == 'ch-1 removed from in progress map' + } + + def 'Remove non-existing cm handle id from hazelcast map'() { + given: 'hazelcast map does not contains cm handle id' + def result = moduleSyncStartedOnCmHandles.get('non-existing-cm-handle') + assert result == null + when: 'remove cm handle entry from hazelcast map' + objectUnderTest.removeResetCmHandleFromModuleSyncMap('non-existing-cm-handle') + then: 'no event is logged' + def loggingEvent = getLoggingEvent() + assert loggingEvent == null + } + + def cmHandleAsDataNodeByIdAndState(cmHandleId, cmHandleState) { + return new DataNode(anchorName: cmHandleId, leaves: ['id': cmHandleId, 'cm-handle-state': cmHandleState]) + } + + def assertBatch(args, expectedCmHandleStatePerCmHandleIds, expectedCmHandleState) { + { + Map actualCmHandleStatePerCmHandle = args[0] + assert actualCmHandleStatePerCmHandle.size() == expectedCmHandleStatePerCmHandleIds.size() + actualCmHandleStatePerCmHandle.each { + assert expectedCmHandleStatePerCmHandleIds.contains(it.key.id) + assert it.value == expectedCmHandleState + } + } + return true + } + + def resetModuleSyncStartedOnCmHandles(moduleSyncStartedOnCmHandles) { + moduleSyncStartedOnCmHandles.clear(); + } + + def getLoggingEvent() { + return logger.list[0] + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy new file mode 100644 index 0000000000..155edc8bc6 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -0,0 +1,121 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2023 Nordix Foundation + * Modifications Copyright (C) 2022 Bell Canada + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory.sync + +import com.hazelcast.map.IMap +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +import java.util.concurrent.ArrayBlockingQueue + +class ModuleSyncWatchdogSpec extends Specification { + + def mockSyncUtils = Mock(ModuleOperationsUtils) + + def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE + + def moduleSyncWorkQueue = new ArrayBlockingQueue(testQueueCapacity) + + def mockModuleSyncStartedOnCmHandles = Mock(IMap) + + def mockModuleSyncTasks = Mock(ModuleSyncTasks) + + def spiedAsyncTaskExecutor = Spy(AsyncTaskExecutor) + + def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor) + + void setup() { + spiedAsyncTaskExecutor.setupThreadPool() + } + + def 'Module sync advised cm handles with #scenario.'() { + given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles' + mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + and: 'the executor has enough available threads' + spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3 + when: ' module sync is started' + objectUnderTest.moduleSyncAdvisedCmHandles() + then: 'it performs #expectedNumberOfTaskExecutions tasks' + expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_) + where: 'the following parameter are used' + scenario | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions + 'less then 1 batch' | 1 || 1 + 'exactly 1 batch' | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 1 + '2 batches' | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 2 + 'queue capacity' | testQueueCapacity || 3 + 'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3 + } + + def 'Module sync advised cm handles starts with no available threads.'() { + given: 'sync utilities returns a advise cm handles' + mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the executor first has no threads but has one thread on the second attempt' + spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >>> [ 0, 1 ] + when: ' module sync is started' + objectUnderTest.moduleSyncAdvisedCmHandles() + then: 'it performs one task' + 1 * spiedAsyncTaskExecutor.executeTask(*_) + } + + def 'Module sync advised cm handles already handled.'() { + given: 'sync utilities returns a advise cm handles' + mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the executor has a thread available' + spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 1 + and: 'the semaphore cache indicates the cm handle is already being processed' + mockModuleSyncStartedOnCmHandles.putIfAbsent(*_) >> 'Started' + when: ' module sync is started' + objectUnderTest.moduleSyncAdvisedCmHandles() + then: 'it does NOT execute a task to process the (empty) batch' + 0 * spiedAsyncTaskExecutor.executeTask(*_) + } + + def 'Module sync with previous cm handle(s) left in work queue.'() { + given: 'there is still a cm handle in the queue' + moduleSyncWorkQueue.offer(new DataNode()) + and: 'sync utilities returns many advise cm handles' + mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(500) + and: 'the executor has plenty threads available' + spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 10 + when: ' module sync is started' + objectUnderTest.moduleSyncAdvisedCmHandles() + then: 'it does executes only one task to process the remaining handle in the queue' + 1 * spiedAsyncTaskExecutor.executeTask(*_) + } + + def 'Reset failed cm handles.'() { + given: 'sync utilities returns failed cm handles' + def failedCmHandles = [new YangModelCmHandle()] + mockSyncUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles + when: 'reset failed cm handles is started' + objectUnderTest.resetPreviouslyFailedCmHandles() + then: 'it is delegated to the module sync task (service)' + 1 * mockModuleSyncTasks.resetFailedCmHandles(failedCmHandles) + } + + def createDataNodes(numberOfDataNodes) { + def dataNodes = [] + (1..numberOfDataNodes).each {dataNodes.add(new DataNode())} + return dataNodes + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy new file mode 100644 index 0000000000..8e59922c41 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfigSpec.groovy @@ -0,0 +1,143 @@ +/* + * ============LICENSE_START======================================================== + * Copyright (C) 2022-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory.sync + +import com.hazelcast.config.Config +import com.hazelcast.core.Hazelcast +import com.hazelcast.map.IMap +import org.onap.cps.spi.model.DataNode +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification +import spock.util.concurrent.PollingConditions + +import java.util.concurrent.BlockingQueue +import java.util.concurrent.TimeUnit + +@SpringBootTest +@ContextConfiguration(classes = [SynchronizationCacheConfig]) +class SynchronizationCacheConfigSpec extends Specification { + + @Autowired + private BlockingQueue moduleSyncWorkQueue + + @Autowired + private IMap moduleSyncStartedOnCmHandles + + @Autowired + private IMap dataSyncSemaphores + + def 'Embedded (hazelcast) Caches for Module and Data Sync.'() { + expect: 'system is able to create an instance of the Module Sync Work Queue' + assert null != moduleSyncWorkQueue + and: 'system is able to create an instance of a map to hold cm handles which have started (and maybe finished) module sync' + assert null != moduleSyncStartedOnCmHandles + and: 'system is able to create an instance of a map to hold data sync semaphores' + assert null != dataSyncSemaphores + 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') + } + + def 'Verify configs for Distributed objects'(){ + given: 'the Module Sync Work Queue config' + def moduleSyncWorkQueueConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncWorkQueue').config + def moduleSyncDefaultWorkQueueConfig = moduleSyncWorkQueueConfig.queueConfigs.get('defaultQueueConfig') + and: 'the Module Sync Started Cm Handle Map config' + def moduleSyncStartedOnCmHandlesConfig = Hazelcast.getHazelcastInstanceByName('moduleSyncStartedOnCmHandles').config + def moduleSyncStartedOnCmHandlesMapConfig = moduleSyncStartedOnCmHandlesConfig.mapConfigs.get('moduleSyncStartedConfig') + and: 'the Data Sync Semaphores Map config' + def dataSyncSemaphoresConfig = Hazelcast.getHazelcastInstanceByName('dataSyncSemaphores').config + def dataSyncSemaphoresMapConfig = dataSyncSemaphoresConfig.mapConfigs.get('dataSyncSemaphoresConfig') + expect: 'system created instance with correct config of Module Sync Work Queue' + assert moduleSyncDefaultWorkQueueConfig.backupCount == 1 + assert moduleSyncDefaultWorkQueueConfig.asyncBackupCount == 0 + and: 'Module Sync Started Cm Handle Map has the correct settings' + assert moduleSyncStartedOnCmHandlesMapConfig.backupCount == 1 + assert moduleSyncStartedOnCmHandlesMapConfig.asyncBackupCount == 0 + and: 'Data Sync Semaphore Map has the correct settings' + assert dataSyncSemaphoresMapConfig.backupCount == 1 + assert dataSyncSemaphoresMapConfig.asyncBackupCount == 0 + and: 'all instances are part of same cluster' + def testClusterName = 'cps-and-ncmp-test-caches' + assert moduleSyncWorkQueueConfig.clusterName == testClusterName + assert moduleSyncStartedOnCmHandlesConfig.clusterName == testClusterName + assert dataSyncSemaphoresConfig.clusterName == testClusterName + } + + 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) + then: 'the entry is present in the map' + assert moduleSyncStartedOnCmHandles.get('testKeyModuleSync') != null + and: 'the entry expires' + new PollingConditions().within(10) { + assert moduleSyncStartedOnCmHandles.get('testKeyModuleSync') == null + } + } + + def 'Time to Live Verify for Data Sync Semaphore'() { + when: 'the key is inserted with a TTL of 1 second' + dataSyncSemaphores.put('testKeyDataSync', Boolean.TRUE, 1, TimeUnit.SECONDS) + then: 'the entry is present in the map' + assert dataSyncSemaphores.get('testKeyDataSync') != null + and: 'the entry expires' + new PollingConditions().within(10) { + assert dataSyncSemaphores.get('testKeyDataSync') == null + } + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy new file mode 100644 index 0000000000..a943962e96 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/WatchdogSchedulingConfigurerSpec.groovy @@ -0,0 +1,42 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.inventory.sync + + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +@SpringBootTest +@ContextConfiguration(classes = [WatchdogSchedulingConfigurer]) +class WatchdogSchedulingConfigurerSpec extends Specification { + + @Autowired + WatchdogSchedulingConfigurer watchdogSchedulingConfigurer + + def 'Validate watchdog scheduling configuration'() { + given: 'task scheduler configuration properties are loaded as map' + def linkedHashMap = watchdogSchedulingConfigurer.taskScheduler().getProperties() + expect: 'thread name prefix is mapped correctly' + assert linkedHashMap.'threadNamePrefix' == 'watchdog-th-' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy new file mode 100644 index 0000000000..ad84495825 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/utils/AlternateIdMatcherSpec.groovy @@ -0,0 +1,66 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.utils + +import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException +import org.onap.cps.ncmp.impl.inventory.InventoryPersistence +import org.onap.cps.spi.exceptions.DataNodeNotFoundException +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class AlternateIdMatcherSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence) + + def setup() { + given: 'cm handle in the registry with alternate id /a/b' + mockInventoryPersistence.getCmHandleDataNodeByAlternateId('/a/b') >> new DataNode() + and: 'no other cm handle' + mockInventoryPersistence.getCmHandleDataNodeByAlternateId(_) >> { throw new DataNodeNotFoundException('', '') } + } + + def 'Finding longest alternate id matches.'() { + expect: 'querying for alternate id a matching result found' + assert objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') != null + where: 'the following parameters are used' + scenario | targetAlternateId + 'exact match' | '/a/b' + 'parent match' | '/a/b/c' + 'grand parent match' | '/a/b/c/d' + 'trailing separator match' | '/a/b/' + } + + def 'Attempt to find longest alternate id match without any matches.'() { + when: 'attempt to find alternateId' + objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') + then: 'no alternate id match found exception thrown' + def thrown = thrown(NoAlternateIdMatchFoundException) + and: 'the exception has the relevant details from the error response' + assert thrown.message == 'No matching cm handle found using alternate ids' + assert thrown.details == 'cannot find a datanode with alternate id ' + targetAlternateId + where: 'the following parameters are used' + scenario | targetAlternateId + 'no match for parent only' | '/a' + 'no match for other child' | '/a/c' + 'no match at all' | '/x/y' + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy index 3d490c861c..dd1fd0b462 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy @@ -35,7 +35,7 @@ import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME class CmDataSubscriptionModelLoaderSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy index d432886888..76c1589be3 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy @@ -33,8 +33,8 @@ import org.springframework.boot.context.event.ApplicationStartedEvent import org.springframework.context.annotation.AnnotationConfigApplicationContext import spock.lang.Specification -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME -import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR class InventoryModelLoaderSpec extends Specification { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/AlternateIdMatcherSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/AlternateIdMatcherSpec.groovy deleted file mode 100644 index 3422610450..0000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/utils/AlternateIdMatcherSpec.groovy +++ /dev/null @@ -1,66 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2024 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the 'License'); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.onap.cps.ncmp.utils - -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence -import org.onap.cps.ncmp.exceptions.NoAlternateIdMatchFoundException -import org.onap.cps.spi.exceptions.DataNodeNotFoundException -import org.onap.cps.spi.model.DataNode -import spock.lang.Specification - -class AlternateIdMatcherSpec extends Specification { - - def mockInventoryPersistence = Mock(InventoryPersistence) - def objectUnderTest = new AlternateIdMatcher(mockInventoryPersistence) - - def setup() { - given: 'cm handle in the registry with alternate id /a/b' - mockInventoryPersistence.getCmHandleDataNodeByAlternateId('/a/b') >> new DataNode() - and: 'no other cm handle' - mockInventoryPersistence.getCmHandleDataNodeByAlternateId(_) >> { throw new DataNodeNotFoundException('', '') } - } - - def 'Finding longest alternate id matches.'() { - expect: 'querying for alternate id a matching result found' - assert objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') != null - where: 'the following parameters are used' - scenario | targetAlternateId - 'exact match' | '/a/b' - 'parent match' | '/a/b/c' - 'grand parent match' | '/a/b/c/d' - 'trailing separator match' | '/a/b/' - } - - def 'Attempt to find longest alternate id match without any matches.'() { - when: 'attempt to find alternateId' - objectUnderTest.getCmHandleDataNodeByLongestMatchingAlternateId(targetAlternateId, '/') - then: 'no alternate id match found exception thrown' - def thrown = thrown(NoAlternateIdMatchFoundException) - and: 'the exception has the relevant details from the error response' - assert thrown.message == 'No matching cm handle found using alternate ids' - assert thrown.details == 'cannot find a datanode with alternate id ' + targetAlternateId - where: 'the following parameters are used' - scenario | targetAlternateId - 'no match for parent only' | '/a' - 'no match for other child' | '/a/c' - 'no match at all' | '/x/y' - } -} \ No newline at end of file -- cgit 1.2.3-korg