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
11 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.cps.ncmp.api.impl
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.DataValidationException
38 import org.onap.cps.spi.model.CmHandleQueryServiceParameters
39 import spock.lang.Shared
41 import java.util.stream.Collectors
43 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
44 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
45 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
46 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
48 import org.onap.cps.utils.JsonObjectMapper
49 import com.fasterxml.jackson.databind.ObjectMapper
50 import org.onap.cps.api.CpsDataService
51 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
52 import org.onap.cps.spi.FetchDescendantsOption
53 import org.onap.cps.spi.model.DataNode
54 import org.springframework.http.HttpStatus
55 import org.springframework.http.ResponseEntity
56 import spock.lang.Specification
58 class NetworkCmProxyDataServiceImplSpec extends Specification {
60 def mockCpsDataService = Mock(CpsDataService)
61 def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
62 def mockDmiDataOperations = Mock(DmiDataOperations)
63 def nullNetworkCmProxyDataServicePropertyHandler = null
64 def mockInventoryPersistence = Mock(InventoryPersistence)
65 def mockDmiPluginRegistration = Mock(DmiPluginRegistration)
66 def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandlerQueryService)
67 def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
70 def NO_REQUEST_ID = null
72 def OPTIONS_PARAM = '(a=1,b=2)'
74 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
76 def objectUnderTest = new NetworkCmProxyDataServiceImpl(
77 spiedJsonObjectMapper,
78 mockDmiDataOperations,
79 nullNetworkCmProxyDataServicePropertyHandler,
80 mockInventoryPersistence,
81 mockCpsCmHandlerQueryService,
82 mockLcmEventsCmHandleStateHandler)
84 def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
86 def dataNode = new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])
88 def 'Write resource data for pass-through running from DMI using POST.'() {
89 given: 'cpsDataService returns valid datanode'
90 mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
91 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
92 when: 'write resource data is called'
93 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
94 'testResourceId', CREATE,
95 '{some-json}', 'application/json')
96 then: 'DMI called with correct data'
97 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
98 CREATE, '{some-json}', 'application/json')
99 >> { new ResponseEntity<>(HttpStatus.CREATED) }
102 def 'Write resource data for pass-through running from DMI using an invalid id.'() {
103 when: 'write resource data is called'
104 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('invalid cm handle name',
105 'testResourceId', CREATE,
106 '{some-json}', 'application/json')
107 then: 'exception is thrown'
108 thrown(DataValidationException.class)
109 and: 'DMI is not invoked'
110 0 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi(_, _, _, _, _)
113 def 'Get resource data for pass-through operational from DMI.'() {
114 given: 'get data node is called'
115 mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
116 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
117 and: 'get resource data from DMI is called'
118 mockDmiDataOperations.getResourceDataFromDmi(
122 PASSTHROUGH_OPERATIONAL,
124 NO_TOPIC) >> new ResponseEntity<>('dmi-response', HttpStatus.OK)
125 when: 'get resource data operational for cm-handle is called'
126 def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
131 then: 'DMI returns a json response'
132 response == 'dmi-response'
135 def 'Get resource data for pass-through running from DMI.'() {
136 given: 'cpsDataService returns valid data node'
137 mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
138 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
139 and: 'DMI returns valid response and data'
140 mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
145 NO_TOPIC) >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
146 when: 'get resource data is called'
147 def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
152 then: 'get resource data returns expected response'
153 response == '{dmi-response}'
156 def 'Getting Yang Resources.'() {
157 when: 'yang resources is called'
158 objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
159 then: 'CPS module services is invoked for the correct dataspace and cm handle'
160 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
163 def 'Getting Yang Resources with an invalid #scenario.'() {
164 when: 'yang resources is called'
165 objectUnderTest.getYangResourcesModuleReferences('invalid cm handle with spaces')
166 then: 'a data validation exception is thrown'
167 thrown(DataValidationException)
168 and: 'CPS module services is not invoked'
169 0 * mockInventoryPersistence.getYangResourcesModuleReferences(*_)
172 def 'Get a cm handle.'() {
173 given: 'the system returns a yang modelled cm handle'
174 def dmiServiceName = 'some service name'
175 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
176 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
177 lastUpdateTime: 'some-timestamp',
178 dataSyncEnabled: false,
179 dataStores: dataStores())
180 def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
181 def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
182 def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
183 dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
184 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
185 when: 'getting cm handle details for a given cm handle id from ncmp service'
186 def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
187 then: 'the result is a ncmpServiceCmHandle'
188 result.class == NcmpServiceCmHandle.class
189 and: 'the cm handle contains the cm handle id'
190 result.cmHandleId == 'some-cm-handle'
191 and: 'the cm handle contains the DMI Properties'
192 result.dmiProperties ==[ Book:'Romance Novel' ]
193 and: 'the cm handle contains the public Properties'
194 result.publicProperties == [ "Public Book":'Public Romance Novel' ]
195 and: 'the cm handle contains the cm handle composite state'
196 result.compositeState == compositeState
200 def 'Get a cm handle with an invalid id.'() {
201 when: 'getting cm handle details for a given cm handle id with an invalid name'
202 objectUnderTest.getNcmpServiceCmHandle('invalid cm handle with spaces')
203 then: 'an exception is thrown'
204 thrown(DataValidationException)
205 and: 'the yang model cm handle retriever is not invoked'
206 0 * mockInventoryPersistence.getYangModelCmHandle(*_)
209 def 'Get cm handle public properties'() {
210 given: 'a yang modelled cm handle'
211 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
212 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
213 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
214 and: 'the system returns this yang modelled cm handle'
215 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
216 when: 'getting cm handle public properties for a given cm handle id from ncmp service'
217 def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
218 then: 'the result returns the correct data'
219 result == [ 'public prop' : 'some public prop' ]
222 def 'Get cm handle public properties with an invalid id.'() {
223 when: 'getting cm handle public properties for a given cm handle id with an invalid name'
224 objectUnderTest.getCmHandlePublicProperties('invalid cm handle with spaces')
225 then: 'an exception is thrown'
226 thrown(DataValidationException)
227 and: 'the yang model cm handle retriever is not invoked'
228 0 * mockInventoryPersistence.getYangModelCmHandle(*_)
231 def 'Get cm handle composite state'() {
232 given: 'a yang modelled cm handle'
233 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
234 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.LOCKED_MODULE_SYNC_FAILED).details("lock details").build(),
235 lastUpdateTime: 'some-timestamp',
236 dataSyncEnabled: false,
237 dataStores: dataStores())
238 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
239 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
240 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
241 and: 'the system returns this yang modelled cm handle'
242 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
243 when: 'getting cm handle composite state for a given cm handle id from ncmp service'
244 def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
245 then: 'the result returns the correct data'
246 result == compositeState
249 def 'Get cm handle composite state with an invalid id.'() {
250 when: 'getting cm handle composite state for a given cm handle id with an invalid name'
251 objectUnderTest.getCmHandleCompositeState('invalid cm handle with spaces')
252 then: 'an exception is thrown'
253 thrown(DataValidationException)
254 and: 'the yang model cm handle retriever is not invoked'
255 0 * mockInventoryPersistence.getYangModelCmHandle(_)
258 def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
259 given: 'cpsDataService returns valid datanode'
260 mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
261 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
262 when: 'get resource data is called'
263 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
264 'testResourceId', UPDATE,
265 '{some-json}', 'application/json')
266 then: 'DMI called with correct data'
267 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
268 UPDATE, '{some-json}', 'application/json')
269 >> { new ResponseEntity<>(HttpStatus.OK) }
272 def 'Verify modules and create anchor params'() {
273 given: 'dmi plugin registration return created cm handles'
274 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
275 dmiDataPlugin: 'service2')
276 dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
277 mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
278 when: 'parse and create cm handle in dmi registration then sync module'
279 objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(mockDmiPluginRegistration)
280 then: 'system persists the cm handle state'
281 1 * mockInventoryPersistence.saveCmHandle(_) >> {
283 def result = (args[0] as YangModelCmHandle)
284 assert result.id == 'test-cm-handle-id'
285 assert result.compositeState.cmHandleState == CmHandleState.ADVISED
290 def 'Execute cm handle id search'() {
291 given: 'valid CmHandleQueryApiParameters input'
292 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
293 def conditionApiProperties = new ConditionApiProperties()
294 conditionApiProperties.conditionName = 'hasAllModules'
295 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
296 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
297 and: 'query cm handle method return with a data node list'
298 mockCpsCmHandlerQueryService.queryCmHandleIds(
299 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
300 >> ['cm-handle-id-1']
301 when: 'execute cm handle search is called'
302 def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
303 then: 'result is the same collection as returned by the CPS Data Service'
304 assert result == ['cm-handle-id-1'] as Set
307 def 'Getting module definitions.'() {
308 when: 'get module definitions method is called with a valid cm handle ID'
309 objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
310 then: 'CPS module services is invoked once'
311 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
316 CompositeState.DataStores.builder()
317 .operationalDataStore(CompositeState.Operational.builder()
318 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
319 .lastSyncTime('some-timestamp').build()).build()
322 def 'Execute cm handle search'() {
323 given: 'valid CmHandleQueryApiParameters input'
324 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
325 def conditionApiProperties = new ConditionApiProperties()
326 conditionApiProperties.conditionName = 'hasAllModules'
327 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
328 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
329 and: 'query cm handle method return with a data node list'
330 mockCpsCmHandlerQueryService.queryCmHandles(
331 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
332 >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
333 when: 'execute cm handle search is called'
334 def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
335 then: 'result is the same collection as returned by the CPS Data Service'
336 assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set