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