2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2023 Nordix Foundation
4 * Modifications Copyright (C) 2021 Pantheon.tech
5 * Modifications Copyright (C) 2021-2022 Bell Canada
6 * Modifications Copyright (C) 2023 TechMahindra Ltd.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.ncmp.api.impl
26 import com.hazelcast.map.IMap
27 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
28 import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler
29 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
30 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
31 import org.onap.cps.ncmp.api.inventory.CmHandleState
32 import org.onap.cps.ncmp.api.inventory.CompositeState
33 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
34 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
35 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
36 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters
37 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
38 import org.onap.cps.ncmp.api.models.ConditionApiProperties
39 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
40 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
41 import org.onap.cps.spi.exceptions.CpsException
42 import org.onap.cps.spi.model.ConditionProperties
43 import spock.lang.Shared
44 import java.util.stream.Collectors
45 import org.onap.cps.utils.JsonObjectMapper
46 import com.fasterxml.jackson.databind.ObjectMapper
47 import org.onap.cps.api.CpsDataService
48 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
49 import org.onap.cps.spi.FetchDescendantsOption
50 import org.onap.cps.spi.model.DataNode
51 import org.springframework.http.HttpStatus
52 import org.springframework.http.ResponseEntity
53 import spock.lang.Specification
55 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
56 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
57 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
58 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
60 class NetworkCmProxyDataServiceImplSpec extends Specification {
62 def mockCpsDataService = Mock(CpsDataService)
63 def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
64 def mockDmiDataOperations = Mock(DmiDataOperations)
65 def nullNetworkCmProxyDataServicePropertyHandler = null
66 def mockInventoryPersistence = Mock(InventoryPersistence)
67 def mockCmHandleQueries = Mock(CmHandleQueries)
68 def mockDmiPluginRegistration = Mock(DmiPluginRegistration)
69 def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
70 def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
71 def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
74 def NO_REQUEST_ID = null
76 def OPTIONS_PARAM = '(a=1,b=2)'
78 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
80 def objectUnderTest = new NetworkCmProxyDataServiceImpl(
81 spiedJsonObjectMapper,
82 mockDmiDataOperations,
83 nullNetworkCmProxyDataServicePropertyHandler,
84 mockInventoryPersistence,
86 mockCpsCmHandlerQueryService,
87 mockLcmEventsCmHandleStateHandler,
89 stubModuleSyncStartedOnCmHandles)
91 def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
93 def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
95 def 'Write resource data for pass-through running from DMI using POST.'() {
96 given: 'cpsDataService returns valid datanode'
97 mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
98 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
99 when: 'write resource data is called'
100 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
101 'testResourceId', CREATE,
102 '{some-json}', 'application/json')
103 then: 'DMI called with correct data'
104 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
105 CREATE, '{some-json}', 'application/json')
106 >> { new ResponseEntity<>(HttpStatus.CREATED) }
109 def 'Get resource data for pass-through operational from DMI.'() {
110 given: 'get data node is called'
111 mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
112 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
113 and: 'get resource data from DMI is called'
114 mockDmiDataOperations.getResourceDataFromDmi(
118 PASSTHROUGH_OPERATIONAL,
120 NO_TOPIC) >> new ResponseEntity<>('dmi-response', HttpStatus.OK)
121 when: 'get resource data operational for cm-handle is called'
122 def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
127 then: 'DMI returns a json response'
128 response == 'dmi-response'
131 def 'Get resource data for pass-through running from DMI.'() {
132 given: 'cpsDataService returns valid data node'
133 mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
134 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
135 and: 'DMI returns valid response and data'
136 mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
141 NO_TOPIC) >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
142 when: 'get resource data is called'
143 def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
148 then: 'get resource data returns expected response'
149 response == '{dmi-response}'
152 def 'Getting Yang Resources.'() {
153 when: 'yang resources is called'
154 objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
155 then: 'CPS module services is invoked for the correct dataspace and cm handle'
156 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
159 def 'Get a cm handle.'() {
160 given: 'the system returns a yang modelled cm handle'
161 def dmiServiceName = 'some service name'
162 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
163 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
164 lastUpdateTime: 'some-timestamp',
165 dataSyncEnabled: false,
166 dataStores: dataStores())
167 def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
168 def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
169 def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
170 dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
171 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
172 when: 'getting cm handle details for a given cm handle id from ncmp service'
173 def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
174 then: 'the result is a ncmpServiceCmHandle'
175 result.class == NcmpServiceCmHandle.class
176 and: 'the cm handle contains the cm handle id'
177 result.cmHandleId == 'some-cm-handle'
178 and: 'the cm handle contains the DMI Properties'
179 result.dmiProperties ==[ Book:'Romance Novel' ]
180 and: 'the cm handle contains the public Properties'
181 result.publicProperties == [ "Public Book":'Public Romance Novel' ]
182 and: 'the cm handle contains the cm handle composite state'
183 result.compositeState == compositeState
187 def 'Get cm handle public properties'() {
188 given: 'a yang modelled cm handle'
189 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
190 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
191 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
192 and: 'the system returns this yang modelled cm handle'
193 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
194 when: 'getting cm handle public properties for a given cm handle id from ncmp service'
195 def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
196 then: 'the result returns the correct data'
197 result == [ 'public prop' : 'some public prop' ]
200 def 'Execute cm handle id search for inventory'() {
201 given: 'a ConditionApiProperties object'
202 def conditionProperties = new ConditionProperties()
203 conditionProperties.conditionName = 'hasAllProperties'
204 conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
205 def conditionServiceProps = new CmHandleQueryServiceParameters()
206 conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
207 and: 'the system returns an set of cmHandle ids'
208 mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
209 when: 'getting cm handle id set for a given dmi property'
210 def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
211 then: 'the result returns the correct 2 elements'
212 assert result.size() == 2
213 assert result.contains('cmHandle1')
214 assert result.contains('cmHandle2')
217 def 'Get cm handle composite state'() {
218 given: 'a yang modelled cm handle'
219 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
220 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
221 lastUpdateTime: 'some-timestamp',
222 dataSyncEnabled: false,
223 dataStores: dataStores())
224 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
225 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
226 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
227 and: 'the system returns this yang modelled cm handle'
228 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
229 when: 'getting cm handle composite state for a given cm handle id from ncmp service'
230 def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
231 then: 'the result returns the correct data'
232 result == compositeState
235 def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
236 given: 'cpsDataService returns valid datanode'
237 mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
238 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
239 when: 'get resource data is called'
240 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
241 'testResourceId', UPDATE,
242 '{some-json}', 'application/json')
243 then: 'DMI called with correct data'
244 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
245 UPDATE, '{some-json}', 'application/json')
246 >> { new ResponseEntity<>(HttpStatus.OK) }
249 def 'Verify modules and create anchor params'() {
250 given: 'dmi plugin registration return created cm handles'
251 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
252 dmiDataPlugin: 'service2')
253 dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
254 mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
255 when: 'parse and create cm handle in dmi registration then sync module'
256 objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(mockDmiPluginRegistration)
257 then: 'system persists the cm handle state'
258 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> {
261 def cmHandleStatePerCmHandle = (args[0] as Map)
262 cmHandleStatePerCmHandle.each {
263 assert (it.key.id == 'test-cm-handle-id'
264 && it.value == CmHandleState.ADVISED)
270 def 'Execute cm handle id search'() {
271 given: 'valid CmHandleQueryApiParameters input'
272 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
273 def conditionApiProperties = new ConditionApiProperties()
274 conditionApiProperties.conditionName = 'hasAllModules'
275 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
276 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
277 and: 'query cm handle method return with a data node list'
278 mockCpsCmHandlerQueryService.queryCmHandleIds(
279 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
280 >> ['cm-handle-id-1']
281 when: 'execute cm handle search is called'
282 def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
283 then: 'result is the same collection as returned by the CPS Data Service'
284 assert result == ['cm-handle-id-1']
287 def 'Getting module definitions.'() {
288 when: 'get module definitions method is called with a valid cm handle ID'
289 objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
290 then: 'CPS module services is invoked once'
291 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
294 def 'Execute cm handle search'() {
295 given: 'valid CmHandleQueryApiParameters input'
296 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
297 def conditionApiProperties = new ConditionApiProperties()
298 conditionApiProperties.conditionName = 'hasAllModules'
299 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
300 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
301 and: 'query cm handle method return with a data node list'
302 mockCpsCmHandlerQueryService.queryCmHandles(
303 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
304 >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
305 when: 'execute cm handle search is called'
306 def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
307 then: 'result is the same collection as returned by the CPS Data Service'
308 assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
311 def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() {
312 given: 'an existing cm handle composite state'
313 def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
314 dataStores: CompositeState.DataStores.builder()
315 .operationalDataStore(CompositeState.Operational.builder()
316 .dataStoreSyncState(initialDataSyncState)
318 and: 'get cm handle state returns the composite state for the given cm handle id'
319 mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
320 when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
321 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
322 then: 'the data sync enabled flag is set to #dataSyncEnabled'
323 compositeState.dataSyncEnabled == dataSyncEnabledFlag
324 and: 'the data store sync state is set to #expectedDataStoreSyncState'
325 compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
326 and: 'the cps data service to delete data nodes is invoked the expected number of times'
327 deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode('NFP-Operational', 'some-cm-handle-id', '/netconf-state', _)
328 and: 'the inventory persistence service to update node leaves is called with the correct values'
329 saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
330 where: 'the following data sync enabled flag is used'
331 scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
332 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1
333 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1
334 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1
335 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0
338 def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
339 given: 'a cm handle composite state'
340 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
341 and: 'get cm handle state returns the composite state for the given cm handle id'
342 mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
343 when: 'set data sync enabled is called with the data sync enabled flag set to true'
344 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
345 then: 'the expected exception is thrown'
347 and: 'the inventory persistence service to update node leaves is not invoked'
348 0 * mockInventoryPersistence.saveCmHandleState(_, _)
351 def 'Get all cm handle IDs by DMI plugin identifier.' () {
352 given: 'cm handle queries service returns cm handles'
353 1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
354 when: 'cm handle Ids are requested with dmi plugin identifier'
355 def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
356 then: 'the result size is correct'
357 assert result.size() == 2
358 and: 'the result returns the correct details'
359 assert result.containsAll('cm-handle-1','cm-handle-2')
363 CompositeState.DataStores.builder()
364 .operationalDataStore(CompositeState.Operational.builder()
365 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
366 .lastSyncTime('some-timestamp').build()).build()