From 62ac0c3b172ef5dbbc9d22d10bb45186b446d4c9 Mon Sep 17 00:00:00 2001 From: "puthuparambil.aditya" Date: Thu, 3 Feb 2022 16:42:13 +0000 Subject: [PATCH] Fix for retry mechanism on concurrent CmHandle registration Issue-ID: CPS-856 Signed-off-by: puthuparambil.aditya Change-Id: Ie7c0033f2987166315611f8a286ae3978466c75f --- .../spi/impl/CpsModulePersistenceServiceImpl.java | 10 +- ...sModulePersistenceServiceConcurrencySpec.groovy | 124 +++++++++++++++++++++ .../test/java/org/onap/cps/TestApplication.java | 2 + docs/release-notes.rst | 1 + 4 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java index 3e39a05c5..86d5de6d0 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java @@ -134,10 +134,10 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ @Transactional // A retry is made to store the schema set if it fails because of duplicated yang resource exception that // can occur in case of specific concurrent requests. - @Retryable(value = DuplicatedYangResourceException.class, maxAttempts = 2, backoff = @Backoff(delay = 500)) + @Retryable(value = DuplicatedYangResourceException.class, maxAttempts = 5, backoff = + @Backoff(random = true, delay = 200, maxDelay = 2000, multiplier = 2)) public void storeSchemaSet(final String dataspaceName, final String schemaSetName, final Map yangResourcesNameToContentMap) { - final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName); final var yangResourceEntities = synchronizeYangResources(yangResourcesNameToContentMap); final var schemaSetEntity = new SchemaSetEntity(); @@ -153,6 +153,10 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ @Override @Transactional + // A retry is made to store the schema set if it fails because of duplicated yang resource exception that + // can occur in case of specific concurrent requests. + @Retryable(value = DuplicatedYangResourceException.class, maxAttempts = 5, backoff = + @Backoff(random = true, delay = 200, maxDelay = 2000, multiplier = 2)) public void storeSchemaSetFromModules(final String dataspaceName, final String schemaSetName, final Map newYangResourcesModuleNameToContentMap, final List moduleReferences) { @@ -219,7 +223,7 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ convertedException.ifPresent( e -> log.warn( "Cannot persist duplicated yang resource. " - + "A total of 2 attempts to store the schema set are planned.", e)); + + "System will attempt this method up to 5 times.", e)); throw convertedException.isPresent() ? convertedException.get() : dataIntegrityViolationException; } } diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy new file mode 100644 index 000000000..085bb3340 --- /dev/null +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy @@ -0,0 +1,124 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Bell Canada. + * ================================================================================ + * 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.impl + +import com.fasterxml.jackson.databind.ObjectMapper +import org.hibernate.exception.ConstraintViolationException +import org.mockito.InjectMocks +import org.mockito.Mock +import org.onap.cps.spi.CpsAdminPersistenceService +import org.onap.cps.spi.CpsModulePersistenceService +import org.onap.cps.spi.entities.DataspaceEntity +import org.onap.cps.spi.entities.YangResourceEntity +import org.onap.cps.spi.exceptions.DuplicatedYangResourceException +import org.onap.cps.spi.model.ExtendedModuleReference +import org.onap.cps.spi.model.ModuleReference +import org.onap.cps.spi.repository.AnchorRepository +import org.onap.cps.spi.repository.DataspaceRepository +import org.onap.cps.spi.repository.FragmentRepository +import org.onap.cps.spi.repository.SchemaSetRepository +import org.onap.cps.spi.repository.YangResourceRepository +import org.onap.cps.utils.JsonObjectMapper +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.dao.DataIntegrityViolationException +import org.springframework.test.context.jdbc.Sql +import spock.lang.Shared +import spock.lang.Specification + +import java.sql.SQLException + +class CpsModulePersistenceServiceConcurrencySpec extends CpsPersistenceSpecBase { + + @Autowired + CpsModulePersistenceService objectUnderTest + + @Autowired + AnchorRepository anchorRepository + + @Autowired + SchemaSetRepository schemaSetRepository + + @Autowired + CpsAdminPersistenceService cpsAdminPersistenceService + + @SpringBean + YangResourceRepository yangResourceRepositoryMock = Mock() + + @SpringBean + DataspaceRepository dataspaceRepositoryMock = Mock() + + @SpringBean + JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) + + static final String DATASPACE_NAME = 'DATASPACE-001' + static final String SCHEMA_SET_NAME_NEW = 'SCHEMA-SET-NEW' + static final String NEW_RESOURCE_NAME = 'some new resource' + static final String NEW_RESOURCE_CONTENT = 'module stores {\n' + + ' yang-version 1.1;\n' + + ' namespace "org:onap:ccsdk:sample";\n' + + '}' + + def newYangResourcesNameToContentMap = [(NEW_RESOURCE_NAME):NEW_RESOURCE_CONTENT] + + @Shared + yangResourceChecksum = 'b13faef573ed1374139d02c40d8ce09c80ea1dc70e63e464c1ed61568d48d539' + + @Shared + yangResourceChecksumDbConstraint = 'yang_resource_checksum_key' + + @Shared + sqlExceptionMessage = String.format('(checksum)=(%s)', yangResourceChecksum) + + @Shared + checksumIntegrityException = + new DataIntegrityViolationException("checksum integrity exception", + new ConstraintViolationException('', new SQLException(sqlExceptionMessage), yangResourceChecksumDbConstraint)) + + def 'Store new schema set, retry mechanism'() { + given: 'no pre-existing schemaset in database' + dataspaceRepositoryMock.getByName(_) >> new DataspaceEntity() + yangResourceRepositoryMock.findAllByChecksumIn(_) >> Collections.emptyList() + when: 'a new schemaset is stored' + objectUnderTest.storeSchemaSet(DATASPACE_NAME, SCHEMA_SET_NAME_NEW, newYangResourcesNameToContentMap) + then: ' duplicated yang resource exception is thrown ' + def e = thrown(DuplicatedYangResourceException) + and: 'the system will attempt to save the data 5 times (because checksum integrity exception is thrown each time)' + 5 * yangResourceRepositoryMock.saveAll(_) >> { throw checksumIntegrityException } + } + + def 'Store schema set using modules, retry mechanism'() { + given: 'map of new modules, a list of existing modules, module reference' + def mapOfNewModules = [newModule1: 'module newmodule { yang-version 1.1; revision "2021-10-12" { } }'] + def moduleReferenceForExistingModule = new ModuleReference("test","2021-10-12") + def listOfExistingModulesModuleReference = [moduleReferenceForExistingModule] + and: 'no pre-existing schemaset in database' + dataspaceRepositoryMock.getByName(_) >> new DataspaceEntity() + yangResourceRepositoryMock.findAllByChecksumIn(_) >> Collections.emptyList() + when: 'a new schemaset is stored from a module' + objectUnderTest.storeSchemaSetFromModules(DATASPACE_NAME, "newSchemaSetName" , mapOfNewModules, listOfExistingModulesModuleReference) + then: ' duplicated yang resource exception is thrown ' + def e = thrown(DuplicatedYangResourceException) + and: 'the system will attempt to save the data 5 times (because checksum integrity exception is thrown each time)' + 5 * yangResourceRepositoryMock.saveAll(_) >> { throw checksumIntegrityException } + } +} diff --git a/cps-ri/src/test/java/org/onap/cps/TestApplication.java b/cps-ri/src/test/java/org/onap/cps/TestApplication.java index 0d1df456e..075a241fc 100644 --- a/cps-ri/src/test/java/org/onap/cps/TestApplication.java +++ b/cps-ri/src/test/java/org/onap/cps/TestApplication.java @@ -21,11 +21,13 @@ package org.onap.cps; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.retry.annotation.EnableRetry; /** * The @SpringBootApplication annotated class is required in order to run tests * marked with @SpringBootTest annotation. */ @SpringBootApplication(scanBasePackages = "org.onap.cps.spi") +@EnableRetry public class TestApplication { } diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 058f6baae..3fb0a7015 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -41,6 +41,7 @@ Bug Fixes - `CPS-783 `_ Remove cm handle does not completely remove all cm handle information - `CPS-841 `_ Upgrade log4j to 2.17.1 as recommended by ONAP SECCOM - `CPS-867 `_ Database port made configurable through env variable DB_PORT + - `CPS-856 `_ Retry mechanism not working for concurrent CmHandle registration Known Limitations, Issues and Workarounds ----------------------------------------- -- 2.16.6