2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2021-2024 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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.cps.ncmp.api.impl
26 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME
27 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME
28 import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR
29 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
30 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
31 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
32 import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
33 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
35 import org.onap.cps.ncmp.api.impl.utils.AlternateIdChecker
36 import com.hazelcast.map.IMap
37 import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
38 import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler
39 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
40 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevelManager
41 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
42 import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries
43 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
44 import org.onap.cps.ncmp.api.impl.inventory.CompositeState
45 import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistence
46 import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
47 import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
48 import org.onap.cps.ncmp.api.models.DataOperationDefinition
49 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters
50 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
51 import org.onap.cps.ncmp.api.models.ConditionApiProperties
52 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
53 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
54 import org.onap.cps.ncmp.api.models.DataOperationRequest
55 import org.onap.cps.spi.exceptions.CpsException
56 import org.onap.cps.spi.model.ConditionProperties
58 import java.util.stream.Collectors
59 import org.onap.cps.utils.JsonObjectMapper
60 import com.fasterxml.jackson.databind.ObjectMapper
61 import org.onap.cps.api.CpsDataService
62 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
63 import org.onap.cps.spi.FetchDescendantsOption
64 import org.onap.cps.spi.model.DataNode
65 import org.springframework.http.HttpStatus
66 import org.springframework.http.ResponseEntity
67 import spock.lang.Specification
69 class NetworkCmProxyDataServiceImplSpec extends Specification {
71 def mockCpsDataService = Mock(CpsDataService)
72 def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
73 def mockDmiDataOperations = Mock(DmiDataOperations)
74 def nullNetworkCmProxyDataServicePropertyHandler = null
75 def mockInventoryPersistence = Mock(InventoryPersistence)
76 def mockCmHandleQueries = Mock(CmHandleQueries)
77 def mockDmiPluginRegistration = Mock(DmiPluginRegistration)
78 def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
79 def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
80 def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
81 def stubTrustLevelPerDmiPlugin = Stub(Map<String, TrustLevel>)
82 def mockTrustLevelManager = Mock(TrustLevelManager)
83 def mockAlternateIdChecker = Mock(AlternateIdChecker)
86 def NO_REQUEST_ID = null
87 def OPTIONS_PARAM = '(a=1,b=2)'
88 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
90 def objectUnderTest = new NetworkCmProxyDataServiceImpl(
91 spiedJsonObjectMapper,
92 mockDmiDataOperations,
93 nullNetworkCmProxyDataServicePropertyHandler,
94 mockInventoryPersistence,
96 mockCpsCmHandlerQueryService,
97 mockLcmEventsCmHandleStateHandler,
99 stubModuleSyncStartedOnCmHandles,
100 stubTrustLevelPerDmiPlugin,
101 mockTrustLevelManager,
102 mockAlternateIdChecker)
104 def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
106 def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
108 def 'Write resource data for pass-through running from DMI using POST.'() {
109 given: 'cpsDataService returns valid datanode'
111 when: 'write resource data is called'
112 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
113 'testResourceId', CREATE,
114 '{some-json}', 'application/json')
115 then: 'DMI called with correct data'
116 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
117 CREATE, '{some-json}', 'application/json')
118 >> { new ResponseEntity<>(HttpStatus.CREATED) }
121 def 'Get resource data for pass-through operational from DMI.'() {
122 given: 'cpsDataService returns valid data node'
124 and: 'get resource data from DMI is called'
125 mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName,'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
126 new ResponseEntity<>('dmi-response', HttpStatus.OK)
127 when: 'get resource data operational for cm-handle is called'
128 def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
129 then: 'DMI returns a json response'
130 assert response == 'dmi-response'
133 def 'Get resource data for pass-through running from DMI.'() {
134 given: 'cpsDataService returns valid data node'
136 and: 'DMI returns valid response and data'
137 mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
138 new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
139 when: 'get resource data is called'
140 def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
141 then: 'get resource data returns expected response'
142 assert response == '{dmi-response}'
145 def 'Get resource data for operational (cached) data.'() {
146 given: 'CPS Data service returns some object(s)'
147 mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object']
148 when: 'get resource data is called'
149 def response = objectUnderTest.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS)
150 then: 'get resource data returns teh first object from the data service'
151 assert response == 'First Object'
154 def 'Execute (async) data operation for #datastoreName from DMI.'() {
155 given: 'cpsDataService returns valid data node'
156 def dataOperationRequest = getDataOperationRequest(datastoreName)
157 when: 'request resource data for data operation is called'
158 objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId')
159 then: 'request resource data for data operation returns expected response'
160 1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId')
161 where: 'the following data stores are used'
162 datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
165 def 'Getting Yang Resources.'() {
166 when: 'yang resources is called'
167 objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
168 then: 'CPS module services is invoked for the correct dataspace and cm handle'
169 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
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.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 moduleSetTag = 'some-module-set-tag'
183 def alternateId = 'some-alternate-id'
184 def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
185 dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState,
186 moduleSetTag: moduleSetTag, alternateId: alternateId)
187 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
188 when: 'getting cm handle details for a given cm handle id from ncmp service'
189 def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
190 then: 'the result is a ncmpServiceCmHandle'
191 result.class == NcmpServiceCmHandle.class
192 and: 'the cm handle contains the cm handle id'
193 result.cmHandleId == 'some-cm-handle'
194 and: 'the cm handle contains the alternate id'
195 result.alternateId == 'some-alternate-id'
196 and: 'the cm handle contains the module-set-tag'
197 result.moduleSetTag == 'some-module-set-tag'
198 and: 'the cm handle contains the DMI Properties'
199 result.dmiProperties ==[ Book:'Romance Novel' ]
200 and: 'the cm handle contains the public Properties'
201 result.publicProperties == [ "Public Book":'Public Romance Novel' ]
202 and: 'the cm handle contains the cm handle composite state'
203 result.compositeState == compositeState
206 def 'Get cm handle public properties'() {
207 given: 'a yang modelled cm handle'
208 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
209 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
210 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
211 and: 'the system returns this yang modelled cm handle'
212 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
213 when: 'getting cm handle public properties for a given cm handle id from ncmp service'
214 def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
215 then: 'the result returns the correct data'
216 result == [ 'public prop' : 'some public prop' ]
219 def 'Execute cm handle id search for inventory'() {
220 given: 'a ConditionApiProperties object'
221 def conditionProperties = new ConditionProperties()
222 conditionProperties.conditionName = 'hasAllProperties'
223 conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
224 def conditionServiceProps = new CmHandleQueryServiceParameters()
225 conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
226 and: 'the system returns an set of cmHandle ids'
227 mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
228 when: 'getting cm handle id set for a given dmi property'
229 def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
230 then: 'the result returns the correct 2 elements'
231 assert result.size() == 2
232 assert result.contains('cmHandle1')
233 assert result.contains('cmHandle2')
236 def 'Get cm handle composite state'() {
237 given: 'a yang modelled cm handle'
238 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
239 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
240 lastUpdateTime: 'some-timestamp',
241 dataSyncEnabled: false,
242 dataStores: dataStores())
243 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
244 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
245 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
246 and: 'the system returns this yang modelled cm handle'
247 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
248 when: 'getting cm handle composite state for a given cm handle id from ncmp service'
249 def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
250 then: 'the result returns the correct data'
251 result == compositeState
254 def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
255 given: 'cpsDataService returns valid datanode'
256 mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
257 when: 'get resource data is called'
258 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
259 'testResourceId', UPDATE,
260 '{some-json}', 'application/json')
261 then: 'DMI called with correct data'
262 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
263 UPDATE, '{some-json}', 'application/json')
264 >> { new ResponseEntity<>(HttpStatus.OK) }
267 def 'Verify modules and create anchor params.'() {
268 given: 'dmi plugin registration return created cm handles'
269 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
270 dmiDataPlugin: 'service2')
271 dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
272 mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
273 when: 'parse and create cm handle in dmi registration then sync module'
274 objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(mockDmiPluginRegistration, ['test-cm-handle-id'])
275 then: 'system persists the cm handle state'
276 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> {
278 def cmHandleStatePerCmHandle = (args[0] as Collection)
279 cmHandleStatePerCmHandle.each {
280 assert it.id == 'test-cm-handle-id'
286 def 'Execute cm handle id search'() {
287 given: 'valid CmHandleQueryApiParameters input'
288 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
289 def conditionApiProperties = new ConditionApiProperties()
290 conditionApiProperties.conditionName = 'hasAllModules'
291 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
292 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
293 and: 'query cm handle method return with a data node list'
294 mockCpsCmHandlerQueryService.queryCmHandleIds(
295 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
296 >> ['cm-handle-id-1']
297 when: 'execute cm handle search is called'
298 def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
299 then: 'result is the same collection as returned by the CPS Data Service'
300 assert result == ['cm-handle-id-1']
303 def 'Getting module definitions by module'() {
304 when: 'get module definitions is performed with module name'
305 objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
306 then: 'ncmp inventory persistence service is invoked once with correct parameters'
307 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
310 def 'Getting module definitions by cm handle id'() {
311 when: 'get module definitions is performed with cm handle id'
312 objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
313 then: 'ncmp inventory persistence service is invoked once with correct parameter'
314 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
317 def 'Execute cm handle search'() {
318 given: 'valid CmHandleQueryApiParameters input'
319 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
320 def conditionApiProperties = new ConditionApiProperties()
321 conditionApiProperties.conditionName = 'hasAllModules'
322 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
323 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
324 and: 'query cm handle method return with a data node list'
325 mockCpsCmHandlerQueryService.queryCmHandles(
326 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
327 >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
328 when: 'execute cm handle search is called'
329 def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
330 then: 'result is the same collection as returned by the CPS Data Service'
331 assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
334 def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() {
335 given: 'an existing cm handle composite state'
336 def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
337 dataStores: CompositeState.DataStores.builder()
338 .operationalDataStore(CompositeState.Operational.builder()
339 .dataStoreSyncState(initialDataSyncState)
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 #dataSyncEnabledFlag'
344 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
345 then: 'the data sync enabled flag is set to #dataSyncEnabled'
346 compositeState.dataSyncEnabled == dataSyncEnabledFlag
347 and: 'the data store sync state is set to #expectedDataStoreSyncState'
348 compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
349 and: 'the cps data service to delete data nodes is invoked the expected number of times'
350 deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _)
351 and: 'the inventory persistence service to update node leaves is called with the correct values'
352 saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
353 where: 'the following data sync enabled flag is used'
354 scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
355 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1
356 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1
357 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1
358 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0
361 def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
362 given: 'a cm handle composite state'
363 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
364 and: 'get cm handle state returns the composite state for the given cm handle id'
365 mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
366 when: 'set data sync enabled is called with the data sync enabled flag set to true'
367 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
368 then: 'the expected exception is thrown'
370 and: 'the inventory persistence service to update node leaves is not invoked'
371 0 * mockInventoryPersistence.saveCmHandleState(_, _)
374 def 'Get all cm handle IDs by DMI plugin identifier.' () {
375 given: 'cm handle queries service returns cm handles'
376 1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
377 when: 'cm handle Ids are requested with dmi plugin identifier'
378 def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
379 then: 'the result size is correct'
380 assert result.size() == 2
381 and: 'the result returns the correct details'
382 assert result.containsAll('cm-handle-1','cm-handle-2')
386 CompositeState.DataStores.builder()
387 .operationalDataStore(CompositeState.Operational.builder()
388 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
389 .lastSyncTime('some-timestamp').build()).build()
393 mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
396 def getDataOperationRequest(datastore) {
397 def dataOperationRequest = new DataOperationRequest()
398 def dataOperationDefinitions = new ArrayList()
399 dataOperationDefinitions.add(getDataOperationDefinition(datastore))
400 dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions)
401 return dataOperationRequest
404 def getDataOperationDefinition(datastore) {
405 def dataOperationDefinition = new DataOperationDefinition()
406 dataOperationDefinition.setOperation("read")
407 dataOperationDefinition.setOperationId("operational-12")
408 dataOperationDefinition.setDatastore(datastore)
409 def targetIds = new ArrayList()
410 targetIds.add("some-cm-handle")
411 dataOperationDefinition.setCmHandleIds(targetIds)
412 return dataOperationDefinition