Fix for retry mechanism on concurrent CmHandle registration 49/126949/10
authorputhuparambil.aditya <aditya.puthuparambil@bell.ca>
Thu, 3 Feb 2022 16:42:13 +0000 (16:42 +0000)
committerputhuparambil.aditya <aditya.puthuparambil@bell.ca>
Wed, 9 Feb 2022 09:47:25 +0000 (09:47 +0000)
Issue-ID: CPS-856
Signed-off-by: puthuparambil.aditya <aditya.puthuparambil@bell.ca>
Change-Id: Ie7c0033f2987166315611f8a286ae3978466c75f

cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy [new file with mode: 0644]
cps-ri/src/test/java/org/onap/cps/TestApplication.java
docs/release-notes.rst

index 3e39a05..86d5de6 100755 (executable)
@@ -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<String, String> 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<String, String> newYangResourcesModuleNameToContentMap,
                                           final List<ModuleReference> 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 (file)
index 0000000..085bb33
--- /dev/null
@@ -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 }
+    }
+}
index 0d1df45..075a241 100644 (file)
 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 {
 }
index 058f6ba..3fb0a70 100755 (executable)
@@ -41,6 +41,7 @@ Bug Fixes
    - `CPS-783 <https://jira.onap.org/browse/CPS-783>`_ Remove cm handle does not completely remove all cm handle information
    - `CPS-841 <https://jira.onap.org/browse/CPS-841>`_ Upgrade log4j to 2.17.1 as recommended by ONAP SECCOM
    - `CPS-867 <https://jira.onap.org/browse/CPS-867>`_ Database port made configurable through env variable DB_PORT
+   - `CPS-856 <https://jira.onap.org/browse/CPS-856>`_ Retry mechanism not working for concurrent CmHandle registration
 
 Known Limitations, Issues and Workarounds
 -----------------------------------------