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