From 6b2cdc63c02cc2d1a9bc9d43997ab14e740d7973 Mon Sep 17 00:00:00 2001 From: ToineSiebelink Date: Mon, 29 Aug 2022 12:35:33 +0100 Subject: Performance Improvement: Use hazelcast blocking queue - Introducing hazelcast for queue and progress map - process batch of 100 at the time - decreased module sync watchdog sleeptime to 5 seconds - separate module sync tasks in new class and some other async preparations and easier testing - tests for batching in module sync watchdog - remove qualifiers annotation (support) where no longer needed Issue-ID: CPS-1210 Issue-ID: CPS-1126 Signed-off-by: ToineSiebelink Change-Id: I0a7d3755bf774e27c5688741bddb01f427d4a8a7 --- .../SynchronizationCacheConfigSpec.groovy | 54 ++++++++++ .../SynchronizationSemaphoresConfigSpec.groovy | 49 --------- .../inventory/sync/ModuleSyncServiceSpec.groovy | 34 ++----- .../api/inventory/sync/ModuleSyncTasksSpec.groovy | 110 ++++++++++++++++++++ .../inventory/sync/ModuleSyncWatchdogSpec.groovy | 112 +++++++-------------- .../ncmp/api/inventory/sync/SyncUtilsSpec.groovy | 16 --- .../ncmp/api/utils/YangDataConverterSpec.groovy | 42 ++++++++ 7 files changed, 251 insertions(+), 166 deletions(-) create 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/config/embeddedcache/SynchronizationSemaphoresConfigSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/YangDataConverterSpec.groovy (limited to 'cps-ncmp-service/src/test/groovy/org') 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 new file mode 100644 index 000000000..80aa81b04 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationCacheConfigSpec.groovy @@ -0,0 +1,54 @@ +/* + * ============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.config.embeddedcache +import com.hazelcast.core.Hazelcast +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 java.util.concurrent.BlockingQueue + +@SpringBootTest +@ContextConfiguration(classes = [SynchronizationCacheConfig]) +class SynchronizationCacheConfigSpec extends Specification { + + @Autowired + private BlockingQueue moduleSyncWorkQueue + + @Autowired + private Map moduleSyncStartedOnCmHandles + + @Autowired + private Map 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 3 instances' + assert Hazelcast.allHazelcastInstances.size() == 3 + and: 'they have the correct names (in any order)' + assert Hazelcast.allHazelcastInstances.name.containsAll('moduleSyncWorkQueue', 'moduleSyncStartedOnCmHandles', 'dataSyncSemaphores' ) + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationSemaphoresConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationSemaphoresConfigSpec.groovy deleted file mode 100644 index ea84b440f..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/embeddedcache/SynchronizationSemaphoresConfigSpec.groovy +++ /dev/null @@ -1,49 +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.config.embeddedcache - -import com.hazelcast.core.Hazelcast -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 = [SynchronizationSemaphoresConfig]) -class SynchronizationSemaphoresConfigSpec extends Specification { - - @Autowired - private Map moduleSyncSemaphores; - - @Autowired - private Map dataSyncSemaphores; - - def 'Embedded Sync Semaphores'() { - expect: 'system is able to create an instance of ModuleSyncSemaphores' - assert null != moduleSyncSemaphores - and: 'system is able to create an instance of DataSyncSemaphores' - assert null != dataSyncSemaphores - and: 'we have 2 instances' - assert Hazelcast.allHazelcastInstances.size() == 2 - and: 'the names match' - assert Hazelcast.allHazelcastInstances.name == ['moduleSyncSemaphores', 'dataSyncSemaphores'] - } -} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy index 6a2fbe8e7..78da7eb74 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy @@ -24,8 +24,6 @@ import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.inventory.CmHandleState -import org.onap.cps.ncmp.api.inventory.CompositeState import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.exceptions.SchemaSetNotFoundException @@ -34,7 +32,6 @@ import spock.lang.Specification class ModuleSyncServiceSpec extends Specification { - def mockCpsModuleService = Mock(CpsModuleService) def mockDmiModelOperations = Mock(DmiModelOperations) def mockCpsAdminService = Mock(CpsAdminService) @@ -72,38 +69,27 @@ class ModuleSyncServiceSpec extends Specification { } def 'Delete Schema Set for CmHandle' () { - given: 'a CmHandle in the advised state' - def cmHandle = new YangModelCmHandle(id: 'some-cmhandle-id', compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED)) - and: 'the Schema Set exists for the CmHandle' - 1 * mockCpsModuleService.deleteSchemaSet(_ as String, 'some-cmhandle-id', - CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) when: 'delete schema set if exists is called' - objectUnderTest.deleteSchemaSetIfExists(cmHandle) - then: 'there are no exceptions' - noExceptionThrown() + 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: 'a CmHandle in the advised state' - def cmHandle = new YangModelCmHandle(id: 'some-cmhandle-id', compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED)) - and: 'the DB throws an exception because its Schema Set does not exist' - 1 * mockCpsModuleService.deleteSchemaSet(_ as String, 'some-cmhandle-id', - CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) >> { throw new SchemaSetNotFoundException('some-dataspace-name', 'some-cmhandle-id') } + 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(cmHandle) - then: 'there are no exceptions' + 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: 'a CmHandle in the advised state' - def cmHandle = new YangModelCmHandle(id: 'some-cmhandle-id', compositeState: new CompositeState(cmHandleState: CmHandleState.ADVISED)) - and: 'an exception other than SchemaSetNotFoundException is thrown' + given: 'an exception other than SchemaSetNotFoundException is thrown' UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException(); - 1 * mockCpsModuleService.deleteSchemaSet(_ as String, 'some-cmhandle-id', - CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) >> { throw unsupportedOperationException } + 1 * mockCpsModuleService.deleteSchemaSet(*_) >> { throw unsupportedOperationException } when: 'delete schema set if exists is called' - objectUnderTest.deleteSchemaSetIfExists(cmHandle) + 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/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy new file mode 100644 index 000000000..291ba968f --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy @@ -0,0 +1,110 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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.inventory.sync + +import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle +import org.onap.cps.ncmp.api.inventory.CmHandleState +import org.onap.cps.ncmp.api.inventory.CompositeState +import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder +import org.onap.cps.ncmp.api.inventory.InventoryPersistence +import org.onap.cps.ncmp.api.inventory.LockReasonCategory +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class ModuleSyncTasksSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + + def mockSyncUtils = Mock(SyncUtils) + + def mockModuleSyncService = Mock(ModuleSyncService) + + def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) + + def objectUnderTest = new ModuleSyncTasks(mockInventoryPersistence, mockSyncUtils, mockModuleSyncService, mockLcmEventsCmHandleStateHandler) + + def 'Module Sync ADVISED cm handles.'() { + given: 'cm handles in an ADVISED state' + def cmHandle1 = advisedCmHandleAsDataNode('cm-handle-1') + def cmHandle2 = advisedCmHandleAsDataNode('cm-handle-2') + 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]) + 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 -> assertYamgModelCmHandleArgument(args,'cm-handle-1') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args,'cm-handle-2') } + and: 'the state handler is called for the both cm handles' + 2 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.READY) + } + + def 'Module Sync ADVISED cm handle with failure during sync.'() { + given: 'a cm handle in an ADVISED state' + def cmHandle = advisedCmHandleAsDataNode('cm-handle') + 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]) + then: 'update lock reason, details and attempts is invoked' + 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED ,'some exception') + and: 'the state handler is called to update the state to LOCKED' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.LOCKED) + } + + def 'Reset failed CM Handles #scenario.'() { + given: 'cm handles in an locked state' + def lockedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) + .withLockReason(LockReasonCategory.LOCKED_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) + and: 'sync utils retry locked cm handle returns #isReadyForRetry' + mockSyncUtils.isReadyForRetry(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' + expectedNumberOfInvocationsToSaveCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.ADVISED) + where: + scenario | isReadyForRetry || expectedNumberOfInvocationsToSaveCmHandleState + 'retry locked cm handle once' | [true, false] || 1 + 'retry locked cm handle twice' | [true, true] || 2 + 'do not retry locked cm handle' | [false, false] || 0 + } + + def advisedCmHandleAsDataNode(cmHandleId) { + return new DataNode(anchorName:cmHandleId, leaves:['id':cmHandleId, 'cm-handle-state':'ADVISED']) + } + + def assertYamgModelCmHandleArgument(args, expectedCmHandleId) { + { + def yangModelCmHandle = args[0] + assert yangModelCmHandle.id == expectedCmHandleId + } + return true + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy index 81268cbc0..43f492dbd 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -21,98 +21,56 @@ package org.onap.cps.ncmp.api.inventory.sync - -import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle -import org.onap.cps.ncmp.api.inventory.CmHandleState -import org.onap.cps.ncmp.api.inventory.CompositeState -import org.onap.cps.ncmp.api.inventory.InventoryPersistence -import org.onap.cps.ncmp.api.inventory.LockReasonCategory -import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder -import spock.lang.Specification -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap +import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.BlockingQueue +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification class ModuleSyncWatchdogSpec extends Specification { - def mockInventoryPersistence = Mock(InventoryPersistence) - def mockSyncUtils = Mock(SyncUtils) - def mockModuleSyncService = Mock(ModuleSyncService) + def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE - def stubbedMap = Stub(ConcurrentMap) + BlockingQueue moduleSyncWorkQueue = new ArrayBlockingQueue(testQueueCapacity) - def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler) + def moduleSyncStartedOnCmHandles = [:] - def cmHandleState = CmHandleState.ADVISED + def mockModuleSyncTasks = Mock(ModuleSyncTasks) - def objectUnderTest = new ModuleSyncWatchdog(mockInventoryPersistence, mockSyncUtils, mockModuleSyncService, stubbedMap as ConcurrentHashMap, mockLcmEventsCmHandleStateHandler) + def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils, moduleSyncWorkQueue , moduleSyncStartedOnCmHandles, mockModuleSyncTasks) - def 'Schedule a Cm-Handle Sync for ADVISED Cm-Handles'() { - given: 'cm handles in an advised state and a data sync state' - def compositeState1 = new CompositeState(cmHandleState: cmHandleState) - def compositeState2 = new CompositeState(cmHandleState: cmHandleState) - def yangModelCmHandle1 = new YangModelCmHandle(id: 'some-cm-handle', compositeState: compositeState1) - def yangModelCmHandle2 = new YangModelCmHandle(id: 'some-cm-handle-2', compositeState: compositeState2) - and: 'sync utilities return a cm handle twice' - mockSyncUtils.getAdvisedCmHandles() >> [yangModelCmHandle1, yangModelCmHandle2] - when: 'module sync poll is executed' - objectUnderTest.executeAdvisedCmHandlePoll() - then: 'the inventory persistence cm handle returns a composite state for the first cm handle' - 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle') >> compositeState1 - and: 'module sync service deletes schema set of cm handle if it exists' - 1 * mockModuleSyncService.deleteSchemaSetIfExists(yangModelCmHandle1) - and: 'module sync service syncs the first cm handle and creates a schema set' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle1) - then: 'the state handler is called for the first cm handle' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(yangModelCmHandle1, CmHandleState.READY) - and: 'the inventory persistence cm handle returns a composite state for the second cm handle' - mockInventoryPersistence.getCmHandleState('some-cm-handle-2') >> compositeState2 - and: 'module sync service syncs the second cm handle and creates a schema set' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle2) - then: 'the state handler is called for the second cm handle' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(yangModelCmHandle2, CmHandleState.READY) + def 'Module sync #scenario , #numberOfAdvisedCmHandles advised cm handles.'() { + given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles' + mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + when: ' module sync is started' + objectUnderTest.moduleSyncAdvisedCmHandles() + then: 'it performs #expectedNumberOfTaskExecutions tasks' + expectedNumberOfTaskExecutions * mockModuleSyncTasks.performModuleSync(_) + where: + 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 'Schedule a Cm-Handle Sync for ADVISED Cm-Handle with failure'() { - given: 'cm handles in an advised state' - def compositeState = new CompositeState(cmHandleState: cmHandleState) - def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', compositeState: compositeState) - and: 'sync utilities return a cm handle' - mockSyncUtils.getAdvisedCmHandles() >> [yangModelCmHandle] - when: 'module sync poll is executed' - objectUnderTest.executeAdvisedCmHandlePoll() - then: 'the inventory persistence cm handle returns a composite state for the cm handle' - 1 * mockInventoryPersistence.getCmHandleState('some-cm-handle') >> compositeState - and: 'module sync service attempts to sync the cm handle and throws an exception' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } - and: 'update lock reason, details and attempts is invoked' - 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED ,'some exception') - and: 'the state handler is called to update the state to LOCKED' - 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(yangModelCmHandle, CmHandleState.LOCKED) + def 'Reset failed cm handles.'() { + given: 'sync utilities returns failed cm handles' + def failedCmHandles = [new YangModelCmHandle()] + mockSyncUtils.getModuleSyncFailedCmHandles() >> 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 'Schedule a Cm-Handle Sync with condition #scenario '() { - given: 'cm handles in an locked state' - def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) - .withLockReason(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, '').withLastUpdatedTimeNow().build() - def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', compositeState: compositeState) - and: 'sync utilities return a cm handle twice' - mockSyncUtils.getModuleSyncFailedCmHandles() >> [yangModelCmHandle, yangModelCmHandle] - and: 'inventory persistence returns the composite state of the cm handle' - mockInventoryPersistence.getCmHandleState(yangModelCmHandle.getId()) >> compositeState - and: 'sync utils retry locked cm handle returns #isReadyForRetry' - mockSyncUtils.isReadyForRetry(compositeState) >>> isReadyForRetry - when: 'module sync poll is executed' - objectUnderTest.executeLockedCmHandlePoll() - then: 'the first cm handle is updated to state "ADVISED" from "READY"' - expectedNumberOfInvocationsToSaveCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleState(yangModelCmHandle, CmHandleState.ADVISED) - where: - scenario | isReadyForRetry || expectedNumberOfInvocationsToSaveCmHandleState - 'retry locked cm handle once' | [true, false] || 1 - 'retry locked cm handle twice' | [true, true] || 2 - 'do not retry locked cm handle' | [false, false] || 0 + 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/inventory/sync/SyncUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy index 52fb110b3..6ccdcf12d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy @@ -47,8 +47,6 @@ import java.util.stream.Collectors class SyncUtilsSpec extends Specification{ - def mockInventoryPersistence = Mock(InventoryPersistence) - def mockCmHandleQueries = Mock(CmHandleQueries) def mockDmiDataOperations = Mock(DmiDataOperations) @@ -63,28 +61,14 @@ class SyncUtilsSpec extends Specification{ @Shared def dataNode = new DataNode(leaves: ['id': 'cm-handle-123']) - @Shared - def dataNodeAdditionalProperties = new DataNode(leaves: ['name': 'dmiProp1', 'value': 'dmiValue1']) - 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 - and: 'we have some additional (dmi, private) properties' - dataNodeAdditionalProperties.xpath = dataNode.xpath + '/additional-properties[@name="dmiProp1"]' - dataNode.childDataNodes = [dataNodeAdditionalProperties] when: 'get advised cm handles are fetched' def yangModelCmHandles = objectUnderTest.getAdvisedCmHandles() then: 'the returned data node collection is the correct size' yangModelCmHandles.size() == expectedDataNodeSize - and: 'if there is a data node the additional (dmi, private) properties are included' - if (expectedDataNodeSize > 0) { - assert yangModelCmHandles[0].dmiProperties[0].name == 'dmiProp1' - assert yangModelCmHandles[0].dmiProperties[0].value == 'dmiValue1' - } - and: 'yang model collection contains the correct data' - yangModelCmHandles.stream().map(yangModel -> yangModel.id).collect(Collectors.toSet()) == - dataNodeCollection.stream().map(dataNode -> dataNode.leaves.get("id")).collect(Collectors.toSet()) where: 'the following scenarios are used' scenario | dataNodeCollection || expectedCallsToGetYangModelCmHandle | expectedDataNodeSize 'exists' | [dataNode] || 1 | 1 diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/YangDataConverterSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/YangDataConverterSpec.groovy new file mode 100644 index 000000000..20d384fa5 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/YangDataConverterSpec.groovy @@ -0,0 +1,42 @@ +/* + * ============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.utils + +import org.onap.cps.ncmp.api.impl.utils.YangDataConverter +import org.onap.cps.spi.model.DataNode +import spock.lang.Specification + +class YangDataConverterSpec extends Specification{ + + def 'Convert a cm handle data node with private properties.'() { + given: 'a datanode with some additional (dmi, private) properties' + def dataNodeAdditionalProperties = new DataNode(xpath:'/additional-properties[@name="dmiProp1"]', + leaves: ['name': 'dmiProp1', 'value': 'dmiValue1']) + def dataNode = new DataNode(childDataNodes:[dataNodeAdditionalProperties]) + when: 'the dataNode is converted' + def yangModelCmHandle = YangDataConverter.convertCmHandleToYangModel(dataNode,'sample-id') + then: 'the converted object has the correct id' + assert yangModelCmHandle.id == 'sample-id' + and: 'the additional (dmi, private) properties are included' + assert yangModelCmHandle.dmiProperties[0].name == 'dmiProp1' + assert yangModelCmHandle.dmiProperties[0].value == 'dmiValue1' + } +} -- cgit 1.2.3-korg