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.CmHandleIdMapper
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
57 import spock.lang.Shared
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 mockCmHandleIdMapper = Mock(CmHandleIdMapper)
84 def mockModuleSetTagCache = [:]
87 def NO_REQUEST_ID = null
89 def OPTIONS_PARAM = '(a=1,b=2)'
91 def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'test-cm-handle-id')
93 def objectUnderTest = new NetworkCmProxyDataServiceImpl(
94 spiedJsonObjectMapper,
95 mockDmiDataOperations,
96 nullNetworkCmProxyDataServicePropertyHandler,
97 mockInventoryPersistence,
99 mockCpsCmHandlerQueryService,
100 mockLcmEventsCmHandleStateHandler,
102 stubModuleSyncStartedOnCmHandles,
103 stubTrustLevelPerDmiPlugin,
104 mockTrustLevelManager,
105 mockCmHandleIdMapper,
106 mockModuleSetTagCache)
108 def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
110 def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
112 def 'Write resource data for pass-through running from DMI using POST.'() {
113 given: 'cpsDataService returns valid datanode'
115 when: 'write resource data is called'
116 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
117 'testResourceId', CREATE,
118 '{some-json}', 'application/json')
119 then: 'DMI called with correct data'
120 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
121 CREATE, '{some-json}', 'application/json')
122 >> { new ResponseEntity<>(HttpStatus.CREATED) }
125 def 'Get resource data for pass-through operational from DMI.'() {
126 given: 'cpsDataService returns valid data node'
128 and: 'get resource data from DMI is called'
129 mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_OPERATIONAL.datastoreName,'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
130 new ResponseEntity<>('dmi-response', HttpStatus.OK)
131 when: 'get resource data operational for cm-handle is called'
132 def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
133 then: 'DMI returns a json response'
134 assert response == 'dmi-response'
137 def 'Get resource data for pass-through running from DMI.'() {
138 given: 'cpsDataService returns valid data node'
140 and: 'DMI returns valid response and data'
141 mockDmiDataOperations.getResourceDataFromDmi(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID) >>
142 new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
143 when: 'get resource data is called'
144 def response = objectUnderTest.getResourceDataForCmHandle(PASSTHROUGH_RUNNING.datastoreName, 'testCmHandle', 'testResourceId', OPTIONS_PARAM, NO_TOPIC, NO_REQUEST_ID)
145 then: 'get resource data returns expected response'
146 assert response == '{dmi-response}'
149 def 'Get resource data for operational (cached) data.'() {
150 given: 'CPS Data service returns some object(s)'
151 mockCpsDataService.getDataNodes(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS) >> ['First Object', 'other Object']
152 when: 'get resource data is called'
153 def response = objectUnderTest.getResourceDataForCmHandle(OPERATIONAL.datastoreName, 'testCmHandle', 'testResourceId', FetchDescendantsOption.OMIT_DESCENDANTS)
154 then: 'get resource data returns teh first object from the data service'
155 assert response == 'First Object'
158 def 'Execute (async) data operation for #datastoreName from DMI.'() {
159 given: 'cpsDataService returns valid data node'
160 def dataOperationRequest = getDataOperationRequest(datastoreName)
161 when: 'request resource data for data operation is called'
162 objectUnderTest.executeDataOperationForCmHandles('some topic', dataOperationRequest, 'requestId')
163 then: 'request resource data for data operation returns expected response'
164 1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', dataOperationRequest, 'requestId')
165 where: 'the following data stores are used'
166 datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
169 def 'Getting Yang Resources.'() {
170 when: 'yang resources is called'
171 objectUnderTest.getYangResourcesModuleReferences('some-cm-handle')
172 then: 'CPS module services is invoked for the correct dataspace and cm handle'
173 1 * mockInventoryPersistence.getYangResourcesModuleReferences('some-cm-handle')
176 def 'Get a cm handle.'() {
177 given: 'the system returns a yang modelled cm handle'
178 def dmiServiceName = 'some service name'
179 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
180 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
181 lastUpdateTime: 'some-timestamp',
182 dataSyncEnabled: false,
183 dataStores: dataStores())
184 def dmiProperties = [new YangModelCmHandle.Property('Book', 'Romance Novel')]
185 def publicProperties = [new YangModelCmHandle.Property('Public Book', 'Public Romance Novel')]
186 def yangModelCmHandle = new YangModelCmHandle(id: 'some-cm-handle', dmiServiceName: dmiServiceName,
187 dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
188 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
189 when: 'getting cm handle details for a given cm handle id from ncmp service'
190 def result = objectUnderTest.getNcmpServiceCmHandle('some-cm-handle')
191 then: 'the result is a ncmpServiceCmHandle'
192 result.class == NcmpServiceCmHandle.class
193 and: 'the cm handle contains the cm handle id'
194 result.cmHandleId == 'some-cm-handle'
195 and: 'the cm handle contains the DMI Properties'
196 result.dmiProperties ==[ Book:'Romance Novel' ]
197 and: 'the cm handle contains the public Properties'
198 result.publicProperties == [ "Public Book":'Public Romance Novel' ]
199 and: 'the cm handle contains the cm handle composite state'
200 result.compositeState == compositeState
204 def 'Get cm handle public properties'() {
205 given: 'a yang modelled cm handle'
206 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
207 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
208 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties)
209 and: 'the system returns this yang modelled cm handle'
210 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
211 when: 'getting cm handle public properties for a given cm handle id from ncmp service'
212 def result = objectUnderTest.getCmHandlePublicProperties('some-cm-handle')
213 then: 'the result returns the correct data'
214 result == [ 'public prop' : 'some public prop' ]
217 def 'Execute cm handle id search for inventory'() {
218 given: 'a ConditionApiProperties object'
219 def conditionProperties = new ConditionProperties()
220 conditionProperties.conditionName = 'hasAllProperties'
221 conditionProperties.conditionParameters = [ [ 'some-key' : 'some-value' ] ]
222 def conditionServiceProps = new CmHandleQueryServiceParameters()
223 conditionServiceProps.cmHandleQueryParameters = [conditionProperties] as List<ConditionProperties>
224 and: 'the system returns an set of cmHandle ids'
225 mockCpsCmHandlerQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ]
226 when: 'getting cm handle id set for a given dmi property'
227 def result = objectUnderTest.executeCmHandleIdSearchForInventory(conditionServiceProps)
228 then: 'the result returns the correct 2 elements'
229 assert result.size() == 2
230 assert result.contains('cmHandle1')
231 assert result.contains('cmHandle2')
234 def 'Get cm handle composite state'() {
235 given: 'a yang modelled cm handle'
236 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED,
237 lockReason: CompositeState.LockReason.builder().lockReasonCategory(LockReasonCategory.MODULE_SYNC_FAILED).details("lock details").build(),
238 lastUpdateTime: 'some-timestamp',
239 dataSyncEnabled: false,
240 dataStores: dataStores())
241 def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
242 def publicProperties = [new YangModelCmHandle.Property('public prop', 'some public prop')]
243 def yangModelCmHandle = new YangModelCmHandle(id:'some-cm-handle', dmiServiceName: 'some service name', dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
244 and: 'the system returns this yang modelled cm handle'
245 1 * mockInventoryPersistence.getYangModelCmHandle('some-cm-handle') >> yangModelCmHandle
246 when: 'getting cm handle composite state for a given cm handle id from ncmp service'
247 def result = objectUnderTest.getCmHandleCompositeState('some-cm-handle')
248 then: 'the result returns the correct data'
249 result == compositeState
252 def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
253 given: 'cpsDataService returns valid datanode'
254 mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
255 when: 'get resource data is called'
256 objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
257 'testResourceId', UPDATE,
258 '{some-json}', 'application/json')
259 then: 'DMI called with correct data'
260 1 * mockDmiDataOperations.writeResourceDataPassThroughRunningFromDmi('testCmHandle', 'testResourceId',
261 UPDATE, '{some-json}', 'application/json')
262 >> { new ResponseEntity<>(HttpStatus.OK) }
265 def 'Verify modules and create anchor params.'() {
266 given: 'dmi plugin registration return created cm handles'
267 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
268 dmiDataPlugin: 'service2')
269 dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
270 mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
271 when: 'parse and create cm handle in dmi registration then sync module'
272 objectUnderTest.parseAndProcessCreatedCmHandlesInRegistration(mockDmiPluginRegistration)
273 then: 'system persists the cm handle state'
274 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> {
276 def cmHandleStatePerCmHandle = (args[0] as Collection)
277 cmHandleStatePerCmHandle.each {
278 assert it.id == 'test-cm-handle-id'
284 def 'Execute cm handle id search'() {
285 given: 'valid CmHandleQueryApiParameters input'
286 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
287 def conditionApiProperties = new ConditionApiProperties()
288 conditionApiProperties.conditionName = 'hasAllModules'
289 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
290 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
291 and: 'query cm handle method return with a data node list'
292 mockCpsCmHandlerQueryService.queryCmHandleIds(
293 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
294 >> ['cm-handle-id-1']
295 when: 'execute cm handle search is called'
296 def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
297 then: 'result is the same collection as returned by the CPS Data Service'
298 assert result == ['cm-handle-id-1']
301 def 'Getting module definitions by module'() {
302 when: 'get module definitions is performed with module name'
303 objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
304 then: 'ncmp inventory persistence service is invoked once with correct parameters'
305 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
308 def 'Getting module definitions by cm handle id'() {
309 when: 'get module definitions is performed with cm handle id'
310 objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
311 then: 'ncmp inventory persistence service is invoked once with correct parameter'
312 1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
315 def 'Execute cm handle search'() {
316 given: 'valid CmHandleQueryApiParameters input'
317 def cmHandleQueryApiParameters = new CmHandleQueryApiParameters()
318 def conditionApiProperties = new ConditionApiProperties()
319 conditionApiProperties.conditionName = 'hasAllModules'
320 conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']]
321 cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties]
322 and: 'query cm handle method return with a data node list'
323 mockCpsCmHandlerQueryService.queryCmHandles(
324 spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class))
325 >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-id-1')]
326 when: 'execute cm handle search is called'
327 def result = objectUnderTest.executeCmHandleSearch(cmHandleQueryApiParameters)
328 then: 'result is the same collection as returned by the CPS Data Service'
329 assert result.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['cm-handle-id-1'] as Set
332 def 'Set Cm Handle Data Sync Enabled Flag where data sync flag is #scenario'() {
333 given: 'an existing cm handle composite state'
334 def compositeState = new CompositeState(cmHandleState: CmHandleState.READY, dataSyncEnabled: initialDataSyncEnabledFlag,
335 dataStores: CompositeState.DataStores.builder()
336 .operationalDataStore(CompositeState.Operational.builder()
337 .dataStoreSyncState(initialDataSyncState)
339 and: 'get cm handle state returns the composite state for the given cm handle id'
340 mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
341 when: 'set data sync enabled is called with the data sync enabled flag set to #dataSyncEnabledFlag'
342 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
343 then: 'the data sync enabled flag is set to #dataSyncEnabled'
344 compositeState.dataSyncEnabled == dataSyncEnabledFlag
345 and: 'the data store sync state is set to #expectedDataStoreSyncState'
346 compositeState.dataStores.operationalDataStore.dataStoreSyncState == expectedDataStoreSyncState
347 and: 'the cps data service to delete data nodes is invoked the expected number of times'
348 deleteDataNodeExpectedNumberOfInvocation * mockCpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'some-cm-handle-id', '/netconf-state', _)
349 and: 'the inventory persistence service to update node leaves is called with the correct values'
350 saveCmHandleStateExpectedNumberOfInvocations * mockInventoryPersistence.saveCmHandleState('some-cm-handle-id', compositeState)
351 where: 'the following data sync enabled flag is used'
352 scenario | dataSyncEnabledFlag | initialDataSyncEnabledFlag | initialDataSyncState || expectedDataStoreSyncState | deleteDataNodeExpectedNumberOfInvocation | saveCmHandleStateExpectedNumberOfInvocations
353 'enabled' | true | false | DataStoreSyncState.NONE_REQUESTED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 1
354 'disabled' | false | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 0 | 1
355 'disabled where sync-state is currently SYNCHRONIZED' | false | true | DataStoreSyncState.SYNCHRONIZED || DataStoreSyncState.NONE_REQUESTED | 1 | 1
356 'is set to existing flag state' | true | true | DataStoreSyncState.UNSYNCHRONIZED || DataStoreSyncState.UNSYNCHRONIZED | 0 | 0
359 def 'Set cm Handle Data Sync Enabled flag with following cm handle not in ready state exception' () {
360 given: 'a cm handle composite state'
361 def compositeState = new CompositeState(cmHandleState: CmHandleState.ADVISED, dataSyncEnabled: false)
362 and: 'get cm handle state returns the composite state for the given cm handle id'
363 mockInventoryPersistence.getCmHandleState('some-cm-handle-id') >> compositeState
364 when: 'set data sync enabled is called with the data sync enabled flag set to true'
365 objectUnderTest.setDataSyncEnabled('some-cm-handle-id', true)
366 then: 'the expected exception is thrown'
368 and: 'the inventory persistence service to update node leaves is not invoked'
369 0 * mockInventoryPersistence.saveCmHandleState(_, _)
372 def 'Get all cm handle IDs by DMI plugin identifier.' () {
373 given: 'cm handle queries service returns cm handles'
374 1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
375 when: 'cm handle Ids are requested with dmi plugin identifier'
376 def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
377 then: 'the result size is correct'
378 assert result.size() == 2
379 and: 'the result returns the correct details'
380 assert result.containsAll('cm-handle-1','cm-handle-2')
384 CompositeState.DataStores.builder()
385 .operationalDataStore(CompositeState.Operational.builder()
386 .dataStoreSyncState(DataStoreSyncState.NONE_REQUESTED)
387 .lastSyncTime('some-timestamp').build()).build()
391 mockCpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
394 def getDataOperationRequest(datastore) {
395 def dataOperationRequest = new DataOperationRequest()
396 def dataOperationDefinitions = new ArrayList()
397 dataOperationDefinitions.add(getDataOperationDefinition(datastore))
398 dataOperationRequest.setDataOperationDefinitions(dataOperationDefinitions)
399 return dataOperationRequest
402 def getDataOperationDefinition(datastore) {
403 def dataOperationDefinition = new DataOperationDefinition()
404 dataOperationDefinition.setOperation("read")
405 dataOperationDefinition.setOperationId("operational-12")
406 dataOperationDefinition.setDatastore(datastore)
407 def targetIds = new ArrayList()
408 targetIds.add("some-cm-handle")
409 dataOperationDefinition.setCmHandleIds(targetIds)
410 return dataOperationDefinition