Merge "Update outdated dependencies"
[cps.git] / cps-ncmp-service / src / test / groovy / org / onap / cps / ncmp / api / impl / NetworkCmProxyDataServiceImplSpec.groovy
1 /*
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
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  *
19  *  SPDX-License-Identifier: Apache-2.0
20  *  ============LICENSE_END=========================================================
21  */
22
23 package org.onap.cps.ncmp.api.impl
24
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
53
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
58
59 class NetworkCmProxyDataServiceImplSpec extends Specification {
60
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>)
71
72     def NO_TOPIC = null
73     def NO_REQUEST_ID = null
74     @Shared
75     def OPTIONS_PARAM = '(a=1,b=2)'
76     @Shared
77     def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
78
79     def objectUnderTest = new NetworkCmProxyDataServiceImpl(
80             spiedJsonObjectMapper,
81             mockDmiDataOperations,
82             nullNetworkCmProxyDataServicePropertyHandler,
83             mockInventoryPersistence,
84             mockCmHandleQueries,
85             mockCpsCmHandlerQueryService,
86             mockLcmEventsCmHandleStateHandler,
87             mockCpsDataService,
88             stubModuleSyncStartedOnCmHandles)
89
90     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
91
92     def dataNode = new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])
93
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) }
106     }
107
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(
114                 'testCmHandle',
115                 'testResourceId',
116                 OPTIONS_PARAM,
117                 PASSTHROUGH_OPERATIONAL,
118                 NO_REQUEST_ID,
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',
122                 'testResourceId',
123                 OPTIONS_PARAM,
124                 NO_TOPIC,
125                 NO_REQUEST_ID)
126         then: 'DMI returns a json response'
127             response == 'dmi-response'
128     }
129
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',
136                 'testResourceId',
137                 OPTIONS_PARAM,
138                 PASSTHROUGH_RUNNING,
139                 NO_REQUEST_ID,
140                 NO_TOPIC) >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
141         when: 'get resource data is called'
142             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
143                 'testResourceId',
144                 OPTIONS_PARAM,
145                 NO_TOPIC,
146                 NO_REQUEST_ID)
147         then: 'get resource data returns expected response'
148             response == '{dmi-response}'
149     }
150
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')
156     }
157
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
183
184     }
185
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' ]
197     }
198
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')
214     }
215
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
232     }
233
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) }
246     }
247
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(_) >> {
258                 args ->
259                     {
260                         def cmHandleStatePerCmHandle = (args[0] as Map)
261                         cmHandleStatePerCmHandle.each {
262                             assert (it.key.id == 'test-cm-handle-id'
263                                     && it.value == CmHandleState.ADVISED)
264                         }
265                     }
266             }
267     }
268
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
284     }
285
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')
291     }
292
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
308     }
309
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)
316                         .build()).build())
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
335     }
336
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'
345             thrown(CpsException)
346         and: 'the inventory persistence service to update node leaves is not invoked'
347             0 * mockInventoryPersistence.saveCmHandleState(_, _)
348     }
349
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')
361     }
362
363     def dataStores() {
364         CompositeState.DataStores.builder()
365             .operationalDataStore(CompositeState.Operational.builder()
366                 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
367                 .lastSyncTime('some-timestamp').build()).build()
368     }
369 }