diff --git a/pom.xml b/pom.xml index d0feba41ac..efdde98d7b 100644 --- a/pom.xml +++ b/pom.xml @@ -234,7 +234,7 @@ software.amazon.awssdk.iotdevicesdk aws-iot-device-sdk - 1.19.1 + 1.19.1-C2CUC-SNAPSHOT org.bouncycastle diff --git a/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java b/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java index d593cd4e1f..8da7d73e52 100644 --- a/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java +++ b/src/main/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgent.java @@ -69,6 +69,7 @@ public class ConfigStoreIPCEventStreamAgent { private static final Logger logger = LogManager.getLogger(ConfigStoreIPCEventStreamAgent.class); + private static final String COMPONENT_NOT_FOUND_ERROR_FORMAT = "Component config not found for component %s"; private static final String KEY_NOT_FOUND_ERROR_MESSAGE = "Key not found"; private static final String SERVICE_NAME = "service-name"; @Getter(AccessLevel.PACKAGE) @@ -192,7 +193,7 @@ public GetConfigurationResponse handleRequest(GetConfigurationRequest request) { Topics serviceTopics = kernel.findServiceTopic(finalServiceName); if (serviceTopics == null) { - throw new ResourceNotFoundError(KEY_NOT_FOUND_ERROR_MESSAGE); + throw new ResourceNotFoundError(String.format(COMPONENT_NOT_FOUND_ERROR_FORMAT, finalServiceName)); } Topics configTopics = serviceTopics.findInteriorChild(CONFIGURATION_CONFIG_KEY); @@ -262,6 +263,9 @@ public UpdateConfigurationResponse handleRequest(UpdateConfigurationRequest requ logger.atDebug().kv(SERVICE_NAME, serviceName).log("Config IPC config update request"); validateRequest(request); + String targetServiceName = + request.getComponentName() == null ? serviceName : request.getComponentName(); + String[] keyPath = new String[0]; // Keypath is expected to denote the container node if (request.getKeyPath() != null) { @@ -270,7 +274,14 @@ public UpdateConfigurationResponse handleRequest(UpdateConfigurationRequest requ Object value = request.getValueToMerge(); - Topics serviceTopics = kernel.findServiceTopic(serviceName); + logger.atDebug().kv(SERVICE_NAME, serviceName).log( + "Updating configuration for target component {}, key path {}, and value keys [{}]", + targetServiceName, + Arrays.toString(keyPath), + String.join(", ", request.getValueToMerge().keySet()) + ); + + Topics serviceTopics = kernel.findServiceTopic(targetServiceName); Topics configTopics = serviceTopics.lookupTopics(CONFIGURATION_CONFIG_KEY); Node node = configTopics.findNode(keyPath); long updateTime = request.getTimestamp().toEpochMilli(); @@ -327,14 +338,17 @@ private void validateRequest(UpdateConfigurationRequest request) { + restrictedConfigurationFields); } - Topics serviceTopics = kernel.findServiceTopic(serviceName); - if (serviceTopics == null) { - throw new InvalidArgumentsError("Component config not found for component " + serviceName); + final String targetServiceName = + request.getComponentName() == null ? serviceName : request.getComponentName(); + + Topics targetServiceTopics = kernel.findServiceTopic(targetServiceName); + if (targetServiceTopics == null) { + throw new InvalidArgumentsError(String.format(COMPONENT_NOT_FOUND_ERROR_FORMAT, targetServiceName)); } - Topics configTopics = serviceTopics.lookupTopics(CONFIGURATION_CONFIG_KEY); + Topics configTopics = targetServiceTopics.lookupTopics(CONFIGURATION_CONFIG_KEY); Node node = configTopics.findNode(keyPath); if (node != null && !(node instanceof Topic) && !(node instanceof Topics)) { - logger.atError().kv(SERVICE_NAME, serviceName) + logger.atError().kv(SERVICE_NAME, targetServiceName) .log("Somehow Node has an unknown type {}", node.getClass()); throw new InvalidArgumentsError("Node corresponding to keypath " + request.getKeyPath().toString() + " has an unknown type"); diff --git a/src/test/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgentTest.java b/src/test/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgentTest.java index 33bf563854..4bc10c1a33 100644 --- a/src/test/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgentTest.java +++ b/src/test/java/com/aws/greengrass/builtin/services/configstore/ConfigStoreIPCEventStreamAgentTest.java @@ -57,9 +57,11 @@ import static com.aws.greengrass.lifecyclemanager.GreengrassService.SERVICES_NAMESPACE_TOPIC; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -221,7 +223,7 @@ void GIVEN_get_config_request_WHEN_component_requested_does_not_exist_THEN_fail( request.setKeyPath(Collections.singletonList("AnyKey")); ResourceNotFoundError error = assertThrows(ResourceNotFoundError.class, () -> agent.getGetConfigurationHandler(mockContext).handleRequest(request)); - assertEquals("Key not found", error.getMessage()); + assertEquals("Component config not found for component WrongComponent", error.getMessage()); } @Test @@ -262,7 +264,21 @@ void GIVEN_agent_running_WHEN_update_config_request_for_ACL_THEN_update_fails() request.setTimestamp(Instant.now()); Exception e = assertThrows(InvalidArgumentsError.class, () -> agent.getUpdateConfigurationHandler(mockContext).handleRequest(request)); - assertTrue(e.getMessage().contains("Config update is not allowed for following fields")); + assertThat(e.getMessage(), containsString("Config update is not allowed for following fields")); + } + + @Test + void GIVEN_component_name_provided_WHEN_update_config_request_for_ACL_THEN_update_fails() { + when(mockAuthenticationData.getIdentityLabel()).thenReturn(TEST_COMPONENT_A); + UpdateConfigurationRequest request = new UpdateConfigurationRequest(); + request.setComponentName(TEST_COMPONENT_B); + request.setKeyPath(Collections.EMPTY_LIST); + request.setValueToMerge(Collections.singletonMap(ACCESS_CONTROL_NAMESPACE_TOPIC, Collections.singletonMap( + "aws.greengrass.ipc.pubsub", "policy"))); + request.setTimestamp(Instant.now()); + Exception e = assertThrows(InvalidArgumentsError.class, + () -> agent.getUpdateConfigurationHandler(mockContext).handleRequest(request)); + assertThat(e.getMessage(), containsString("Config update is not allowed for following fields")); } @Test @@ -274,7 +290,7 @@ void GIVEN_agent_running_WHEN_update_config_request_for_ACL_nested_node_THEN_upd request.setTimestamp(Instant.now()); Exception e = assertThrows(InvalidArgumentsError.class, () -> agent.getUpdateConfigurationHandler(mockContext).handleRequest(request)); - assertTrue(e.getMessage().contains("Config update is not allowed for following fields")); + assertThat(e.getMessage(), containsString("Config update is not allowed for following fields")); } @Test @@ -293,6 +309,64 @@ void GIVEN_update_config_request_WHEN_update_key_does_not_exist_THEN_create_key( assertEquals("SomeValue", newConfigKeyTopic.getOnce()); } + @Test + void GIVEN_update_config_request_WHEN_component_requested_does_not_exist_THEN_fail() { + when(mockAuthenticationData.getIdentityLabel()).thenReturn(TEST_COMPONENT_A); + String wrongComponentName = "WrongComponent"; + + UpdateConfigurationRequest request = new UpdateConfigurationRequest(); + request.setComponentName(wrongComponentName); + request.setKeyPath(Collections.singletonList("AnyKeyPath")); + request.setValueToMerge(Collections.singletonMap("AnyKey", "AnyValue")); + request.setTimestamp(Instant.now()); + InvalidArgumentsError error = assertThrows(InvalidArgumentsError.class, () -> + agent.getUpdateConfigurationHandler(mockContext).handleRequest(request)); + assertEquals("Component config not found for component " + wrongComponentName, error.getMessage()); + } + + @Test + void GIVEN_component_name_provided_for_update_WHEN_key_path_does_not_exist_THEN_create_key_for_given_component() { + when(kernel.findServiceTopic(TEST_COMPONENT_B)) + .thenReturn(configuration.getRoot().lookupTopics(SERVICES_NAMESPACE_TOPIC, TEST_COMPONENT_B)); + + assertNull(kernel.findServiceTopic(TEST_COMPONENT_B).findTopics(CONFIGURATION_CONFIG_KEY, "NewKey")); + + when(mockAuthenticationData.getIdentityLabel()).thenReturn(TEST_COMPONENT_A); + UpdateConfigurationRequest request = new UpdateConfigurationRequest(); + request.setKeyPath(Collections.singletonList("NewKey")); + request.setValueToMerge(Collections.singletonMap("NewValueToMergeKey", "SomeValue")); + request.setComponentName(TEST_COMPONENT_B); + request.setTimestamp(Instant.now()); + UpdateConfigurationResponse response = agent.getUpdateConfigurationHandler(mockContext).handleRequest(request); + assertNotNull(response); + + Topics newKeyTopics = kernel.findServiceTopic(TEST_COMPONENT_B).findTopics(CONFIGURATION_CONFIG_KEY, "NewKey"); + assertNotNull(newKeyTopics); + Topic newValueToMergeTopic = kernel.findServiceTopic(TEST_COMPONENT_B).find(CONFIGURATION_CONFIG_KEY, "NewKey", "NewValueToMergeKey"); + assertNotNull(newValueToMergeTopic); + assertEquals("SomeValue", newValueToMergeTopic.getOnce()); + } + + @Test + void GIVEN_component_name_provided_for_update_WHEN_key_not_provided_THEN_merge_values_at_config_root() { + when(kernel.findServiceTopic(TEST_COMPONENT_B)) + .thenReturn(configuration.getRoot().lookupTopics(SERVICES_NAMESPACE_TOPIC, TEST_COMPONENT_B)); + + assertNull(kernel.findServiceTopic(TEST_COMPONENT_B).find(CONFIGURATION_CONFIG_KEY, "NewKey")); + + when(mockAuthenticationData.getIdentityLabel()).thenReturn(TEST_COMPONENT_A); + UpdateConfigurationRequest request = new UpdateConfigurationRequest(); + request.setValueToMerge(Collections.singletonMap("NewKey", "SomeValue")); + request.setComponentName(TEST_COMPONENT_B); + request.setTimestamp(Instant.now()); + UpdateConfigurationResponse response = agent.getUpdateConfigurationHandler(mockContext).handleRequest(request); + assertNotNull(response); + + Topic newConfigKeyTopic = kernel.findServiceTopic(TEST_COMPONENT_B).find(CONFIGURATION_CONFIG_KEY, "NewKey"); + assertNotNull(newConfigKeyTopic); + assertEquals("SomeValue", newConfigKeyTopic.getOnce()); + } + @Test void GIVEN_update_config_request_WHEN_proposed_timestamp_is_stale_THEN_fail() { when(mockAuthenticationData.getIdentityLabel()).thenReturn(TEST_COMPONENT_A);