From 884a5a0e8bf08cc47e0e0dbd7e20558212ca82b1 Mon Sep 17 00:00:00 2001 From: lukegleeson Date: Mon, 27 Jun 2022 16:26:21 +0100 Subject: Allow Module Re-Sync Allows the Resync of an already synced CmHandle Currently: Advised -(create schemaset)-> Ready -(manual write)-> Advised -> Locked as schemaset for cmhandle already exists With this: Advised -(create schemaset)-> Ready -(manual write)-> Advised -(delete schemaset, create schemaset)-> Ready Included some logging Renamed ModuleSyncSpec -> ModuleSyncWatchdogSpec to match class Issue-ID: CPS-1045 Signed-off-by: lukegleeson Change-Id: I408fbea698b7926dbf5d0cddc74acf1b00235b1f --- .../ncmp/api/inventory/sync/ModuleSyncService.java | 19 ++++ .../api/inventory/sync/ModuleSyncWatchdog.java | 1 + .../inventory/sync/ModuleSyncServiceSpec.groovy | 44 +++++++- .../ncmp/api/inventory/sync/ModuleSyncSpec.groovy | 114 -------------------- .../inventory/sync/ModuleSyncWatchdogSpec.groovy | 116 +++++++++++++++++++++ 5 files changed, 179 insertions(+), 115 deletions(-) delete mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy create mode 100644 cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy (limited to 'cps-ncmp-service/src') diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java index 58e2bf345..c574aa61d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java @@ -32,6 +32,8 @@ 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.spi.CascadeDeleteAllowed; +import org.onap.cps.spi.exceptions.SchemaSetNotFoundException; import org.onap.cps.spi.model.ModuleReference; import org.springframework.stereotype.Service; @@ -83,4 +85,21 @@ public class ModuleSyncService { schemaSetAndAnchorName); } + /** + * Deletes the SchemaSet for provided cmHandle if the SchemaSet Exists. + * + * @param yangModelCmHandle the yang model of cm handle. + */ + public void deleteSchemaSetIfExists(final YangModelCmHandle yangModelCmHandle) { + final String schemaSetAndAnchorName = yangModelCmHandle.getId(); + try { + cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetAndAnchorName, + CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED); + log.debug("SchemaSet for {} has been deleted. Ready to be recreated.", schemaSetAndAnchorName); + } catch (final SchemaSetNotFoundException e) { + log.debug("No SchemaSet for {}. Assuming CmHandle has not been previously Module Synced.", + schemaSetAndAnchorName); + } + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java index 402f9f6b4..9383ac1ef 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java @@ -53,6 +53,7 @@ public class ModuleSyncWatchdog { final String cmHandleId = advisedCmHandle.getId(); final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId); try { + moduleSyncService.deleteSchemaSetIfExists(advisedCmHandle); moduleSyncService.syncAndCreateSchemaSetAndAnchor(advisedCmHandle); compositeState.setCmHandleState(CmHandleState.READY); } catch (final Exception e) { 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 f93b3a73e..6a2fbe8e7 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 @@ -1,5 +1,5 @@ /* - * ============LICENSE_START======================================================= + * ============LICENSE_START======================================================= * Copyright (C) 2022 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,11 @@ 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 import org.onap.cps.spi.model.ModuleReference import spock.lang.Specification @@ -67,6 +71,44 @@ class ModuleSyncServiceSpec extends Specification { 'no new module' | [['module1' : '1'], ['module2' : '2']] | [] | [:] | [new ModuleReference(moduleName:'module1',revision:'1'), new ModuleReference(moduleName:'module2',revision:'2')] } + 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() + } + + 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') } + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists(cmHandle) + then: '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' + UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException(); + 1 * mockCpsModuleService.deleteSchemaSet(_ as String, 'some-cmhandle-id', + CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED) >> { throw unsupportedOperationException } + when: 'delete schema set if exists is called' + objectUnderTest.deleteSchemaSetIfExists(cmHandle) + then: 'an exception is thrown' + def result = thrown(UnsupportedOperationException) + result == unsupportedOperationException + } + def toModuleReference(moduleReferenceAsMap) { def moduleReferences = [].withDefault { [:] } moduleReferenceAsMap.forEach(property -> diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy deleted file mode 100644 index 0f89a428b..000000000 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncSpec.groovy +++ /dev/null @@ -1,114 +0,0 @@ -/* - * ============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.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 - -class ModuleSyncSpec extends Specification { - - def mockInventoryPersistence = Mock(InventoryPersistence) - - def mockSyncUtils = Mock(SyncUtils) - - def mockModuleSyncService = Mock(ModuleSyncService) - - def cmHandleState = CmHandleState.ADVISED - - def objectUnderTest = new ModuleSyncWatchdog(mockInventoryPersistence, mockSyncUtils, mockModuleSyncService) - - def 'Schedule a Cm-Handle Sync for ADVISED Cm-Handles'() { - given: 'cm handles in an advised 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.getAnAdvisedCmHandle() >>> [yangModelCmHandle1, yangModelCmHandle2, null] - 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 syncs the first cm handle and creates a schema set' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle1) - and: 'the composite state cm handle state is now READY' - assert compositeState1.getCmHandleState() == CmHandleState.READY - and: 'the first cm handle state is updated' - 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle', compositeState1) - then: '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) - and: 'the composite state cm handle state is now READY' - assert compositeState2.getCmHandleState() == CmHandleState.READY - and: 'the second cm handle state is updated' - 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle-2', compositeState2) - } - - 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.getAnAdvisedCmHandle() >>> [yangModelCmHandle, null] - 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: 'the composite state cm handle state is now LOCKED' - assert compositeState.getCmHandleState() == CmHandleState.LOCKED - and: 'update lock reason, details and attempts is invoked' - 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.LOCKED_MISBEHAVING ,'some exception') - and: 'the cm handle state is updated' - 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle', compositeState) - - } - - def 'Schedule a Cm-Handle Sync for LOCKED with reason LOCKED_MISBEHAVING Cm-Handles with #scenario'() { - given: 'cm handles in an locked state' - def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) - .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, '').withLastUpdatedTimeNow().build() - def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', compositeState: compositeState) - and: 'sync utilities return a cm handle twice' - mockSyncUtils.getLockedMisbehavingYangModelCmHandles() >> [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 * mockInventoryPersistence.saveCmHandleState(yangModelCmHandle.id, compositeState) - 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 - } -} 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 new file mode 100644 index 000000000..b7eb133bf --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -0,0 +1,116 @@ +/* + * ============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.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 + +class ModuleSyncWatchdogSpec extends Specification { + + def mockInventoryPersistence = Mock(InventoryPersistence) + + def mockSyncUtils = Mock(SyncUtils) + + def mockModuleSyncService = Mock(ModuleSyncService) + + def cmHandleState = CmHandleState.ADVISED + + def objectUnderTest = new ModuleSyncWatchdog(mockInventoryPersistence, mockSyncUtils, mockModuleSyncService) + + def 'Schedule a Cm-Handle Sync for ADVISED Cm-Handles'() { + given: 'cm handles in an advised 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.getAnAdvisedCmHandle() >>> [yangModelCmHandle1, yangModelCmHandle2, null] + 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) + and: 'the composite state cm handle state is now READY' + assert compositeState1.getCmHandleState() == CmHandleState.READY + and: 'the first cm handle state is updated' + 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle', compositeState1) + then: '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) + and: 'the composite state cm handle state is now READY' + assert compositeState2.getCmHandleState() == CmHandleState.READY + and: 'the second cm handle state is updated' + 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle-2', compositeState2) + } + + 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.getAnAdvisedCmHandle() >>> [yangModelCmHandle, null] + 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: 'the composite state cm handle state is now LOCKED' + assert compositeState.getCmHandleState() == CmHandleState.LOCKED + and: 'update lock reason, details and attempts is invoked' + 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(compositeState, LockReasonCategory.LOCKED_MISBEHAVING ,'some exception') + and: 'the cm handle state is updated' + 1 * mockInventoryPersistence.saveCmHandleState('some-cm-handle', compositeState) + + } + + def 'Schedule a Cm-Handle Sync for LOCKED with reason LOCKED_MISBEHAVING Cm-Handles with #scenario'() { + given: 'cm handles in an locked state' + def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) + .withLockReason(LockReasonCategory.LOCKED_MISBEHAVING, '').withLastUpdatedTimeNow().build() + def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', compositeState: compositeState) + and: 'sync utilities return a cm handle twice' + mockSyncUtils.getLockedMisbehavingYangModelCmHandles() >> [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 * mockInventoryPersistence.saveCmHandleState(yangModelCmHandle.id, compositeState) + 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 + } +} -- cgit 1.2.3-korg