From: Renu Kumari Date: Thu, 10 Feb 2022 14:31:17 +0000 (-0500) Subject: Fix to keep yang resource cache in sync X-Git-Tag: 3.0.0~24 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=cps.git;a=commitdiff_plain;h=21fa4f207e7d36befc49a3f4926dc4f52678a45e Fix to keep yang resource cache in sync - Removed schemaset from cache when schemaset is deleted - Added separate test cases for yang resource cache Issue-ID: CPS-864 Signed-off-by: Renu Kumari Change-Id: Ie1f9978406de1c92b513549216873cba4a98cdd7 --- diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java index e96781786..ffcc5a22f 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java @@ -25,6 +25,7 @@ package org.onap.cps.api.impl; import java.util.Collection; import java.util.List; import java.util.Map; +import lombok.AllArgsConstructor; import org.onap.cps.api.CpsAdminService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.spi.CascadeDeleteAllowed; @@ -38,25 +39,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service("CpsModuleServiceImpl") +@AllArgsConstructor public class CpsModuleServiceImpl implements CpsModuleService { - private CpsModulePersistenceService cpsModulePersistenceService; - private YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; - private CpsAdminService cpsAdminService; - - /** - * Create an instance of CpsModuleServiceImpl. - * - * @param cpsModulePersistenceService cpsModulePersistenceService - * @param yangTextSchemaSourceSetCache yangTextSchemaSourceSetCache - * @param cpsAdminService cpsAdminService - */ - public CpsModuleServiceImpl(final CpsModulePersistenceService cpsModulePersistenceService, - final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache, final CpsAdminService cpsAdminService) { - this.cpsModulePersistenceService = cpsModulePersistenceService; - this.yangTextSchemaSourceSetCache = yangTextSchemaSourceSetCache; - this.cpsAdminService = cpsAdminService; - } + private final CpsModulePersistenceService cpsModulePersistenceService; + private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; + private final CpsAdminService cpsAdminService; @Override public void createSchemaSet(final String dataspaceName, final String schemaSetName, @@ -96,6 +84,7 @@ public class CpsModuleServiceImpl implements CpsModuleService { cpsAdminService.deleteAnchor(dataspaceName, anchor.getName()); } cpsModulePersistenceService.deleteSchemaSet(dataspaceName, schemaSetName); + yangTextSchemaSourceSetCache.removeFromCache(dataspaceName, schemaSetName); cpsModulePersistenceService.deleteUnusedYangResourceModules(); } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java b/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java index 859dab9a9..03b52a308 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/YangTextSchemaSourceSetCache.java @@ -1,12 +1,14 @@ /* - * ============LICENSE_START======================================================= + * ============LICENSE_START======================================================= * Copyright (C) 2021 Pantheon.tech + * Modifications 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. @@ -26,6 +28,7 @@ import org.onap.cps.yang.YangTextSchemaSourceSet; import org.onap.cps.yang.YangTextSchemaSourceSetBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -57,8 +60,8 @@ public class YangTextSchemaSourceSetCache { /** * Updates cache YangTextSchemaSourceSet. * - * @param dataspaceName dataspace name - * @param schemaSetName schema set name + * @param dataspaceName dataspace name + * @param schemaSetName schema set name * @param yangTextSchemaSourceSet yangTextSchemaSourceSet * @return YangTextSchemaSourceSet */ @@ -68,4 +71,17 @@ public class YangTextSchemaSourceSetCache { final YangTextSchemaSourceSet yangTextSchemaSourceSet) { return yangTextSchemaSourceSet; } + + + /** + * Remove the cached value for the given dataspace and schema-set. + * + * @param dataspaceName dataspace name + * @param schemaSetName schema set name + */ + @CacheEvict(key = "#p0.concat('-').concat(#p1)") + public void removeFromCache(final String dataspaceName, final String schemaSetName) { + // Spring provides implementation for removing object from cache + } + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy index b0205705a..67dce1daf 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy @@ -30,33 +30,18 @@ import org.onap.cps.spi.exceptions.SchemaSetInUseException import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.ExtendedModuleReference import org.onap.cps.spi.model.ModuleReference -import org.spockframework.spring.SpringBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.cache.CacheManager -import org.springframework.cache.annotation.EnableCaching -import org.springframework.cache.caffeine.CaffeineCacheManager -import org.springframework.test.context.ContextConfiguration +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import spock.lang.Specification import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED -@SpringBootTest -@EnableCaching -@ContextConfiguration(classes = [YangTextSchemaSourceSetCache, CpsModuleServiceImpl]) class CpsModuleServiceImplSpec extends Specification { - @SpringBean - CpsModulePersistenceService mockModuleStoreService = Mock() + def mockModuleStoreService = Mock(CpsModulePersistenceService) + def mockCpsAdminService = Mock(CpsAdminService) + def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) - @SpringBean - CpsAdminService mockCpsAdminService = Mock() - - @SpringBean - CacheManager cacheManager = new CaffeineCacheManager("yangSchema") - - @Autowired - CpsModuleServiceImpl objectUnderTest + def objectUnderTest = new CpsModuleServiceImpl(mockModuleStoreService, mockYangTextSchemaSourceSetCache, mockCpsAdminService) def 'Create schema set.'() { given: 'Valid yang resource as name-to-content map' @@ -90,7 +75,8 @@ class CpsModuleServiceImplSpec extends Specification { def 'Get schema set by name and dataspace.'() { given: 'an already present schema set' def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') - mockModuleStoreService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> yangResourcesNameToContentMap + and: 'yang resource cache returns the expected schema set' + mockYangTextSchemaSourceSetCache.get('someDataspace', 'someSchemaSet') >> YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap) when: 'get schema set method is invoked' def result = objectUnderTest.getSchemaSet('someDataspace', 'someSchemaSet') then: 'the correct schema set is returned' @@ -99,17 +85,6 @@ class CpsModuleServiceImplSpec extends Specification { result.getExtendedModuleReferences().contains(new ExtendedModuleReference('stores', 'org:onap:ccsdk:sample', '2020-09-15')) } - def 'Schema set caching.'() { - given: 'an schema set' - def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') - when: 'get schema set method is invoked twice' - 2.times { - objectUnderTest.getSchemaSet('someDataspace', 'someSchemaSet') - } - then: 'the persistency service called only once' - 1 * mockModuleStoreService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> yangResourcesNameToContentMap - } - def 'Delete schema-set when cascade is allowed.'() { given: '#numberOfAnchors anchors are associated with schemaset' def associatedAnchors = createAnchors(numberOfAnchors) @@ -120,6 +95,8 @@ class CpsModuleServiceImplSpec extends Specification { numberOfAnchors * mockCpsAdminService.deleteAnchor('my-dataspace', _) and: 'persistence service method is invoked with same parameters' 1 * mockModuleStoreService.deleteSchemaSet('my-dataspace', 'my-schemaset') + and: 'schema set will be removed from the cache' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my-dataspace', 'my-schemaset') and: 'orphan yang resources are deleted' 1 * mockModuleStoreService.deleteUnusedYangResourceModules() where: 'following parameters are used' @@ -135,6 +112,8 @@ class CpsModuleServiceImplSpec extends Specification { 0 * mockCpsAdminService.deleteAnchor(_, _) and: 'persistence service method is invoked with same parameters' 1 * mockModuleStoreService.deleteSchemaSet('my-dataspace', 'my-schemaset') + and: 'schema set will be removed from the cache' + 1 * mockYangTextSchemaSourceSetCache.removeFromCache('my-dataspace', 'my-schemaset') and: 'orphan yang resources are deleted' 1 * mockModuleStoreService.deleteUnusedYangResourceModules() } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy new file mode 100644 index 000000000..860b7399d --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/YangTextSchemaSourceSetCacheSpec.groovy @@ -0,0 +1,133 @@ +/* + * ============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.api.impl + +import org.onap.cps.TestUtils +import org.onap.cps.spi.CpsModulePersistenceService +import org.onap.cps.yang.YangTextSchemaSourceSet +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.spockframework.spring.SpringBean +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.cache.Cache +import org.springframework.cache.CacheManager +import org.springframework.cache.annotation.EnableCaching +import org.springframework.cache.caffeine.CaffeineCacheManager +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +@SpringBootTest +@EnableCaching +@ContextConfiguration(classes = [YangTextSchemaSourceSetCache, CaffeineCacheManager]) +class YangTextSchemaSourceSetCacheSpec extends Specification { + + @SpringBean + CpsModulePersistenceService mockModuleStoreService = Mock() + + @Autowired + YangTextSchemaSourceSetCache objectUnderTest + + @Autowired + CacheManager cacheManager + + Cache yangResourceCacheImpl; + + def setup() { + yangResourceCacheImpl = cacheManager.getCache('yangSchema') + yangResourceCacheImpl.clear() + } + + + def 'Cache Miss: Fetch data from Module persistence'() { + given: 'cache is empty' + yangResourceCacheImpl.clear() + and: 'a schema set exists' + def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + def expectedYangTextSchemaSourceSet = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap) + when: 'schema-set information is asked' + def result = objectUnderTest.get('my-dataspace', 'my-schemaset') + then: 'information fetched from cps module persistence' + 1 * mockModuleStoreService.getYangSchemaResources('my-dataspace', 'my-schemaset') + >> yangResourcesNameToContentMap + and: 'stored in the cache' + def cachedValue = getCachedValue('my-dataspace', 'my-schemaset') + assert cachedValue.getModuleReferences() == expectedYangTextSchemaSourceSet.getModuleReferences() + and: 'the response is as expected' + assert result.getModuleReferences() == expectedYangTextSchemaSourceSet.getModuleReferences() + } + + def 'Cache Hit: Respond from cache'() { + given: 'a schema set exists' + def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + def expectedYangTextSchemaSourceSet = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap) + and: 'stored in cache' + yangResourceCacheImpl.put(getCacheKey('my-dataspace', 'my-schemaset'), expectedYangTextSchemaSourceSet) + when: 'schema-set information is asked' + def result = objectUnderTest.get('my-dataspace', 'my-schemaset') + then: 'expected value is returned' + result.getModuleReferences() == expectedYangTextSchemaSourceSet.getModuleReferences() + and: 'module persistence is not invoked' + 0 * mockModuleStoreService.getYangSchemaResources(_, _) + } + + def 'Cache Update: when no data exist in the cache'() { + given: 'a schema set exists' + def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + def yangTextSchemaSourceSet = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap) + when: 'cache is updated' + objectUnderTest.updateCache('my-dataspace', 'my-schemaset', yangTextSchemaSourceSet) + then: 'cached value is same as expected' + def cachedValue = getCachedValue('my-dataspace', 'my-schemaset') + cachedValue.getModuleReferences() == yangTextSchemaSourceSet.getModuleReferences() + } + + def 'Cache Evict: remove when exist'() { + given: 'a schema set exists in cache' + def yangResourcesNameToContentMap = TestUtils.getYangResourcesAsMap('bookstore.yang') + def yangTextSchemaSourceSet = YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap) + yangResourceCacheImpl.put(getCacheKey('my-dataspace', 'my-schemaset'), yangTextSchemaSourceSet) + def cachedValue = getCachedValue('my-dataspace', 'my-schemaset') + assert cachedValue.getModuleReferences() == yangTextSchemaSourceSet.getModuleReferences() + when: 'cache is evicted for schemaset' + objectUnderTest.removeFromCache('my-dataspace', 'my-schemaset') + then: 'cached does not have value' + assert getCachedValue('my-dataspace', 'my-schemaset') == null + } + + def 'Cache Evict: remove when does not exist'() { + given: 'cache is empty' + yangResourceCacheImpl.clear() + when: 'cache is evicted for schemaset' + objectUnderTest.removeFromCache('my-dataspace', 'my-schemaset') + then: 'cached does not have value' + assert getCachedValue('my-dataspace', 'my-schemaset') == null + } + + def getCachedValue(dataSpace, schemaSet) { + yangResourceCacheImpl.get(getCacheKey(dataSpace, schemaSet), YangTextSchemaSourceSet) + } + + def getCacheKey(dataSpace, schemaSet) { + return new String("${dataSpace}-${schemaSet}") + } + + +}