3d8e9cb2e8175bfc20df02bf320b0b47c788dd5f
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyDataServiceImplSpec.groovy
1 /*
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
11  *
12  *        http://www.apache.org/licenses/LICENSE-2.0
13  *
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.
19  *
20  *  SPDX-License-Identifier: Apache-2.0
21  *  ============LICENSE_END=========================================================
22  */
23
24 package org.onap.cps.ncmp.api.impl
25
26 import com.hazelcast.map.IMap
27 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
28 import org.onap.cps.ncmp.api.impl.events.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
54
55 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
56 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
57 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
58 import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
59
60 class NetworkCmProxyDataServiceImplSpec extends Specification {
61
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>)
72
73     def NO_TOPIC = null
74     def NO_REQUEST_ID = null
75     @Shared
76     def OPTIONS_PARAM = '(a=1,b=2)'
77     @Shared
78     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
79
80     def objectUnderTest = new NetworkCmProxyDataServiceImpl(
81             spiedJsonObjectMapper,
82             mockDmiDataOperations,
83             nullNetworkCmProxyDataServicePropertyHandler,
84             mockInventoryPersistence,
85             mockCmHandleQueries,
86             mockCpsCmHandlerQueryService,
87             mockLcmEventsCmHandleStateHandler,
88             mockCpsDataService,
89             stubModuleSyncStartedOnCmHandles)
90
91     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
92
93     def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
94
95     def 'Write resource data for pass-through running from DMI using POST.'() {
96         given: 'cpsDataService returns valid datanode'
97             mockDataNode()
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) }
106     }
107
108     def 'Get resource data for pass-through operational from DMI.'() {
109         given: 'get data node is called'
110             mockDataNode()
111         and: 'get resource data from DMI is called'
112             mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName,'testCmHandle',
113                     'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
114                     new ResponseEntity<>('dmi-response', HttpStatus.OK)
115         when: 'get resource data operational for cm-handle is called'
116             def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle',
117                     'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
118         then: 'DMI returns a json response'
119             response == 'dmi-response'
120     }
121
122     def 'Get resource data for pass-through running from DMI.'() {
123         given: 'cpsDataService returns valid data node'
124             mockDataNode()
125         and: 'DMI returns valid response and data'
126             mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
127                     'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
128                     new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
129         when: 'get resource data is called'
130             def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle',
131                     'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
132         then: 'get resource data returns expected response'
133             response == '{dmi-response}'
134     }
135
136     def 'Get bulk resource data for #datastoreName from DMI.'() {
137         given: 'cpsDataService returns valid data node'
138             mockDataNode()
139         and: 'DMI returns valid response and data'
140             mockDmiDataOperations.getResourceDataFromDmi(datastoreName, ['testCmHandle'],
141                     'testResourceId', OPTIONS_PARAM,'some topic','requestId') >>
142                     new ResponseEntity<>('{dmi-bulk-response}', HttpStatus.OK)
143         when: 'get batch resource data is called'
144             def response = objectUnderTest.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'],
145                     'testResourceId',
146                     OPTIONS_PARAM,
147                     'some topic',
148                     'requestId')
149         then: 'get bulk resource data returns expected response'
150             response == '{dmi-bulk-response}'
151         where: 'the following data stores are used'
152             datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
153     }
154
155     def 'Getting Yang Resources.'() {
156         when: 'yang resources is called'
157             objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
158         then: 'CPS module services is invoked for the correct dataspace and cm handle'
159             1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
160     }
161
162     def 'Get a cm handle.'() {
163         given: 'the system returns a yang modelled cm handle'
164             def dmiServiceName = 'some service name'
165             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
166                 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
167                 lastUpdateTime: 'some-timestamp',
168                 dataSyncEnabled: false,
169                 dataStores: dataStores())
170             def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
171             def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
172             def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
173                 dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
174             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
175         when: 'getting cm handle details for a given cm handle id from ncmp service'
176             def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
177         then: 'the result is a ncmpServiceCmHandle'
178             result.class == NcmpServiceCmHandle.class
179         and: 'the cm handle contains the cm handle id'
180             result.cmHandleId == 'some-cm-handle'
181         and: 'the cm handle contains the DMI Properties'
182             result.dmiProperties ==[ Book:'Romance Novel' ]
183         and: 'the cm handle contains the public Properties'
184             result.publicProperties == [ "Public Book":'Public Romance Novel' ]
185         and: 'the cm handle contains the cm handle composite state'
186             result.compositeState == compositeState
187
188     }
189
190     def 'Get cm handle public properties'() {
191         given: 'a yang modelled cm handle'
192             def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
193             def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
194             def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
195         and: 'the system returns this yang modelled cm handle'
196             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
197         when: 'getting cm handle public properties for a given cm handle id from ncmp service'
198             def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
199         then: 'the result returns the correct data'
200             result == [ 'public prop' : 'some public prop' ]
201     }
202
203     def 'Execute cm handle id search for inventory'() {
204         given: 'a ConditionApiProperties object'
205             def conditionProperties = new ConditionProperties()
206             conditionProperties.conditionName = 'hasAllProperties'
207             conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
208             def conditionServiceProps = new CmHandleQueryServiceParameters()
209             conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
210         and: 'the system returns an set of cmHandle ids'
211             mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
212         when: 'getting cm handle id set for a given dmi property'
213             def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
214         then: 'the result returns the correct 2 elements'
215             assert result.size() == 2
216             assert result.contains('cmHandle1')
217             assert result.contains('cmHandle2')
218     }
219
220     def 'Get cm handle composite state'() {
221         given: 'a yang modelled cm handle'
222             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
223                 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
224                 lastUpdateTime: 'some-timestamp',
225                 dataSyncEnabled: false,
226                 dataStores: dataStores())
227             def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
228             def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
229             def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
230         and: 'the system returns this yang modelled cm handle'
231             1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
232         when: 'getting cm handle composite state for a given cm handle id from ncmp service'
233             def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
234         then: 'the result returns the correct data'
235             result == compositeState
236     }
237
238     def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
239         given: 'cpsDataService returns valid datanode'
240             mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
241                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
242         when: 'get resource data is called'
243             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
244                 'testResourceId', UPDATE,
245                 '{some-json}', 'application/json')
246         then: 'DMI called with correct data'
247             1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
248                     UPDATE, '{some-json}', 'application/json')
249                 >> { new ResponseEntity<>(HttpStatus.OK) }
250     }
251
252     def 'Verify modules and create anchor params'() {
253         given: 'dmi plugin registration return created cm handles'
254             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
255                     dmiDataPlugin: 'service2')
256             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
257             mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
258         when: 'parse and create cm handle in dmi registration then sync module'
259             objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(mockDmiPluginRegistration)
260         then: 'system persists the cm handle state'
261             1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> {
262                 args ->
263                     {
264                         def cmHandleStatePerCmHandle = (args[0] as Map)
265                         cmHandleStatePerCmHandle.each {
266                             assert (it.key.id == 'test-cm-handle-id'
267                                     && it.value == CmHandleState.ADVISED)
268                         }
269                     }
270             }
271     }
272
273     def 'Execute cm handle id search'() {
274         given: 'valid CmHandleQueryApiParameters input'
275             def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
276             def conditionApiProperties = new ConditionApiProperties()
277             conditionApiProperties.conditionName = 'hasAllModules'
278             conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
279             cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
280         and: 'query cm handle method return with a data node list'
281             mockCpsCmHandlerQueryService.queryCmHandleIds(
282                     spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
283                     >> ['cm-handle-id-1']
284         when: 'execute cm handle search is called'
285             def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
286         then: 'result is the same collection as returned by the CPS Data Service'
287             assert result == ['cm-handle-id-1']
288     }
289
290     def 'Getting module definitions.'() {
291         when: 'get module definitions method is called with a valid cm handle ID'
292             objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
293         then: 'CPS module services is invoked once'
294             1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
295     }
296
297     def 'Execute cm handle search'() {
298         given: 'valid CmHandleQueryApiParameters input'
299             def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
300             def conditionApiProperties = new ConditionApiProperties()
301             conditionApiProperties.conditionName = 'hasAllModules'
302             conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
303             cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
304         and: 'query cm handle method return with a data node list'
305             mockCpsCmHandlerQueryService.queryCmHandles(
306                     spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
307                     >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
308         when: 'execute cm handle search is called'
309             def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
310         then: 'result is the same collection as returned by the CPS Data Service'
311             assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
312     }
313
314     def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is  #scenario'() {
315         given: 'an existing cm handle composite state'
316             def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
317                 dataStores: CompositeState.DataStores.builder()
318                     .operationalDataStore(CompositeState.Operational.builder()
319                         .dataStoreSyncState(initialDataSyncState)
320                         .build()).build())
321         and: 'get cm handle state returns the composite state for the given cm handle id'
322             mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
323         when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
324             objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
325         then: 'the data sync enabled flag is set to #dataSyncEnabled'
326             compositeState.dataSyncEnabled == dataSyncEnabledFlag
327         and: 'the data store sync state is set to #expectedDataStoreSyncState'
328             compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
329         and: 'the cps data service to delete data nodes is invoked the expected number of times'
330             deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode('NFP-Operational', 'some-cm-handle-id', '/netconf-state', _)
331         and: 'the inventory persistence service to update node leaves is called with the correct values'
332             saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
333         where: 'the following data sync enabled flag is used'
334             scenario                                              | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState               || expectedDataStoreSyncState         | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
335             'enabled'                                             | true                | false                      | DataStoreSyncState.NONE_REQUESTED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 1
336             'disabled'                                            | false               | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.NONE_REQUESTED  | 0                                        | 1
337             'disabled where sync-state is currently SYNCHRONIZED' | false               | true                       | DataStoreSyncState.SYNCHRONIZED    || DataStoreSyncState.NONE_REQUESTED  | 1                                        | 1
338             'is set to existing flag state'                       | true                | true                       | DataStoreSyncState.UNSYNCHRONIZED  || DataStoreSyncState.UNSYNCHRONIZED  | 0                                        | 0
339     }
340
341     def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
342         given: 'a cm handle composite state'
343             def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
344         and: 'get cm handle state returns the composite state for the given cm handle id'
345             mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
346         when: 'set data sync enabled is called with the data sync enabled flag set to true'
347             objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
348         then: 'the expected exception is thrown'
349             thrown(CpsException)
350         and: 'the inventory persistence service to update node leaves is not invoked'
351             0 * mockInventoryPersistence.saveCmHandleState(_, _)
352     }
353
354     def 'Get all cm handle IDs by DMI plugin identifier.' () {
355         given: 'cm handle queries service returns cm handles'
356             1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
357         when: 'cm handle Ids are requested with dmi plugin identifier'
358             def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
359         then: 'the result size is correct'
360             assert result.size() == 2
361         and: 'the result returns the correct details'
362             assert result.containsAll('cm-handle-1','cm-handle-2')
363     }
364
365     def dataStores() {
366         CompositeState.DataStores.builder()
367             .operationalDataStore(CompositeState.Operational.builder()
368                 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
369                 .lastSyncTime('some-timestamp').build()).build()
370     }
371
372     def mockDataNode() {
373         mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
374                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
375     }
376 }