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