diff --git a/pom.xml b/pom.xml
index bf7c01df..90353653 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,6 +28,16 @@
https://d2jrmugq4soldf.cloudfront.net/snapshots
+
+
+ true
+
+
+ false
+
+ central
+ https://repo1.maven.org/maven2
+
diff --git a/src/main/java/com/aws/greengrass/logmanager/LogManagerService.java b/src/main/java/com/aws/greengrass/logmanager/LogManagerService.java
index 8881d172..04243f47 100644
--- a/src/main/java/com/aws/greengrass/logmanager/LogManagerService.java
+++ b/src/main/java/com/aws/greengrass/logmanager/LogManagerService.java
@@ -62,6 +62,7 @@
import static com.aws.greengrass.componentmanager.KernelConfigResolver.CONFIGURATION_CONFIG_KEY;
import static com.aws.greengrass.logmanager.LogManagerService.LOGS_UPLOADER_SERVICE_TOPICS;
+import static com.aws.greengrass.logmanager.util.ConfigUtil.updateFromMapWhenChanged;
@ImplementsService(name = LOGS_UPLOADER_SERVICE_TOPICS, version = "2.0.0")
@@ -404,7 +405,8 @@ private void loadProcessingFilesConfigDeprecated(String componentName) {
.lookupTopics(PERSISTED_COMPONENT_CURRENT_PROCESSING_FILE_INFORMATION, componentName);
- if (currentProcessingComponentTopicsDeprecated != null
+ if (isDeprecatedVersionSupported()
+ && currentProcessingComponentTopicsDeprecated != null
&& !currentProcessingComponentTopicsDeprecated.isEmpty()) {
CurrentProcessingFileInformation processingFileInformation =
CurrentProcessingFileInformation.builder().build();
@@ -520,15 +522,18 @@ private void handleCloudWatchAttemptStatus(CloudWatchAttempt cloudWatchAttempt)
// Update the runtime configuration and store the last processed file information
context.runOnPublishQueueAndWait(() -> {
processingFilesInformation.forEach((componentName, processingFiles) -> {
- // Update old config value to handle downgrade from v 2.3.1 to older ones
- Topics componentTopicsDeprecated =
- getRuntimeConfig().lookupTopics(PERSISTED_COMPONENT_CURRENT_PROCESSING_FILE_INFORMATION,
- componentName);
-
- if (processingFiles.getMostRecentlyUsed() != null) {
- componentTopicsDeprecated.updateFromMap(
- processingFiles.getMostRecentlyUsed().convertToMapOfObjects(),
- new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.REPLACE, System.currentTimeMillis()));
+ if (isDeprecatedVersionSupported()) {
+ // Update old config value to handle downgrade from v 2.3.1 to older ones
+ Topics componentTopicsDeprecated =
+ getRuntimeConfig().lookupTopics(PERSISTED_COMPONENT_CURRENT_PROCESSING_FILE_INFORMATION,
+ componentName);
+
+ if (processingFiles.getMostRecentlyUsed() != null) {
+ updateFromMapWhenChanged(componentTopicsDeprecated,
+ processingFiles.getMostRecentlyUsed().convertToMapOfObjects(),
+ new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.REPLACE,
+ System.currentTimeMillis()));
+ }
}
// Handle version 2.3.1 and above
@@ -537,7 +542,7 @@ private void handleCloudWatchAttemptStatus(CloudWatchAttempt cloudWatchAttempt)
getRuntimeConfig().lookupTopics(PERSISTED_COMPONENT_CURRENT_PROCESSING_FILE_INFORMATION_V2,
componentName);
- componentTopics.updateFromMap(processingFiles.toMap(),
+ updateFromMapWhenChanged(componentTopics, processingFiles.toMap(),
new UpdateBehaviorTree(UpdateBehaviorTree.UpdateBehavior.REPLACE, System.currentTimeMillis()));
});
});
@@ -552,6 +557,10 @@ private void handleCloudWatchAttemptStatus(CloudWatchAttempt cloudWatchAttempt)
isCurrentlyUploading.set(false);
}
+ private boolean isDeprecatedVersionSupported() {
+ return Coerce.toBoolean(getConfig().findOrDefault(true, CONFIGURATION_CONFIG_KEY, "deprecatedVersionSupport"));
+ }
+
/**
* Remove the processing files information for a given component from memory and disk (runtime config).
* @param componentName - the name of a component
diff --git a/src/main/java/com/aws/greengrass/logmanager/util/ConfigUtil.java b/src/main/java/com/aws/greengrass/logmanager/util/ConfigUtil.java
new file mode 100644
index 00000000..f2a119ec
--- /dev/null
+++ b/src/main/java/com/aws/greengrass/logmanager/util/ConfigUtil.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.aws.greengrass.logmanager.util;
+
+import com.aws.greengrass.config.CaseInsensitiveString;
+import com.aws.greengrass.config.Node;
+import com.aws.greengrass.config.Topic;
+import com.aws.greengrass.config.Topics;
+import com.aws.greengrass.config.UnsupportedInputTypeException;
+import com.aws.greengrass.config.UpdateBehaviorTree;
+import com.aws.greengrass.logging.api.Logger;
+import com.aws.greengrass.logging.impl.LogManager;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public final class ConfigUtil {
+ private static final Logger logger = LogManager.getLogger(ConfigUtil.class);
+
+ private ConfigUtil() {
+ }
+
+ /**
+ * Same as topics.updateFromMap, but only makes the update when the value actually changes, skipping any unnecessary
+ * timestampUpdated events. Ideally this code would exist in Topics, but it isn't, so we need to do this in order to
+ * maintain compatibility.
+ *
+ * @param topics Topics to update with values from the map
+ * @param newValues the new value to apply
+ * @param ubt update behavior tree
+ */
+ public static void updateFromMapWhenChanged(Topics topics, Map newValues, UpdateBehaviorTree ubt) {
+ Set childrenToRemove = new HashSet<>(topics.children.keySet());
+
+ newValues.forEach((okey, value) -> {
+ CaseInsensitiveString key = new CaseInsensitiveString(okey);
+ childrenToRemove.remove(key);
+ updateChild(topics, key, value, ubt);
+ });
+
+ childrenToRemove.forEach(childName -> {
+ UpdateBehaviorTree childMergeBehavior = ubt.getChildBehavior(childName.toString());
+
+ // remove the existing child if its merge behavior is REPLACE
+ if (childMergeBehavior.getBehavior() == UpdateBehaviorTree.UpdateBehavior.REPLACE) {
+ topics.remove(topics.children.get(childName));
+ }
+ });
+ }
+
+ private static void updateChild(Topics t, CaseInsensitiveString key, Object value,
+ @NonNull UpdateBehaviorTree mergeBehavior) {
+ UpdateBehaviorTree childMergeBehavior = mergeBehavior.getChildBehavior(key.toString());
+
+ Node existingChild = t.children.get(key);
+ // if new node is a container node
+ if (value instanceof Map) {
+ // if existing child is a container node
+ if (existingChild == null || existingChild instanceof Topics) {
+ updateFromMapWhenChanged(t.createInteriorChild(key.toString()), (Map) value, childMergeBehavior);
+ } else {
+ t.remove(existingChild);
+ Topics newNode = t.createInteriorChild(key.toString(), mergeBehavior.getTimestampToUse());
+ updateFromMapWhenChanged(newNode, (Map) value, childMergeBehavior);
+ }
+ // if new node is a leaf node
+ } else {
+ try {
+ if (existingChild == null || existingChild instanceof Topic) {
+ Topic node = t.createLeafChild(key.toString());
+ if (!Objects.equals(node.getOnce(), value)) {
+ node.withValueChecked(childMergeBehavior.getTimestampToUse(), value);
+ }
+ } else {
+ t.remove(existingChild);
+ Topic newNode = t.createLeafChild(key.toString());
+ newNode.withValueChecked(childMergeBehavior.getTimestampToUse(), value);
+ }
+ } catch (UnsupportedInputTypeException e) {
+ logger.error("Should never fail in updateChild", e);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/aws/greengrass/logmanager/LogManagerServiceTest.java b/src/test/java/com/aws/greengrass/logmanager/LogManagerServiceTest.java
index 3a2c49df..ddd8e00a 100644
--- a/src/test/java/com/aws/greengrass/logmanager/LogManagerServiceTest.java
+++ b/src/test/java/com/aws/greengrass/logmanager/LogManagerServiceTest.java
@@ -5,20 +5,27 @@
package com.aws.greengrass.logmanager;
+import com.aws.greengrass.config.Configuration;
import com.aws.greengrass.config.Topic;
import com.aws.greengrass.config.Topics;
import com.aws.greengrass.config.UnsupportedInputTypeException;
import com.aws.greengrass.config.UpdateBehaviorTree;
import com.aws.greengrass.dependency.Context;
-import com.aws.greengrass.dependency.Crashable;
import com.aws.greengrass.logging.impl.LogManager;
import com.aws.greengrass.logging.impl.config.LogConfig;
import com.aws.greengrass.logging.impl.config.LogStore;
import com.aws.greengrass.logging.impl.config.model.LogConfigUpdate;
import com.aws.greengrass.logmanager.exceptions.InvalidLogGroupException;
-import com.aws.greengrass.logmanager.model.*;
+import com.aws.greengrass.logmanager.model.CloudWatchAttempt;
+import com.aws.greengrass.logmanager.model.CloudWatchAttemptLogFileInformation;
+import com.aws.greengrass.logmanager.model.CloudWatchAttemptLogInformation;
+import com.aws.greengrass.logmanager.model.ComponentLogConfiguration;
+import com.aws.greengrass.logmanager.model.ComponentLogFileInformation;
+import com.aws.greengrass.logmanager.model.ComponentType;
+import com.aws.greengrass.logmanager.model.LogFile;
+import com.aws.greengrass.logmanager.model.LogFileGroup;
+import com.aws.greengrass.logmanager.model.ProcessingFiles;
import com.aws.greengrass.testcommons.testutilities.GGExtension;
-import com.aws.greengrass.testcommons.testutilities.GGServiceTestUtil;
import com.aws.greengrass.util.Coerce;
import com.aws.greengrass.util.NucleusPaths;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
@@ -27,7 +34,6 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -91,7 +97,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.lenient;
@@ -101,7 +106,7 @@
@ExtendWith({MockitoExtension.class, GGExtension.class})
@SuppressWarnings("PMD.ExcessiveClassLength")
-class LogManagerServiceTest extends GGServiceTestUtil {
+class LogManagerServiceTest {
@Mock
private CloudWatchLogsUploader mockUploader;
@Mock
@@ -110,10 +115,6 @@ class LogManagerServiceTest extends GGServiceTestUtil {
private ArgumentCaptor componentLogsInformationCaptor;
@Captor
private ArgumentCaptor> callbackCaptor;
- @Captor
- private ArgumentCaptor