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