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