From d24a4f0aa92d6ea0c83d82bf2448c43d8ebdddaa Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Thu, 22 Apr 2021 12:33:04 +0100 Subject: Refactor data types cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoids potential issue of data type cache changes by external parties, by returning copies from the cache instead of the original. Refactors the code for more clarity. Change-Id: Ibb518bf638f2f4ee1f5e3869baaace374efb632a Issue-ID: SDC-3569 Signed-off-by: André Schmid --- catalog-model/pom.xml | 11 + .../openecomp/sdc/be/model/DataTypeDefinition.java | 19 +- .../sdc/be/model/cache/ApplicationCache.java | 4 +- .../be/model/cache/ApplicationDataTypeCache.java | 333 ++++++++++--------- .../model/operations/impl/AbstractOperation.java | 3 - .../model/operations/impl/PropertyOperation.java | 12 +- .../model/cache/ApplicationDataTypeCacheTest.java | 356 +++++++++++++-------- 7 files changed, 446 insertions(+), 292 deletions(-) diff --git a/catalog-model/pom.xml b/catalog-model/pom.xml index 17b1a0e30b..7666076de6 100644 --- a/catalog-model/pom.xml +++ b/catalog-model/pom.xml @@ -13,6 +13,10 @@ 1.9.0-SNAPSHOT + + 4.0.3 + + com.fasterxml.jackson.core @@ -297,6 +301,13 @@ test + + org.awaitility + awaitility + ${awaitility.version} + test + + org.codehaus.groovy groovy diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/DataTypeDefinition.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/DataTypeDefinition.java index 6c285802d2..b61aa5a4c2 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/DataTypeDefinition.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/DataTypeDefinition.java @@ -37,17 +37,18 @@ public class DataTypeDefinition extends DataTypeDataDefinition { private List constraints; private List properties; - public DataTypeDefinition(DataTypeDataDefinition p) { - super(p); + public DataTypeDefinition(final DataTypeDataDefinition dataTypeDataDefinition) { + super(dataTypeDataDefinition); } - public DataTypeDefinition(DataTypeDefinition pd) { - this.setName(pd.getName()); - this.setDerivedFrom(pd.getDerivedFrom()); - this.setDerivedFromName(pd.getDerivedFromName()); - this.setUniqueId(pd.getUniqueId()); - this.setConstraints(pd.getConstraints()); - this.setDescription(pd.getDescription()); + public DataTypeDefinition(final DataTypeDefinition dataTypeDefinition) { + super(dataTypeDefinition); + this.setName(dataTypeDefinition.getName()); + this.setDerivedFrom(dataTypeDefinition.getDerivedFrom()); + this.setDerivedFromName(dataTypeDefinition.getDerivedFromName()); + this.setUniqueId(dataTypeDefinition.getUniqueId()); + this.setConstraints(dataTypeDefinition.getConstraints()); + this.setDescription(dataTypeDefinition.getDescription()); } public List safeGetConstraints() { diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationCache.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationCache.java index 4c58285c12..d0a071909c 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationCache.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationCache.java @@ -25,7 +25,7 @@ import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus; public interface ApplicationCache { - public abstract Either, JanusGraphOperationStatus> getAll(); + Either, JanusGraphOperationStatus> getAll(); - public abstract Either get(String uniqueId); + Either get(String uniqueId); } diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCache.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCache.java index d6cc01b3ef..a1bafbc6f2 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCache.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCache.java @@ -20,36 +20,33 @@ package org.openecomp.sdc.be.model.cache; import fj.data.Either; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; +import java.util.Optional; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import javax.annotation.Resource; +import lombok.AccessLevel; import lombok.Getter; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.openecomp.sdc.be.config.BeEcompErrorManager; import org.openecomp.sdc.be.config.BeEcompErrorManager.ErrorSeverity; -import org.openecomp.sdc.be.config.Configuration.ApplicationL1CacheConfig; import org.openecomp.sdc.be.config.Configuration.ApplicationL1CacheInfo; import org.openecomp.sdc.be.config.ConfigurationManager; import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus; -import org.openecomp.sdc.be.datatypes.elements.DataTypeDataDefinition; import org.openecomp.sdc.be.model.DataTypeDefinition; import org.openecomp.sdc.be.model.operations.impl.PropertyOperation; import org.openecomp.sdc.be.resources.data.DataTypeData; +import org.openecomp.sdc.common.log.enums.EcompLoggerErrorCode; import org.openecomp.sdc.common.log.wrappers.Logger; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @@ -58,49 +55,68 @@ import org.springframework.stereotype.Component; public class ApplicationDataTypeCache implements ApplicationCache, Runnable { private static final String APPLICATION_DATA_TYPES_CACHE = "ApplicationDataTypesCache"; - private static final Logger log = Logger.getLogger(ApplicationDataTypeCache.class.getName()); - private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); - private final Lock r = rwl.readLock(); - private final Lock w = rwl.writeLock(); - ScheduledFuture scheduledFuture = null; - private Map data = new HashMap<>(); - private ScheduledExecutorService scheduledPollingService = Executors - .newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("ApplicationDataTypeCacheThread-%d").build()); + private static final Logger log = Logger.getLogger(ApplicationDataTypeCache.class); + + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + private final PropertyOperation propertyOperation; + private final ApplicationEventPublisher applicationEventPublisher; + @Getter(AccessLevel.PACKAGE) + private final ScheduledExecutorService scheduledPollingService; + @Getter(AccessLevel.PACKAGE) + private ScheduledFuture scheduledFuture = null; + private Map dataTypesCacheMap = new HashMap<>(); private int firstRunDelayInSec = 30; private int pollingIntervalInSec = 60; - @Resource - private PropertyOperation propertyOperation; - @Autowired - private ApplicationEventPublisher applicationEventPublisher; + + public ApplicationDataTypeCache(final PropertyOperation propertyOperation, final ApplicationEventPublisher applicationEventPublisher) { + this.propertyOperation = propertyOperation; + this.applicationEventPublisher = applicationEventPublisher; + scheduledPollingService = Executors + .newScheduledThreadPool(1, new BasicThreadFactory.Builder().namingPattern("ApplicationDataTypeCacheThread-%d").build()); + } @PostConstruct - public void init() { - ApplicationL1CacheConfig applicationL1CacheConfig = ConfigurationManager.getConfigurationManager().getConfiguration().getApplicationL1Cache(); - if (applicationL1CacheConfig != null) { - if (applicationL1CacheConfig.getDatatypes() != null) { - ApplicationL1CacheInfo datatypesInfo = applicationL1CacheConfig.getDatatypes(); - if (datatypesInfo.getEnabled()) { - Integer intervalInSec = datatypesInfo.getPollIntervalInSec(); - if (intervalInSec != null) { - pollingIntervalInSec = intervalInSec; - } - Integer firstRunDelay = datatypesInfo.getFirstRunDelay(); - if (firstRunDelay != null) { - firstRunDelayInSec = firstRunDelay; - } - log.trace("ApplicationDataTypesCache polling interval is {} seconds.", pollingIntervalInSec); - if (scheduledPollingService != null) { - log.debug("Start ApplicationDataTypeCache polling task. polling interval {} seconds", pollingIntervalInSec); - scheduledFuture = scheduledPollingService - .scheduleAtFixedRate(this, firstRunDelayInSec, pollingIntervalInSec, TimeUnit.SECONDS); - } - } - } else { - BeEcompErrorManager.getInstance().logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Cache is disabled", ErrorSeverity.INFO); - } - } else { - BeEcompErrorManager.getInstance().logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Cache is disabled", ErrorSeverity.INFO); + void init() { + final Optional dataTypeCacheConfigOptional = getDataTypeCacheConfig(); + if (dataTypeCacheConfigOptional.isEmpty()) { + BeEcompErrorManager.getInstance() + .logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Data types cache is not configured and will be disabled", ErrorSeverity.INFO); + return; + } + final ApplicationL1CacheInfo dataTypesCacheInfo = dataTypeCacheConfigOptional.get(); + if (!Boolean.TRUE.equals(dataTypesCacheInfo.getEnabled())) { + BeEcompErrorManager.getInstance().logInternalFlowError(APPLICATION_DATA_TYPES_CACHE, "Data types cache is disabled", ErrorSeverity.INFO); + return; + } + loadConfigurationValues(dataTypesCacheInfo); + if (scheduledPollingService != null) { + log.debug("Starting ApplicationDataTypeCache polling task. Initial delay {}s and polling interval {}s", + firstRunDelayInSec, pollingIntervalInSec); + scheduledFuture = scheduledPollingService + .scheduleAtFixedRate(this, firstRunDelayInSec, pollingIntervalInSec, TimeUnit.SECONDS); + } + } + + private void loadConfigurationValues(final ApplicationL1CacheInfo dataTypesCacheInfo) { + final Integer firstRunDelay = dataTypesCacheInfo.getFirstRunDelay(); + if (firstRunDelay != null) { + firstRunDelayInSec = firstRunDelay; + } + log.trace("ApplicationDataTypesCache initial delay configured to {} seconds.", firstRunDelayInSec); + + final Integer intervalInSec = dataTypesCacheInfo.getPollIntervalInSec(); + if (intervalInSec != null) { + pollingIntervalInSec = intervalInSec; } + log.trace("ApplicationDataTypesCache polling interval configured to {} seconds.", pollingIntervalInSec); + } + + private Optional getDataTypeCacheConfig() { + final var applicationL1CacheConfig = ConfigurationManager.getConfigurationManager().getConfiguration().getApplicationL1Cache(); + if (applicationL1CacheConfig == null || applicationL1CacheConfig.getDatatypes() == null) { + return Optional.empty(); + } + return Optional.ofNullable(applicationL1CacheConfig.getDatatypes()); } @PreDestroy @@ -147,138 +163,164 @@ public class ApplicationDataTypeCache implements ApplicationCache, JanusGraphOperationStatus> getAll() { try { - r.lock(); - if (data == null || data.isEmpty()) { + readWriteLock.readLock().lock(); + if (MapUtils.isEmpty(dataTypesCacheMap)) { return getAllDataTypesFromGraph(); } - return Either.left(data); + return Either.left(new HashMap<>(dataTypesCacheMap)); } finally { - r.unlock(); + readWriteLock.readLock().unlock(); } } @Override public Either get(String uniqueId) { try { - r.lock(); - if (data == null || data.isEmpty()) { + readWriteLock.readLock().lock(); + if (MapUtils.isEmpty(dataTypesCacheMap)) { + return propertyOperation.getDataTypeByUid(uniqueId); + } + + final Optional dataTypeDefinition = dataTypesCacheMap.values().stream() + .filter(p -> p.getUniqueId().equals(uniqueId)).findFirst(); + if (dataTypeDefinition.isEmpty()) { return propertyOperation.getDataTypeByUid(uniqueId); - } else { - DataTypeDefinition dataTypeDefinition = data.values().stream().filter(p -> p.getUniqueId().equals(uniqueId)).findFirst().orElse(null); - if (dataTypeDefinition == null) { - return propertyOperation.getDataTypeByUid(uniqueId); - } else { - return Either.left(dataTypeDefinition); - } } + return Either.left(new DataTypeDefinition(dataTypeDefinition.get())); } finally { - r.unlock(); + readWriteLock.readLock().unlock(); } } @Override public void run() { - log.trace("run() method. polling db to fetch data types"); try { - Long start = System.currentTimeMillis(); - log.trace("Start fetching all data types from db"); - Either, JanusGraphOperationStatus> allDataTypeNodes = propertyOperation.getAllDataTypeNodes(); - Long end = System.currentTimeMillis(); - log.trace("Finish fetching all data types from db. Took {} Milliseconds", (end - start)); - if (allDataTypeNodes.isRight()) { - JanusGraphOperationStatus status = allDataTypeNodes.right().value(); - if (status != JanusGraphOperationStatus.OK) { - log.debug("ApplicationDataTypesCache - Failed to fetch all data types nodes"); - BeEcompErrorManager.getInstance() - .logInternalConnectionError("FetchDataTypes", "Failed to fetch data types from graph(cache)", ErrorSeverity.INFO); - } - } else { - List list = allDataTypeNodes.left().value(); - if (list != null) { - Map> dataTypeNameToModificationTime = list.stream().collect(Collectors - .toMap(p -> p.getDataTypeDataDefinition().getName(), p -> new ImmutablePair<>(p.getDataTypeDataDefinition().getCreationTime(), - p.getDataTypeDataDefinition().getModificationTime()))); - Map> currentDataTypeToModificationTime = new HashMap<>(); - try { - r.lock(); - if (data != null) { - currentDataTypeToModificationTime = data.values().stream().collect(Collectors - .toMap(DataTypeDataDefinition::getName, p -> new ImmutablePair<>(p.getCreationTime(), p.getModificationTime()))); - } - } finally { - r.unlock(); - } - boolean isChanged = compareDataTypes(dataTypeNameToModificationTime, currentDataTypeToModificationTime); - if (isChanged) { - replaceAllData(); - } - } + final long startTime = System.currentTimeMillis(); + log.trace("Starting refresh data types cache job"); + if (hasDataTypesChanged()) { + log.info("Detected changes in the data types, updating the data type cache."); + refreshDataTypesCache(); } - } catch (Exception e) { - log.debug("unexpected error occured", e); - BeEcompErrorManager.getInstance() - .logInternalUnexpectedError(APPLICATION_DATA_TYPES_CACHE, "Failed to run refresh data types job", ErrorSeverity.INFO); + log.trace("Finished refresh data types cache job. Finished in {}ms", (System.currentTimeMillis() - startTime)); + } catch (final Exception e) { + var errorMsg = "Failed to run refresh data types cache job"; + log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg, e); + BeEcompErrorManager.getInstance().logInternalUnexpectedError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.INFO); } finally { try { propertyOperation.getJanusGraphGenericDao().commit(); - } catch (Exception e) { - log.trace("Failed to commit ApplicationDataTypeCache", e); + } catch (final Exception e) { + log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), + "Failed to commit ApplicationDataTypeCache", e); } } } - private boolean compareDataTypes(Map> dataTypeNameToModificationTime, - Map> currentDataTypeToModificationTime) { - if (dataTypeNameToModificationTime.size() != currentDataTypeToModificationTime.size()) { + private boolean hasDataTypesChanged() { + final List dataTypeListFromDatabase = findAllDataTypesLazy(); + final Map dataTypesCacheCopyMap = copyDataTypeCache(); + + if (dataTypeListFromDatabase.size() != dataTypesCacheCopyMap.size()) { + log.debug("Total of cached data types '{}' differs from the actual '{}'", dataTypeListFromDatabase.size(), dataTypesCacheCopyMap.size()); return true; - } else { - Set currentkeySet = currentDataTypeToModificationTime.keySet(); - Set keySet = dataTypeNameToModificationTime.keySet(); - if (currentkeySet.containsAll(keySet)) { - for (Entry> entry : dataTypeNameToModificationTime.entrySet()) { - String dataTypeName = entry.getKey(); - ImmutablePair creationAndModificationTimes = entry.getValue(); - long creationTime = creationAndModificationTimes.getLeft() == null ? 0 : creationAndModificationTimes.getLeft().longValue(); - long modificationTime = creationAndModificationTimes.getRight() == null ? 0 : creationAndModificationTimes.getRight().longValue(); - ImmutablePair currentEntry = currentDataTypeToModificationTime.get(dataTypeName); - long currentCreationTime = currentEntry.getLeft() == null ? 0 : currentEntry.getLeft().longValue(); - long currentModificationTime = currentEntry.getRight() == null ? 0 : currentEntry.getRight().longValue(); - if (creationTime > currentCreationTime || modificationTime > currentModificationTime) { - log.debug("Datatype {} was updated. Creation Time {} vs {}. Modification Time {} vs {}", dataTypeName, currentCreationTime, - creationTime, currentModificationTime, modificationTime); - return true; - } - } - } else { + } + + if (CollectionUtils.isEmpty(dataTypeListFromDatabase)) { + log.debug("Both data type cache and database are empty"); + return false; + } + + return hasDataTypesChanged(dataTypeListFromDatabase, dataTypesCacheCopyMap); + } + + private boolean hasDataTypesChanged(final List dataTypeListFromDatabase, final Map dataTypesCacheCopyMap) { + return dataTypeListFromDatabase.stream().map(DataTypeData::getDataTypeDataDefinition).anyMatch(actualDataTypeDefinition -> { + final String dataTypeName = actualDataTypeDefinition.getName(); + final DataTypeDefinition cachedDataTypeDefinition = dataTypesCacheCopyMap.get(dataTypeName); + if (cachedDataTypeDefinition == null) { + log.debug("Datatype '{}' is not present in the cache. ", dataTypeName); return true; } + + final long cachedCreationTime = cachedDataTypeDefinition.getCreationTime() == null ? 0 : cachedDataTypeDefinition.getCreationTime(); + final long actualCreationTime = actualDataTypeDefinition.getCreationTime() == null ? 0 : actualDataTypeDefinition.getCreationTime(); + if (cachedCreationTime != actualCreationTime) { + log.debug("Datatype '{}' was updated. Cache/database creation time '{}'/'{}'.", + dataTypeName, cachedCreationTime, actualCreationTime); + return true; + } + final long cachedModificationTime = + cachedDataTypeDefinition.getModificationTime() == null ? 0 : cachedDataTypeDefinition.getModificationTime(); + final long actualModificationTime = + actualDataTypeDefinition.getModificationTime() == null ? 0 : actualDataTypeDefinition.getModificationTime(); + if (cachedModificationTime != actualModificationTime) { + log.debug("Datatype '{}' was updated. Cache/database modification time '{}'/'{}'.", + dataTypeName, cachedModificationTime, actualModificationTime); + return true; + } + + return false; + }); + } + + private Map copyDataTypeCache() { + try { + readWriteLock.readLock().lock(); + return new HashMap<>(this.dataTypesCacheMap); + } finally { + readWriteLock.readLock().unlock(); } - return false; } - private void replaceAllData() { - Either, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypes(); + private void refreshDataTypesCache() { + final Map dataTypesDefinitionMap = findAllDataTypesEager(); + if (dataTypesDefinitionMap.isEmpty()) { + return; + } + try { + readWriteLock.writeLock().lock(); + dataTypesCacheMap = dataTypesDefinitionMap; + onDataChangeEventEmit(); + BeEcompErrorManager.getInstance() + .logInternalFlowError("ReplaceDataTypesCache", "Succeed to replace the data types cache", ErrorSeverity.INFO); + } finally { + readWriteLock.writeLock().unlock(); + } + } + + private Map findAllDataTypesEager() { + log.trace("Fetching data types from database, eager mode"); + final long startTime = System.currentTimeMillis(); + final Either, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypes(); + log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime)); if (allDataTypes.isRight()) { - JanusGraphOperationStatus status = allDataTypes.right().value(); - log.debug("Failed to fetch all data types from db. Status is {}", status); - } else { - try { - w.lock(); - data = allDataTypes.left().value(); - // send notification on data types change - onDataChangeEventEmit(data); - BeEcompErrorManager.getInstance() - .logInternalFlowError("ReplaceDataTypesCache", "Succeed to replace the data types cache", ErrorSeverity.INFO); - } finally { - w.unlock(); - } + final JanusGraphOperationStatus status = allDataTypes.right().value(); + var errorMsg= String.format("Failed to fetch data types from database. Status is %s", status); + log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg); + BeEcompErrorManager.getInstance().logInternalConnectionError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.ERROR); + return Collections.emptyMap(); } + return allDataTypes.left().value(); } - private void onDataChangeEventEmit(Map newData) { - log.trace("Cache data has changed, sending event to all listening for this change."); - DataTypesCacheChangedEvent dataTypesCacheChangedEvent = new DataTypesCacheChangedEvent(this, newData); - applicationEventPublisher.publishEvent(dataTypesCacheChangedEvent); + private List findAllDataTypesLazy() { + log.trace("Fetching data types from database, lazy mode"); + final long startTime = System.currentTimeMillis(); + final Either, JanusGraphOperationStatus> allDataTypes = propertyOperation.getAllDataTypeNodes(); + log.trace("Finish fetching data types from database. Took {}ms", (System.currentTimeMillis() - startTime)); + if (allDataTypes.isRight()) { + final JanusGraphOperationStatus status = allDataTypes.right().value(); + var errorMsg= String.format("Failed to fetch data types from database. Status is %s", status); + log.error(EcompLoggerErrorCode.UNKNOWN_ERROR, ApplicationDataTypeCache.class.getName(), errorMsg); + BeEcompErrorManager.getInstance().logInternalConnectionError(APPLICATION_DATA_TYPES_CACHE, errorMsg, ErrorSeverity.ERROR); + return Collections.emptyList(); + } + return allDataTypes.left().value(); + } + + private void onDataChangeEventEmit() { + log.trace("Data type cache has changed, sending DataTypesCacheChangedEvent."); + applicationEventPublisher.publishEvent(new DataTypesCacheChangedEvent(this, copyDataTypeCache())); } /** @@ -287,11 +329,12 @@ public class ApplicationDataTypeCache implements ApplicationCache newData; + private final Map newData; - public DataTypesCacheChangedEvent(Object source, Map newData) { + public DataTypesCacheChangedEvent(final Object source, final Map newData) { super(source); this.newData = newData; } } + } diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/AbstractOperation.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/AbstractOperation.java index 1592782bfa..db379f5915 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/AbstractOperation.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/AbstractOperation.java @@ -42,7 +42,6 @@ import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum; import org.openecomp.sdc.be.model.DataTypeDefinition; import org.openecomp.sdc.be.model.IComplexDefaultValue; import org.openecomp.sdc.be.model.PropertyConstraint; -import org.openecomp.sdc.be.model.cache.ApplicationDataTypeCache; import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus; import org.openecomp.sdc.be.model.operations.impl.PropertyOperation.PropertyConstraintDeserialiser; import org.openecomp.sdc.be.model.tosca.ToscaPropertyType; @@ -60,8 +59,6 @@ public abstract class AbstractOperation { @Autowired protected HealingJanusGraphGenericDao janusGraphGenericDao; protected Gson gson = new Gson(); - @Autowired - protected ApplicationDataTypeCache applicationDataTypeCache; protected DataTypeValidatorConverter dataTypeValidatorConverter = DataTypeValidatorConverter.getInstance(); public JanusGraphOperationStatus findAllResourceElementsDefinitionRecursively(String resourceId, diff --git a/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/PropertyOperation.java b/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/PropertyOperation.java index 790646754d..06323644b7 100644 --- a/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/PropertyOperation.java +++ b/catalog-model/src/main/java/org/openecomp/sdc/be/model/operations/impl/PropertyOperation.java @@ -1562,14 +1562,10 @@ public class PropertyOperation extends AbstractOperation implements IPropertyOpe } public Either, JanusGraphOperationStatus> getAllDataTypeNodes() { - Either, JanusGraphOperationStatus> getAllDataTypes = janusGraphGenericDao - .getByCriteria(NodeTypeEnum.DataType, null, DataTypeData.class); - if (getAllDataTypes.isRight()) { - JanusGraphOperationStatus status = getAllDataTypes.right().value(); - if (status == JanusGraphOperationStatus.NOT_FOUND) { - status = JanusGraphOperationStatus.OK; - return Either.right(status); - } + final Either, JanusGraphOperationStatus> getAllDataTypes = + janusGraphGenericDao.getByCriteria(NodeTypeEnum.DataType, null, DataTypeData.class); + if (getAllDataTypes.isRight() && getAllDataTypes.right().value() == JanusGraphOperationStatus.NOT_FOUND) { + return Either.left(Collections.emptyList()); } return getAllDataTypes; } diff --git a/catalog-model/src/test/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCacheTest.java b/catalog-model/src/test/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCacheTest.java index 7186d2a8bf..9126b64659 100644 --- a/catalog-model/src/test/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCacheTest.java +++ b/catalog-model/src/test/java/org/openecomp/sdc/be/model/cache/ApplicationDataTypeCacheTest.java @@ -7,9 +7,9 @@ * 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. @@ -20,175 +20,281 @@ package org.openecomp.sdc.be.model.cache; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + import fj.data.Either; -import mockit.Deencapsulation; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.junit.Before; -import org.junit.Test; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.openecomp.sdc.be.config.Configuration; +import org.openecomp.sdc.be.config.Configuration.ApplicationL1CacheConfig; +import org.openecomp.sdc.be.config.Configuration.ApplicationL1CacheInfo; +import org.openecomp.sdc.be.config.ConfigurationManager; import org.openecomp.sdc.be.dao.janusgraph.JanusGraphOperationStatus; +import org.openecomp.sdc.be.datatypes.elements.DataTypeDataDefinition; import org.openecomp.sdc.be.model.DataTypeDefinition; import org.openecomp.sdc.be.model.operations.impl.PropertyOperation; import org.openecomp.sdc.be.resources.data.DataTypeData; -import org.openecomp.sdc.be.unittests.utils.ModelConfDependentTest; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; import org.springframework.context.ApplicationEventPublisher; -public class ApplicationDataTypeCacheTest extends ModelConfDependentTest{ +class ApplicationDataTypeCacheTest { + + @Mock + private PropertyOperation propertyOperation; + + @Mock + private ApplicationEventPublisher applicationEventPublisher; - @InjectMocks - private ApplicationDataTypeCache testSubject; - - @Mock - PropertyOperation propertyOperation; + @InjectMocks + private ApplicationDataTypeCache applicationDataTypeCache; - @Mock - ApplicationEventPublisher applicationEventPublisher; + private Map dataTypeDefinitionMap; - @Before - public void setUpMocks() throws Exception { - MockitoAnnotations.initMocks(this); + private int schedulerFirstRunDelay = 0; + private int schedulerPollIntervalInSec = 2; + private boolean schedulerIsEnabled = true; + + @BeforeEach + public void beforeEach() { + MockitoAnnotations.openMocks(this); } + @AfterEach + public void afterEach() { + final ScheduledExecutorService scheduledPollingService = applicationDataTypeCache.getScheduledPollingService(); + if (scheduledPollingService == null) { + return; + } + + if (scheduledPollingService.isShutdown()) { + return; + } + + scheduledPollingService.shutdownNow(); + } @Test - public void testInit() throws Exception { - testSubject.init(); + void testInitSuccess() { + defaultInit(); + assertNotNull(applicationDataTypeCache.getScheduledFuture(), "The job should have been triggered"); } @Test - public void testDestroy() throws Exception { - testSubject.init(); - Deencapsulation.invoke(testSubject, "destroy"); + void testDestroySuccess() { + defaultInit(); + assertNotNull(applicationDataTypeCache.getScheduledFuture(), "The job should have been triggered"); + applicationDataTypeCache.destroy(); + assertNull(applicationDataTypeCache.getScheduledFuture(), "The job should have been stopped"); + assertTrue(applicationDataTypeCache.getScheduledPollingService().isShutdown(), "The scheduler should have been stopped"); } @Test - public void testShutdownExecutor() throws Exception { + void testDestroyWithoutSchedulerInitialization() { + mockEmptyConfiguration(); + applicationDataTypeCache.init(); + assertNotNull(applicationDataTypeCache.getScheduledPollingService(), "The scheduler should have been created"); + assertFalse(applicationDataTypeCache.getScheduledPollingService().isShutdown(), "The scheduler should have been running"); + assertNull(applicationDataTypeCache.getScheduledFuture(), "The job should not have been triggered"); + applicationDataTypeCache.destroy(); + assertTrue(applicationDataTypeCache.getScheduledPollingService().isShutdown(), "The scheduler should have been stopped"); + } - // default test - Deencapsulation.invoke(testSubject, "shutdownExecutor"); + @Test + void testInitEmptyConfiguration() { + mockEmptyConfiguration(); + applicationDataTypeCache.init(); + assertNull(applicationDataTypeCache.getScheduledFuture(), "The scheduler should not have started"); } @Test - public void testGetAllDataTypesFromGraph() throws Exception { - Either, JanusGraphOperationStatus> result; + void testInitCacheDisabled() { + final var applicationL1CacheInfo = new ApplicationL1CacheInfo(); + applicationL1CacheInfo.setEnabled(false); + mockConfiguration(applicationL1CacheInfo); + applicationDataTypeCache.init(); + assertNull(applicationDataTypeCache.getScheduledFuture(), "The scheduler should not have started"); + } - // default test - result = Deencapsulation.invoke(testSubject, "getAllDataTypesFromGraph"); + @Test + void testGetAllAfterInitialization() { + defaultInit(); + final ScheduledFuture scheduledFuture = applicationDataTypeCache.getScheduledFuture(); + //waiting the cache to be filled + await().atMost(Duration.ofSeconds(schedulerPollIntervalInSec + 1)).until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) != 0); + assertDataTypeCache(dataTypeDefinitionMap); } @Test - public void testGetAll() throws Exception { - Either, JanusGraphOperationStatus> result; + void testCacheChangeWithDataTypeChange() { + defaultInit(); + final ScheduledFuture scheduledFuture = applicationDataTypeCache.getScheduledFuture(); + //waiting the cache to be filled + await().atMost(Duration.ofSeconds(schedulerPollIntervalInSec + 1)).until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) != 0); + assertDataTypeCache(dataTypeDefinitionMap); + + final DataTypeDefinition testDataType1 = createDataTypeDefinition("test.data.type1", "test.data.type1", 101L, 1000L); + final DataTypeDefinition testDataType2 = createDataTypeDefinition("test.data.type2", "test.data.type2", 101L, 1002L); + final Map modifiedDataTypeDefinitionMap = + Map.of(testDataType1.getName(), testDataType1, testDataType2.getName(), testDataType2); + when(propertyOperation.getAllDataTypes()).thenReturn(Either.left(modifiedDataTypeDefinitionMap)); + + final DataTypeData dataTypeData1 = createDataTypeData("test.data.type1", "test.data.type1", 101L, 101L); + final DataTypeData dataTypeData2 = createDataTypeData("test.data.type2", "test.data.type2", 101L, 1002L); - // default test - result = testSubject.getAll(); + when(propertyOperation.getAllDataTypeNodes()).thenReturn(Either.left(List.of(dataTypeData1, dataTypeData2))); + + await().atMost(Duration.ofSeconds(schedulerPollIntervalInSec + 1)).until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) == 0); + await().atMost(Duration.ofSeconds(schedulerPollIntervalInSec + 1)).until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) != 0); + assertDataTypeCache(modifiedDataTypeDefinitionMap); } @Test - public void testGet() throws Exception { - String uniqueId = ""; - Either result; + void testCacheChangeWithAddedDataType() { + defaultInit(); + final ScheduledFuture scheduledFuture = applicationDataTypeCache.getScheduledFuture(); + //waiting the cache to be filled + await().until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) != 0); + assertDataTypeCache(dataTypeDefinitionMap); + + final Map modifiedDataTypeDefinitionMap = new HashMap<>(); + final DataTypeDefinition testDataType1 = createDataTypeDefinition("test.data.type1", "test.data.type1", 1L, 1L); + modifiedDataTypeDefinitionMap.put(testDataType1.getName(), testDataType1); + final DataTypeDefinition testDataType3 = createDataTypeDefinition("test.data.type3", "test.data.type3", 1L, 1L); + modifiedDataTypeDefinitionMap.put(testDataType3.getName(), testDataType3); + when(propertyOperation.getAllDataTypes()).thenReturn(Either.left(modifiedDataTypeDefinitionMap)); + + final DataTypeData dataTypeData1 = createDataTypeData("test.data.type1", "test.data.type1", 1L, 1L); + final DataTypeData dataTypeData3 = createDataTypeData("test.data.type3", "test.data.type3", 1L, 1L); - // default test - result = testSubject.get(uniqueId); + when(propertyOperation.getAllDataTypeNodes()).thenReturn(Either.left(List.of(dataTypeData1, dataTypeData3))); + + await().atMost(Duration.ofSeconds(schedulerPollIntervalInSec + 1)).until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) == 0); + await().atMost(Duration.ofSeconds(schedulerPollIntervalInSec + 1)).until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) != 0); + assertDataTypeCache(modifiedDataTypeDefinitionMap); } @Test - public void testGet2() throws Exception { - String uniqueId = ""; - Either result; - - HashMap a = new HashMap<>(); - DataTypeDefinition value1 = new DataTypeDefinition(); - value1.setUniqueId("mock"); - a.put("mock", value1); - Either, JanusGraphOperationStatus> value = Either.left(a); - Mockito.when(propertyOperation.getAllDataTypes()).thenReturn(value); - // default test - Deencapsulation.invoke(testSubject, "replaceAllData"); - result = testSubject.get(uniqueId); - } - - @Test - public void testRun() throws Exception { - testSubject.run(); + void testGetAllWithNoInitialization() { + final Map dataTypeDefinitionMap = new HashMap<>(); + when(propertyOperation.getAllDataTypes()).thenReturn(Either.left(dataTypeDefinitionMap)); + final Either, JanusGraphOperationStatus> response = applicationDataTypeCache.getAll(); + assertNotNull(response); + assertTrue(response.isLeft()); } @Test - public void testRun2() throws Exception { - Either, JanusGraphOperationStatus> value = Either.right( - JanusGraphOperationStatus.GENERAL_ERROR); - Mockito.when(propertyOperation.getAllDataTypeNodes()).thenReturn(value); - testSubject.run(); + void testGetWhenCacheIsEmpty() { + var dataTypeDefinition = new DataTypeDefinition(); + when(propertyOperation.getDataTypeByUid("uniqueId")).thenReturn(Either.left(dataTypeDefinition)); + final Either dataTypeEither = applicationDataTypeCache.get("uniqueId"); + assertNotNull(dataTypeEither); + assertTrue(dataTypeEither.isLeft()); + assertEquals(dataTypeDefinition, dataTypeEither.left().value()); } - - @Test - public void testRun3() throws Exception { - LinkedList a = new LinkedList<>(); - a.add(new DataTypeData()); - Either, JanusGraphOperationStatus> value = Either.left(a); - Mockito.when(propertyOperation.getAllDataTypeNodes()).thenReturn(value); - - HashMap a1 = new HashMap<>(); - DataTypeDefinition value1 = new DataTypeDefinition(); - value1.setUniqueId("mock"); - a1.put("mock", value1); - Either, JanusGraphOperationStatus> value2 = Either.left(a1); - Mockito.when(propertyOperation.getAllDataTypes()).thenReturn(value2); - - Deencapsulation.invoke(testSubject, "replaceAllData"); - testSubject.run(); - } - + @Test - public void testCompareDataTypes() throws Exception { - Map> dataTypeNameToModificationTime = new HashMap<>(); - Map> currentDataTypeToModificationTime = new HashMap<>(); - boolean result; + void testGetCacheHit() { + defaultInit(); + final ScheduledFuture scheduledFuture = applicationDataTypeCache.getScheduledFuture(); + await().atMost(Duration.ofSeconds(schedulerPollIntervalInSec + 1)).until(() -> scheduledFuture.getDelay(TimeUnit.SECONDS) != 0); + final Either dataTypeEither = applicationDataTypeCache.get("test.data.type1"); + assertNotNull(dataTypeEither); + assertTrue(dataTypeEither.isLeft()); + final DataTypeDefinition actualDataTypeDefinition = dataTypeEither.left().value(); + final DataTypeDefinition expectedDataTypeDefinition = dataTypeDefinitionMap.get("test.data.type1"); + assertEquals(expectedDataTypeDefinition.getName(), actualDataTypeDefinition.getName()); + assertEquals(expectedDataTypeDefinition.getUniqueId(), actualDataTypeDefinition.getUniqueId()); + assertEquals(expectedDataTypeDefinition.getCreationTime(), actualDataTypeDefinition.getCreationTime()); + assertEquals(expectedDataTypeDefinition.getModificationTime(), actualDataTypeDefinition.getModificationTime()); + } + + private void defaultInit() { + var applicationL1CacheInfo = new ApplicationL1CacheInfo(); + applicationL1CacheInfo.setEnabled(schedulerIsEnabled); + applicationL1CacheInfo.setFirstRunDelay(schedulerFirstRunDelay); + applicationL1CacheInfo.setPollIntervalInSec(schedulerPollIntervalInSec); + mockConfiguration(applicationL1CacheInfo); + + dataTypeDefinitionMap = new HashMap<>(); + final DataTypeDefinition testDataType1 = createDataTypeDefinition("test.data.type1", "test.data.type1", 100L, 1000L); + dataTypeDefinitionMap.put(testDataType1.getName(), testDataType1); + final DataTypeDefinition testDataType2 = createDataTypeDefinition("test.data.type2", "test.data.type2", 101L, 1001L); + dataTypeDefinitionMap.put(testDataType2.getName(), testDataType2); + when(propertyOperation.getAllDataTypes()).thenReturn(Either.left(dataTypeDefinitionMap)); - // default test - result = Deencapsulation.invoke(testSubject, "compareDataTypes", dataTypeNameToModificationTime, currentDataTypeToModificationTime); + final DataTypeData dataTypeData1 = createDataTypeData("test.data.type1", testDataType1.getName(), 100L, 1000L); + final DataTypeData dataTypeData2 = createDataTypeData("test.data.type2", testDataType2.getName(), 101L, 1001L); + + when(propertyOperation.getAllDataTypeNodes()).thenReturn(Either.left(List.of(dataTypeData1, dataTypeData2))); + applicationDataTypeCache.init(); + } + + private DataTypeDefinition createDataTypeDefinition(String name, String uniqueId, long creationTime, long modificationTime) { + final DataTypeDefinition dataTypeDefinition = new DataTypeDefinition(); + dataTypeDefinition.setName(name); + dataTypeDefinition.setUniqueId(uniqueId); + dataTypeDefinition.setCreationTime(creationTime); + dataTypeDefinition.setModificationTime(modificationTime); + return dataTypeDefinition; + } + + private DataTypeData createDataTypeData(String name, String uniqueId, long creationTime, long modificationTime) { + final DataTypeData dataTypeData1 = new DataTypeData(); + dataTypeData1.setDataTypeDataDefinition(createDataTypeDataDefinition(name, uniqueId, creationTime, modificationTime)); + return dataTypeData1; + } + private DataTypeDataDefinition createDataTypeDataDefinition(String name, String uniqueId, long creationTime, long modificationTime) { + final DataTypeDataDefinition testDataType1DataDefinition = new DataTypeDataDefinition(); + testDataType1DataDefinition.setName(name); + testDataType1DataDefinition.setUniqueId(uniqueId); + testDataType1DataDefinition.setCreationTime(creationTime); + testDataType1DataDefinition.setModificationTime(modificationTime); + return testDataType1DataDefinition; + } + + private void mockConfiguration(final ApplicationL1CacheInfo applicationL1CacheInfo) { + final var applicationL1CacheConfig = new ApplicationL1CacheConfig(); + applicationL1CacheConfig.setDatatypes(applicationL1CacheInfo); + final var configuration = new Configuration(); + configuration.setApplicationL1Cache(applicationL1CacheConfig); + final var configurationManager = new ConfigurationManager(); + configurationManager.setConfiguration(configuration); } - @Test - public void testCompareDataTypes2() throws Exception { - Map> dataTypeNameToModificationTime = new HashMap<>(); - Map> currentDataTypeToModificationTime = new HashMap<>(); - boolean result; - - currentDataTypeToModificationTime.put("mock", ImmutablePair.of(1L, 2L)); - dataTypeNameToModificationTime.put("mock", ImmutablePair.of(5L, 6L)); - - // default test - result = Deencapsulation.invoke(testSubject, "compareDataTypes", dataTypeNameToModificationTime, currentDataTypeToModificationTime); - } - - @Test - public void testReplaceAllData() throws Exception { - HashMap a = new HashMap<>(); - DataTypeDefinition value1 = new DataTypeDefinition(); - value1.setUniqueId("mock"); - a.put("mock", value1); - Either, JanusGraphOperationStatus> value = Either.left(a); - Mockito.when(propertyOperation.getAllDataTypes()).thenReturn(value); - // default test - Deencapsulation.invoke(testSubject, "replaceAllData"); - } - - @Test - public void testReplaceAllData2() throws Exception { - Either, JanusGraphOperationStatus> value = Either.right( - JanusGraphOperationStatus.GENERAL_ERROR); - Mockito.when(propertyOperation.getAllDataTypes()).thenReturn(value); - // default test - Deencapsulation.invoke(testSubject, "replaceAllData"); + private void mockEmptyConfiguration() { + final var applicationL1CacheConfig = new ApplicationL1CacheConfig(); + final var configuration = new Configuration(); + configuration.setApplicationL1Cache(applicationL1CacheConfig); + final var configurationManager = new ConfigurationManager(); + configurationManager.setConfiguration(configuration); + } + + public void assertDataTypeCache(final Map expectedDataTypeCache) { + Either, JanusGraphOperationStatus> dataTypeCacheMapEither = applicationDataTypeCache.getAll(); + assertNotNull(dataTypeCacheMapEither); + assertTrue(dataTypeCacheMapEither.isLeft()); + final Map actualDataTypeMap = dataTypeCacheMapEither.left().value(); + expectedDataTypeCache.forEach((dataType, dataTypeDefinition) -> { + final DataTypeDefinition actualDataTypeDefinition = actualDataTypeMap.get(dataType); + assertNotNull(actualDataTypeDefinition); + assertEquals(dataTypeDefinition.getCreationTime(), actualDataTypeDefinition.getCreationTime()); + assertEquals(dataTypeDefinition.getModificationTime(), actualDataTypeDefinition.getModificationTime()); + }); } } -- cgit 1.2.3-korg