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)
84 def mockModuleSetTagCache = [:]
87 def NO_REQUEST_ID = null
88 def OPTIONS_PARAM = '(a=1,b=2)'
89 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
91 def objectUnderTest = new NetworkCmProxyDataServiceImpl(
92 spiedJsonObjectMapper,
93 mockDmiDataOperations,
94 nullNetworkCmProxyDataServicePropertyHandler,
95 mockInventoryPersistence,
97 mockCpsCmHandlerQueryService,
98 mockLcmEventsCmHandleStateHandler,
100 stubModuleSyncStartedOnCmHandles,
101 stubTrustLevelPerDmiPlugin,
102 mockTrustLevelManager,
103 mockAlternateIdChecker,
104 mockModuleSetTagCache)
106 def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
108 def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
110 def 'Write resource data for pass-through running from DMI using POST.'() {
111 given: 'cpsDataService returns valid datanode'
113 when: 'write resource data is called'
114 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
115 'testResourceId', CREATE,
116 '{some-json}', 'application/json')
117 then: 'DMI called with correct data'
118 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
119 CREATE, '{some-json}', 'application/json')
120 >> { new ResponseEntity<>(HttpStatus.CREATED) }
123 def 'Get resource data for pass-through operational from DMI.'() {
124 given: 'cpsDataService returns valid data node'
126 and: 'get resource data from DMI is called'
127 mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName,'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
128 new ResponseEntity<>('dmi-response', HttpStatus.OK)
129 when: 'get resource data operational for cm-handle is called'
130 def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
131 then: 'DMI returns a json response'
132 assert response == 'dmi-response'
135 def 'Get resource data for pass-through running from DMI.'() {
136 given: 'cpsDataService returns valid data node'
138 and: 'DMI returns valid response and data'
139 mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
140 new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
141 when: 'get resource data is called'
142 def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
143 then: 'get resource data returns expected response'
144 assert response == '{dmi-response}'
147 def 'Get resource data for operational (cached) data.'() {
148 given: 'CPS Data service returns some object(s)'
149 mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object']
150 when: 'get resource data is called'
151 def response = objectUnderTest.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS)
152 then: 'get resource data returns teh first object from the data service'
153 assert response == 'First Object'
156 def 'Execute (async) data operation for #datastoreName from DMI.'() {
157 given: 'cpsDataService returns valid data node'
158 def dataOperationRequest = getDataOperationRequest(datastoreName)
159 when: 'request resource data for data operation is called'
160 objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId')
161 then: 'request resource data for data operation returns expected response'
162 1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId')
163 where: 'the following data stores are used'
164 datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
167 def 'Getting Yang Resources.'() {
168 when: 'yang resources is called'
169 objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
170 then: 'CPS module services is invoked for the correct dataspace and cm handle'
171 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
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.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
202 def 'Get cm handle public properties'() {
203 given: 'a yang modelled cm handle'
204 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
205 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
206 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
207 and: 'the system returns this yang modelled cm handle'
208 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
209 when: 'getting cm handle public properties for a given cm handle id from ncmp service'
210 def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
211 then: 'the result returns the correct data'
212 result == [ 'public prop' : 'some public prop' ]
215 def 'Execute cm handle id search for inventory'() {
216 given: 'a ConditionApiProperties object'
217 def conditionProperties = new ConditionProperties()
218 conditionProperties.conditionName = 'hasAllProperties'
219 conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
220 def conditionServiceProps = new CmHandleQueryServiceParameters()
221 conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
222 and: 'the system returns an set of cmHandle ids'
223 mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
224 when: 'getting cm handle id set for a given dmi property'
225 def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
226 then: 'the result returns the correct 2 elements'
227 assert result.size() == 2
228 assert result.contains('cmHandle1')
229 assert result.contains('cmHandle2')
232 def 'Get cm handle composite state'() {
233 given: 'a yang modelled cm handle'
234 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
235 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
236 lastUpdateTime: 'some-timestamp',
237 dataSyncEnabled: false,
238 dataStores: dataStores())
239 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
240 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
241 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
242 and: 'the system returns this yang modelled cm handle'
243 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
244 when: 'getting cm handle composite state for a given cm handle id from ncmp service'
245 def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
246 then: 'the result returns the correct data'
247 result == compositeState
250 def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
251 given: 'cpsDataService returns valid datanode'
252 mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
253 when: 'get resource data is called'
254 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
255 'testResourceId', UPDATE,
256 '{some-json}', 'application/json')
257 then: 'DMI called with correct data'
258 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
259 UPDATE, '{some-json}', 'application/json')
260 >> { new ResponseEntity<>(HttpStatus.OK) }
263 def 'Verify modules and create anchor params.'() {
264 given: 'dmi plugin registration return created cm handles'
265 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
266 dmiDataPlugin: 'service2')
267 dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
268 mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
269 when: 'parse and create cm handle in dmi registration then sync module'
270 objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(mockDmiPluginRegistration, ['test-cm-handle-id'])
271 then: 'system persists the cm handle state'
272 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> {
274 def cmHandleStatePerCmHandle = (args[0] as Collection)
275 cmHandleStatePerCmHandle.each {
276 assert it.id == 'test-cm-handle-id'
282 def 'Execute cm handle id search'() {
283 given: 'valid CmHandleQueryApiParameters input'
284 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
285 def conditionApiProperties = new ConditionApiProperties()
286 conditionApiProperties.conditionName = 'hasAllModules'
287 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
288 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
289 and: 'query cm handle method return with a data node list'
290 mockCpsCmHandlerQueryService.queryCmHandleIds(
291 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
292 >> ['cm-handle-id-1']
293 when: 'execute cm handle search is called'
294 def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
295 then: 'result is the same collection as returned by the CPS Data Service'
296 assert result == ['cm-handle-id-1']
299 def 'Getting module definitions by module'() {
300 when: 'get module definitions is performed with module name'
301 objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
302 then: 'ncmp inventory persistence service is invoked once with correct parameters'
303 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
306 def 'Getting module definitions by cm handle id'() {
307 when: 'get module definitions is performed with cm handle id'
308 objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
309 then: 'ncmp inventory persistence service is invoked once with correct parameter'
310 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
313 def 'Execute cm handle search'() {
314 given: 'valid CmHandleQueryApiParameters input'
315 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
316 def conditionApiProperties = new ConditionApiProperties()
317 conditionApiProperties.conditionName = 'hasAllModules'
318 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
319 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
320 and: 'query cm handle method return with a data node list'
321 mockCpsCmHandlerQueryService.queryCmHandles(
322 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
323 >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
324 when: 'execute cm handle search is called'
325 def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
326 then: 'result is the same collection as returned by the CPS Data Service'
327 assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
330 def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() {
331 given: 'an existing cm handle composite state'
332 def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
333 dataStores: CompositeState.DataStores.builder()
334 .operationalDataStore(CompositeState.Operational.builder()
335 .dataStoreSyncState(initialDataSyncState)
337 and: 'get cm handle state returns the composite state for the given cm handle id'
338 mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
339 when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
340 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
341 then: 'the data sync enabled flag is set to #dataSyncEnabled'
342 compositeState.dataSyncEnabled == dataSyncEnabledFlag
343 and: 'the data store sync state is set to #expectedDataStoreSyncState'
344 compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
345 and: 'the cps data service to delete data nodes is invoked the expected number of times'
346 deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _)
347 and: 'the inventory persistence service to update node leaves is called with the correct values'
348 saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
349 where: 'the following data sync enabled flag is used'
350 scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
351 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1
352 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1
353 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1
354 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0
357 def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
358 given: 'a cm handle composite state'
359 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
360 and: 'get cm handle state returns the composite state for the given cm handle id'
361 mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
362 when: 'set data sync enabled is called with the data sync enabled flag set to true'
363 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
364 then: 'the expected exception is thrown'
366 and: 'the inventory persistence service to update node leaves is not invoked'
367 0 * mockInventoryPersistence.saveCmHandleState(_, _)
370 def 'Get all cm handle IDs by DMI plugin identifier.' () {
371 given: 'cm handle queries service returns cm handles'
372 1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
373 when: 'cm handle Ids are requested with dmi plugin identifier'
374 def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
375 then: 'the result size is correct'
376 assert result.size() == 2
377 and: 'the result returns the correct details'
378 assert result.containsAll('cm-handle-1','cm-handle-2')
382 CompositeState.DataStores.builder()
383 .operationalDataStore(CompositeState.Operational.builder()
384 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
385 .lastSyncTime('some-timestamp').build()).build()
389 mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
392 def getDataOperationRequest(datastore) {
393 def dataOperationRequest = new DataOperationRequest()
394 def dataOperationDefinitions = new ArrayList()
395 dataOperationDefinitions.add(getDataOperationDefinition(datastore))
396 dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions)
397 return dataOperationRequest
400 def getDataOperationDefinition(datastore) {
401 def dataOperationDefinition = new DataOperationDefinition()
402 dataOperationDefinition.setOperation("read")
403 dataOperationDefinition.setOperationId("operational-12")
404 dataOperationDefinition.setDatastore(datastore)
405 def targetIds = new ArrayList()
406 targetIds.add("some-cm-handle")
407 dataOperationDefinition.setCmHandleIds(targetIds)
408 return dataOperationDefinition