2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2022-2023 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.ncmp.api.inventory.sync
23 import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.LOCKED_MISBEHAVING
24 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
25 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
26 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
27 import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE
29 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
30 import org.onap.cps.spi.FetchDescendantsOption
31 import org.onap.cps.spi.model.DataNode
32 import org.onap.cps.api.CpsAdminService
33 import org.onap.cps.api.CpsDataService
34 import org.onap.cps.api.CpsModuleService
35 import org.onap.cps.ncmp.api.impl.inventory.sync.ModuleSyncService
36 import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations
37 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
38 import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
39 import org.onap.cps.ncmp.api.impl.inventory.CompositeStateBuilder
40 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
41 import org.onap.cps.spi.CascadeDeleteAllowed
42 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
43 import org.onap.cps.spi.model.ModuleReference
44 import org.onap.cps.utils.JsonObjectMapper
45 import spock.lang.Specification
47 class ModuleSyncServiceSpec extends Specification {
49 def mockCpsModuleService = Mock(CpsModuleService)
50 def mockDmiModelOperations = Mock(DmiModelOperations)
51 def mockCpsAdminService = Mock(CpsAdminService)
52 def mockCmHandleQueries = Mock(CmHandleQueries)
53 def mockCpsDataService = Mock(CpsDataService)
54 def mockJsonObjectMapper = Mock(JsonObjectMapper)
56 def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, mockCpsAdminService,
57 mockCmHandleQueries, mockCpsDataService, mockJsonObjectMapper)
59 def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
61 def 'Sync model for a (new) cm handle with #scenario'() {
63 def ncmpServiceCmHandle = new NcmpServiceCmHandle()
64 ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder().build())
65 def dmiServiceName = 'some service name'
66 ncmpServiceCmHandle.cmHandleId = 'cmHandleId-1'
67 def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle,'')
68 and: 'DMI operations returns some module references'
69 def moduleReferences = [ new ModuleReference('module1','1'), new ModuleReference('module2','2') ]
70 mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences
71 and: 'CPS-Core returns list of existing module resources'
72 mockCpsModuleService.getYangResourceModuleReferences(expectedDataspaceName) >> toModuleReference(existingModuleResourcesInCps)
73 and: 'DMI-Plugin returns resource(s) for "new" module(s)'
74 mockDmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle, [new ModuleReference('module1', '1')]) >> newModuleNameContentToMap
75 when: 'module sync is triggered'
76 mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> toModuleReference(identifiedNewModuleReferences)
77 objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle)
78 then: 'create schema set from module is invoked with correct parameters'
79 1 * mockCpsModuleService.createOrUpgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', newModuleNameContentToMap, moduleReferences)
80 and: 'anchor is created with the correct parameters'
81 1 * mockCpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', 'cmHandleId-1')
82 where: 'the following parameters are used'
83 scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap
84 'one new module' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']] | [module1: 'some yang source']
85 'no add. properties' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']] | [module1: 'some yang source']
86 'no new module' | [['module1' : '1'], ['module2' : '2']] | [] | [:]
89 def 'upgrade model for a existing cm handle'() {
90 given: 'a cm handle that is ready but locked for upgrade'
91 def ncmpServiceCmHandle = new NcmpServiceCmHandle()
92 ncmpServiceCmHandle.setCompositeState(new CompositeStateBuilder()
93 .withLockReason(MODULE_UPGRADE, 'new moduleSetTag: targetModuleSetTag').build())
94 ncmpServiceCmHandle.setCmHandleId('cmHandleId-1')
95 def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle('some service name', '', '', ncmpServiceCmHandle, 'targetModuleSetTag')
96 mockCmHandleQueries.cmHandleHasState('cmHandleId-1', CmHandleState.READY) >> true
97 and: 'the module service returns some module references'
98 def moduleReferences = [new ModuleReference('module1', '1'), new ModuleReference('module2', '2')]
99 mockCpsModuleService.getYangResourcesModuleReferences(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR) >> moduleReferences
100 and: 'a cm handle with the same moduleSetTag can be found in the registry'
101 mockCmHandleQueries.queryNcmpRegistryByCpsPath("//cm-handles[@module-set-tag='targetModuleSetTag']",
102 FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'cmHandleId-1\']', leaves: ['id': 'cmHandleId-1', 'cm-handle-state': 'READY'])]
103 when: 'module upgrade is triggered'
104 objectUnderTest.syncAndCreateOrUpgradeSchemaSetAndAnchor(yangModelCmHandle)
105 then: 'the upgrade is delegated to the module service (with the correct parameters)'
106 1 * mockCpsModuleService.createOrUpgradeSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'cmHandleId-1', Collections.emptyMap(), moduleReferences)
109 def 'Delete Schema Set for CmHandle'() {
110 when: 'delete schema set if exists is called'
111 objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id')
112 then: 'the module service is invoked to delete the correct schema set'
113 1 * mockCpsModuleService.deleteSchemaSet(expectedDataspaceName, 'some-cmhandle-id', CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
116 def 'Delete a non-existing Schema Set for CmHandle' () {
117 given: 'the DB throws an exception because its Schema Set does not exist'
118 mockCpsModuleService.deleteSchemaSet(*_) >> { throw new SchemaSetNotFoundException('some-dataspace-name', 'some-cmhandle-id') }
119 when: 'delete schema set if exists is called'
120 objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id')
121 then: 'the exception from the DB is ignored; there are no exceptions'
125 def 'Delete Schema Set for CmHandle with other exception' () {
126 given: 'an exception other than SchemaSetNotFoundException is thrown'
127 UnsupportedOperationException unsupportedOperationException = new UnsupportedOperationException();
128 1 * mockCpsModuleService.deleteSchemaSet(*_) >> { throw unsupportedOperationException }
129 when: 'delete schema set if exists is called'
130 objectUnderTest.deleteSchemaSetIfExists('some-cmhandle-id')
131 then: 'an exception is thrown'
132 def result = thrown(UnsupportedOperationException)
133 result == unsupportedOperationException
136 def 'Extract module set tag with #scenario'() {
137 expect: 'the module set tag is extracted correctly'
138 assert expectedModuleSetTag == objectUnderTest.extractModuleSetTag(new CompositeStateBuilder().withLockReason(lockReason, 'new moduleSetTag: targetModuleSetTag').build())
139 where: 'the following parameters are used'
140 scenario | lockReason || expectedModuleSetTag
141 'locked reason is null' | null || ''
142 'locked for other reason' | LOCKED_MISBEHAVING || ''
143 'locked for module upgrade' | MODULE_UPGRADE || 'targetModuleSetTag'
146 def toModuleReference(moduleReferenceAsMap) {
147 def moduleReferences = [].withDefault { [:] }
148 moduleReferenceAsMap.forEach(property ->
149 property.forEach((moduleName, revision) -> {
150 moduleReferences.add(new ModuleReference('moduleName' : moduleName, 'revision' : revision))
152 return moduleReferences