diff options
Diffstat (limited to 'cps-ri')
8 files changed, 170 insertions, 29 deletions
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java index 51b2482953..5b89d9f4c4 100755 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java @@ -24,9 +24,9 @@ package org.onap.cps.spi.impl; import java.util.Collection; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import javax.transaction.Transactional; +import lombok.AllArgsConstructor; import org.onap.cps.spi.CpsAdminPersistenceService; import org.onap.cps.spi.entities.AnchorEntity; import org.onap.cps.spi.entities.DataspaceEntity; @@ -38,30 +38,19 @@ import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException; import org.onap.cps.spi.model.Anchor; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; @Component +@AllArgsConstructor public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceService { - @Autowired - private DataspaceRepository dataspaceRepository; - - @Autowired - private AnchorRepository anchorRepository; - - @Autowired - private SchemaSetRepository schemaSetRepository; - - @Autowired - private FragmentRepository fragmentRepository; - - @Autowired - private YangResourceRepository yangResourceRepository; + private final DataspaceRepository dataspaceRepository; + private final AnchorRepository anchorRepository; + private final SchemaSetRepository schemaSetRepository; + private final YangResourceRepository yangResourceRepository; @Override public void createDataspace(final String dataspaceName) { @@ -140,7 +129,6 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic @Override public void deleteAnchor(final String dataspaceName, final String anchorName) { final var anchorEntity = getAnchorEntity(dataspaceName, anchorName); - fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity)); anchorRepository.delete(anchorEntity); } 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 2d67d7c953..ed414fc30c 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 @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2020-2021 Bell Canada. + * Modifications Copyright (C) 2020-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. @@ -320,6 +320,14 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService fragmentRepository.save(parentEntity); } + @Override + @Transactional + public void deleteDataNodes(final String dataspaceName, final String anchorName) { + final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName); + anchorRepository.findByDataspaceAndName(dataspaceEntity, anchorName) + .ifPresent( + anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity))); + } @Override @Transactional 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 3e39a05c51..86d5de6d0f 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<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/CpsAdminPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy index 2218014ec1..063bd5b5ae 100644 --- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy +++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy @@ -41,8 +41,7 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { static final String SET_DATA = '/data/anchor.sql' static final String SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES = '/data/anchors-schemaset-modules.sql' static final String DATASPACE_WITH_NO_DATA = 'DATASPACE-002-NO-DATA' - static final Integer DELETED_ANCHOR_ID = 3001 - static final Long DELETED_FRAGMENT_ID = 4001 + static final Integer DELETED_ANCHOR_ID = 3002 @Sql(CLEAR_DATA) def 'Create and retrieve a new dataspace.'() { @@ -150,10 +149,9 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase { @Sql([CLEAR_DATA, SET_DATA]) def 'Delete anchor'() { when: 'delete anchor action is invoked' - objectUnderTest.deleteAnchor(DATASPACE_NAME, ANCHOR_NAME1) - then: 'anchor and associated data fragment are deleted' + objectUnderTest.deleteAnchor(DATASPACE_NAME, ANCHOR_NAME2) + then: 'anchor is deleted' assert anchorRepository.findById(DELETED_ANCHOR_ID).isEmpty() - assert fragmentRepository.findById(DELETED_FRAGMENT_ID).isEmpty() } @Sql([CLEAR_DATA, SET_DATA]) 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 60d7774cca..41e7a419a5 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 @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Nordix Foundation * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021 Bell Canada. + * Modifications Copyright (C) 2021-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. @@ -35,7 +35,6 @@ import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.utils.JsonObjectMapper import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.jdbc.Sql - import javax.validation.ConstraintViolationException import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS @@ -49,6 +48,8 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { static final JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) static final String SET_DATA = '/data/fragment.sql' + static final int DATASPACE_1001_ID = 1001L + static final int ANCHOR_3003_ID = 3003L static final long ID_DATA_NODE_WITH_DESCENDANTS = 4001 static final String XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1' static final String XPATH_DATA_NODE_WITH_LEAVES = '/parent-100' @@ -524,6 +525,20 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase { 'invalid list element' | '/parent-206/child-206/grand-child-206@key="A"]' } + @Sql([CLEAR_DATA, SET_DATA]) + def 'Delete data node for an anchor.'() { + given: 'a data-node exists for an anchor' + assert fragmentsExistInDB(DATASPACE_1001_ID, ANCHOR_3003_ID) + when: 'data nodes are deleted ' + objectUnderTest.deleteDataNodes(DATASPACE_NAME, ANCHOR_NAME3) + then: 'all data-nodes are deleted successfully' + assert !fragmentsExistInDB(DATASPACE_1001_ID, ANCHOR_3003_ID) + } + + def fragmentsExistInDB(dataSpaceId, anchorId) { + !fragmentRepository.findRootsByDataspaceAndAnchor(dataSpaceId, anchorId).isEmpty() + } + static Collection<DataNode> toDataNodes(xpaths) { return xpaths.collect { new DataNodeBuilder().withXpath(it).build() } } 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 0000000000..085bb3340b --- /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 0d1df456ee..075a241fc7 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/cps-ri/src/test/resources/application.yml b/cps-ri/src/test/resources/application.yml index 73292bbfaf..18e6ed6243 100644 --- a/cps-ri/src/test/resources/application.yml +++ b/cps-ri/src/test/resources/application.yml @@ -18,10 +18,12 @@ spring: jpa: ddl-auto: create + show-sql: true properties: hibernate: enable_lazy_load_no_trans: true dialect: org.hibernate.dialect.PostgreSQLDialect + format_sql: true datasource: url: ${DB_URL} |