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