aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormpriyank <priyank.maheshwari@est.tech>2022-09-06 18:29:34 +0100
committermpriyank <priyank.maheshwari@est.tech>2022-09-12 18:09:43 +0100
commit7b6ab50231f5ab39d1476531031437f81328115e (patch)
tree3939da0046a69ab31d391e14bd2b5af699202baf
parent440dc8aab179f6c8451683f53b019ccd8bd60bdf (diff)
Handle partial failure
- Removing the transaction boundaries as it was getting rollbacked on partial failures - Handled adding the elements in batch and if it fails try them individually - Refactored code a bit and when there is partial failure we try one more time in sequence and even if there are failures we collect the failures Issue-ID: CPS-1232 Issue-ID: CPS-1126 Change-Id: I7824c9f37f80cbaeedd5dc06d598ca0e3a69c59b Signed-off-by: mpriyank <priyank.maheshwari@est.tech>
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java12
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java29
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy24
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java67
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy23
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java2
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java8
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java34
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy2
9 files changed, 153 insertions, 48 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
index 209ade9559..0eb275cf0e 100755
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
@@ -59,7 +59,7 @@ import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
import org.onap.cps.spi.exceptions.CpsException;
import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
import org.onap.cps.spi.exceptions.DataValidationException;
@@ -365,12 +365,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
try {
lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
return CmHandleRegistrationResponse.createSuccessResponses(cmHandleIds);
- } catch (final AlreadyDefinedException alreadyDefinedException) {
- return List.of(CmHandleRegistrationResponse.createFailureResponse(
- String.join(",", cmHandleIds), RegistrationError.CM_HANDLE_ALREADY_EXIST));
+ } catch (final AlreadyDefinedExceptionBatch alreadyDefinedExceptionBatch) {
+ return CmHandleRegistrationResponse.createFailureResponses(
+ alreadyDefinedExceptionBatch.getAlreadyDefinedCmHandleIds(),
+ RegistrationError.CM_HANDLE_ALREADY_EXIST);
} catch (final Exception exception) {
- return List.of(CmHandleRegistrationResponse.createFailureResponse(String.join(",", cmHandleIds),
- exception));
+ return CmHandleRegistrationResponse.createFailureResponses(cmHandleIds, exception);
}
}
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
index b7faf09a9e..9f80218420 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
@@ -21,6 +21,7 @@
package org.onap.cps.ncmp.api.models;
+import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Builder;
@@ -67,6 +68,34 @@ public class CmHandleRegistrationResponse {
.build();
}
+ /**
+ * Creates a failure response based on registration error.
+ *
+ * @param cmHandleIds list of failed cmHandleIds
+ * @param registrationError enum describing the type of registration error
+ * @return CmHandleRegistrationResponse
+ */
+ public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> cmHandleIds,
+ final RegistrationError registrationError) {
+ return cmHandleIds.stream()
+ .map(cmHandleId -> CmHandleRegistrationResponse.createFailureResponse(cmHandleId, registrationError))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Creates a failure response based on other exception.
+ *
+ * @param cmHandleIds list of failed cmHandleIds
+ * @param exception exception caught during the registration
+ * @return CmHandleRegistrationResponse
+ */
+ public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> cmHandleIds,
+ final Exception exception) {
+ return cmHandleIds.stream()
+ .map(cmHandleId -> CmHandleRegistrationResponse.createFailureResponse(cmHandleId, exception))
+ .collect(Collectors.toList());
+ }
+
public static CmHandleRegistrationResponse createSuccessResponse(final String cmHandle) {
return CmHandleRegistrationResponse.builder().cmHandle(cmHandle)
.status(Status.SUCCESS).build();
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
index 86a32a1a59..2fe521c031 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
@@ -28,14 +28,13 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService
import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler
import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
import org.onap.cps.ncmp.api.inventory.CmHandleQueries
import org.onap.cps.ncmp.api.inventory.CmHandleState
import org.onap.cps.ncmp.api.inventory.InventoryPersistence
import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
import org.onap.cps.ncmp.api.models.DmiPluginRegistration
import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
import org.onap.cps.spi.exceptions.DataNodeNotFoundException
import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
@@ -185,20 +184,17 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
new NcmpServiceCmHandle(cmHandleId: 'cmhandle2'),
new NcmpServiceCmHandle(cmHandleId: 'cmhandle3')])
and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd'
- mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new RuntimeException("Failed") }
+ mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new AlreadyDefinedExceptionBatch(['cmhandle2']) }
when: 'registration is updated to create cm-handles'
def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
then: 'a response is received for all cm-handles'
response.getCreatedCmHandles().size() == 1
and: 'all cm-handles creation fails'
- with(response.getCreatedCmHandles().get(0)) {
+ response.getCreatedCmHandles().each {
+ assert it.cmHandle == 'cmhandle2'
assert it.status == Status.FAILURE
- assert it.registrationError == UNKNOWN_ERROR
- assert it.errorText == 'Failed'
- def sortedCmHandles = it.cmHandle.split(',').sort()
- assert sortedCmHandles[0] == 'cmhandle1'
- assert sortedCmHandles[1] == 'cmhandle2'
- assert sortedCmHandles[2] == 'cmhandle3'
+ assert it.registrationError == CM_HANDLE_ALREADY_EXIST
+ assert it.errorText == 'cm-handle already exists'
}
}
@@ -219,10 +215,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
assert it.errorText == expectedErrorText
}
where:
- scenario | cmHandleId | exception || expectedError | expectedErrorText
- 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
- 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id'
- 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed'
+ scenario | cmHandleId | exception || expectedError | expectedErrorText
+ 'cm-handle already exist' | 'cmhandle' | new AlreadyDefinedExceptionBatch([cmHandleId]) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
+ 'cm-handle has invalid name' | 'cm handle with space' | new DataValidationException("", "") || CM_HANDLE_INVALID_ID | 'cm-handle has an invalid character(s) in id'
+ 'unknown exception while registering cm-handle' | 'cmhandle' | new RuntimeException('Failed') || UNKNOWN_ERROR | 'Failed'
}
def 'Update CM-Handle: Update Operation Response is added to the response'() {
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
index 61e1d5b569..e02fb7355b 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
@@ -52,6 +52,7 @@ import org.onap.cps.spi.entities.FragmentEntity;
import org.onap.cps.spi.entities.SchemaSetEntity;
import org.onap.cps.spi.entities.YangResourceEntity;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
import org.onap.cps.spi.exceptions.ConcurrencyException;
import org.onap.cps.spi.exceptions.CpsAdminException;
import org.onap.cps.spi.exceptions.CpsPathException;
@@ -88,51 +89,75 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
@Override
- @Transactional
public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final DataNode newChildDataNode) {
- addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode));
+ addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
}
@Override
- @Transactional
public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> newListElements) {
- addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
+ addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
}
@Override
- @Transactional
- public void addListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final Collection<Collection<DataNode>> newListsElements) {
+ public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<Collection<DataNode>> newLists) {
+ final Collection<String> failedCmHandleIds = new HashSet<>();
+ newLists.forEach(newList -> {
+ try {
+ addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
+ } catch (final AlreadyDefinedException e) {
+ newList.forEach(listElement -> failedCmHandleIds.add((String) listElement.getLeaves().get("id")));
+ }
+ });
- newListsElements.forEach(
- newListElement -> addListElements(dataspaceName, anchorName, parentNodeXpath, newListElement));
+ if (!failedCmHandleIds.isEmpty()) {
+ throw new AlreadyDefinedExceptionBatch(failedCmHandleIds);
+ }
}
- private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final Collection<DataNode> newChildren) {
+ private void addNewChildDataNode(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath, final DataNode newChild) {
final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final FragmentEntity newChildAsFragmentEntity =
+ convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+ parentFragmentEntity.getAnchor(), newChild);
+ newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
+ try {
+ fragmentRepository.save(newChildAsFragmentEntity);
+ } catch (final DataIntegrityViolationException e) {
+ throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
+ }
+
+ }
+
+ private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> newChildren) {
+ final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
try {
- final List<FragmentEntity> fragmentEntities = new ArrayList<>();
newChildren.forEach(newChildAsDataNode -> {
- final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(
- parentFragmentEntity.getDataspace(),
- parentFragmentEntity.getAnchor(),
- newChildAsDataNode);
+ final FragmentEntity newChildAsFragmentEntity =
+ convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+ parentFragmentEntity.getAnchor(), newChildAsDataNode);
newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
fragmentEntities.add(newChildAsFragmentEntity);
});
fragmentRepository.saveAll(fragmentEntities);
- } catch (final DataIntegrityViolationException exception) {
- final List<String> conflictXpaths = newChildren.stream()
- .map(DataNode::getXpath)
- .collect(Collectors.toList());
- throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception);
+ } catch (final DataIntegrityViolationException e) {
+ log.warn("Exception occurred : {} , Batch with size : {} will be retried using individual save operations",
+ e, fragmentEntities.size());
+ retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
}
}
+ private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath, final Collection<DataNode> newChildren) {
+ newChildren.forEach(newChild -> addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild));
+ }
+
@Override
public void storeDataNode(final String dataspaceName, final String anchorName, final DataNode dataNode) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
index acc243b5b4..ba9bd6f95a 100755
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
@@ -27,6 +27,7 @@ import org.onap.cps.cpspath.parser.PathParsingException
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.entities.FragmentEntity
import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
import org.onap.cps.spi.exceptions.AnchorNotFoundException
import org.onap.cps.spi.exceptions.CpsAdminException
import org.onap.cps.spi.exceptions.CpsPathException
@@ -165,7 +166,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
def grandChild = buildDataNode('/parent-201/child-204[@key="NEW1"]/grand-child-204[@key2="NEW1-CHILD"]', [leave:'value'], [])
listElements[0].childDataNodes = [grandChild]
when: 'the new data node (list elements) are added to an existing parent node'
- objectUnderTest.addListElementsBatch(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
+ objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
then: 'new entries are successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
def allChildXpaths = parentFragment.childFragments.collect { it.xpath }
@@ -179,6 +180,22 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
+ def 'Add already existing list elements'() {
+ given: 'two new child list elements for an existing parent'
+ def listElementXpaths1 = ['/parent-100', '/parent-100/child-001']
+ def listElementXpaths2 = ['/parent-200', '/parent-200/child-201']
+ def listElements1 = toDataNodesWithId(listElementXpaths1, 'cmhandle1')
+ def listElements2 = toDataNodesWithId(listElementXpaths2, 'cmhandle2')
+ when: 'duplicate data node is requested to be added'
+ objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/', [listElements1,listElements2])
+ then: 'already defined batch exception is thrown'
+ def e = thrown(AlreadyDefinedExceptionBatch)
+ and: 'it contains both cmhandle ids'
+ assert e.alreadyDefinedCmHandleIds.size() == 2
+ assert e.alreadyDefinedCmHandleIds.containsAll(['cmhandle1', 'cmhandle2'])
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
def 'Add list element error scenario: #scenario.'() {
given: 'list element as a collection of data nodes'
def listElementCollection = toDataNodes(listElementXpaths)
@@ -559,6 +576,10 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
}
+ static Collection<DataNode> toDataNodesWithId(xpaths, id) {
+ return xpaths.collect { new DataNodeBuilder().withXpath(it).withLeaves(['id': id]).build() }
+ }
+
static DataNode buildDataNode(xpath, leaves, childDataNodes) {
return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index 6bf493556e..b6aa04be76 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -97,7 +97,7 @@ public class CpsDataServiceImpl implements CpsDataService {
CpsValidator.validateNameCharacters(dataspaceName, anchorName);
final Collection<Collection<DataNode>> listElementDataNodeCollections =
buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList);
- cpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, parentNodeXpath,
+ cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath,
listElementDataNodeCollections);
processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
index 8b45ae78d9..cd0cefcbf3 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
@@ -66,15 +66,15 @@ public interface CpsDataPersistenceService {
Collection<DataNode> listElementsCollection);
/**
- * Adds list child elements to a Fragment.
+ * Add multiple lists of data nodes to a parent node at the same time.
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
* @param parentNodeXpath parent node xpath
- * @param listElementsCollections collections of data nodes representing list elements
+ * @param newLists collections of lists of data nodes representing list elements
*/
- void addListElementsBatch(String dataspaceName, String anchorName, String parentNodeXpath,
- Collection<Collection<DataNode>> listElementsCollections);
+ void addMultipleLists(String dataspaceName, String anchorName, String parentNodeXpath,
+ Collection<Collection<DataNode>> newLists);
/**
* Retrieves datanode by XPath for given dataspace and anchor.
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java
new file mode 100644
index 0000000000..a5ce6fde21
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java
@@ -0,0 +1,34 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+import java.util.Collection;
+import lombok.Getter;
+
+public class AlreadyDefinedExceptionBatch extends RuntimeException {
+
+ @Getter
+ private final Collection<String> alreadyDefinedCmHandleIds;
+
+ public AlreadyDefinedExceptionBatch(final Collection<String> alreadyDefinedCmHandleIds) {
+ this.alreadyDefinedCmHandleIds = alreadyDefinedCmHandleIds;
+ }
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index ab960df6aa..3f28f0ac8d 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -143,7 +143,7 @@ class CpsDataServiceImplSpec extends Specification {
def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
- 1 * mockCpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, '/test-tree',_) >> {
+ 1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
args -> {
def listElementsCollection = args[3] as Collection<Collection<DataNode>>
assert listElementsCollection.size() == 1