Skip to content

Commit 6a50d3d

Browse files
author
Tianci Shen
committed
feat: add nucleus connectivity validation
1 parent cdf1ce9 commit 6a50d3d

File tree

17 files changed

+731
-76
lines changed

17 files changed

+731
-76
lines changed

src/integrationtests/java/com/aws/greengrass/integrationtests/deployment/DeploymentConfigMergingTest.java

Lines changed: 173 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.aws.greengrass.config.Topics;
1010
import com.aws.greengrass.dependency.State;
1111
import com.aws.greengrass.deployment.DeploymentConfigMerger;
12+
import com.aws.greengrass.deployment.DeviceConfiguration;
1213
import com.aws.greengrass.deployment.model.ComponentUpdatePolicy;
1314
import com.aws.greengrass.deployment.model.Deployment;
1415
import com.aws.greengrass.deployment.model.DeploymentDocument;
@@ -25,10 +26,15 @@
2526
import com.aws.greengrass.logging.api.Logger;
2627
import com.aws.greengrass.logging.impl.GreengrassLogMessage;
2728
import com.aws.greengrass.logging.impl.LogManager;
29+
import com.aws.greengrass.mqttclient.MqttClient;
30+
import com.aws.greengrass.mqttclient.PublishRequest;
2831
import com.aws.greengrass.status.FleetStatusService;
2932
import com.aws.greengrass.testcommons.testutilities.GGExtension;
3033
import com.aws.greengrass.testcommons.testutilities.NoOpPathOwnershipHandler;
3134
import com.aws.greengrass.testcommons.testutilities.TestUtils;
35+
import com.aws.greengrass.util.Coerce;
36+
import com.aws.greengrass.util.GreengrassServiceClientFactory;
37+
import com.aws.greengrass.util.Pair;
3238
import org.apache.commons.lang3.SystemUtils;
3339
import org.hamcrest.Matchers;
3440
import org.junit.jupiter.api.AfterAll;
@@ -37,16 +43,26 @@
3743
import org.junit.jupiter.api.BeforeEach;
3844
import org.junit.jupiter.api.Test;
3945
import org.junit.jupiter.api.extension.ExtendWith;
46+
import org.junit.jupiter.api.extension.ExtensionContext;
47+
import org.mockito.Mock;
4048
import org.slf4j.event.Level;
4149
import software.amazon.awssdk.aws.greengrass.GreengrassCoreIPCClient;
4250
import software.amazon.awssdk.aws.greengrass.model.ComponentUpdatePolicyEvents;
4351
import software.amazon.awssdk.aws.greengrass.model.DeferComponentUpdateRequest;
4452
import software.amazon.awssdk.aws.greengrass.model.SubscribeToComponentUpdatesRequest;
4553
import software.amazon.awssdk.aws.greengrass.model.SubscribeToComponentUpdatesResponse;
54+
import software.amazon.awssdk.awscore.exception.AwsServiceException;
4655
import software.amazon.awssdk.crt.io.SocketOptions;
56+
import software.amazon.awssdk.crt.mqtt.MqttClientConnection;
57+
import software.amazon.awssdk.crt.mqtt.MqttException;
4758
import software.amazon.awssdk.eventstreamrpc.EventStreamRPCConnection;
4859
import software.amazon.awssdk.eventstreamrpc.StreamResponseHandler;
60+
import software.amazon.awssdk.http.SdkHttpClient;
61+
import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder;
4962
import software.amazon.awssdk.services.greengrassv2.model.DeploymentConfigurationValidationPolicy;
63+
import software.amazon.awssdk.services.greengrassv2data.GreengrassV2DataClient;
64+
import software.amazon.awssdk.services.greengrassv2data.model.GreengrassV2DataException;
65+
import software.amazon.awssdk.services.greengrassv2data.model.ListThingGroupsForCoreDeviceRequest;
5066

5167
import java.io.IOException;
5268
import java.time.Duration;
@@ -76,6 +92,7 @@
7692
import static com.aws.greengrass.lifecyclemanager.GreengrassService.SERVICE_DEPENDENCIES_NAMESPACE_TOPIC;
7793
import static com.aws.greengrass.lifecyclemanager.GreengrassService.SERVICE_LIFECYCLE_NAMESPACE_TOPIC;
7894
import static com.aws.greengrass.lifecyclemanager.GreengrassService.SETENV_CONFIG_NAMESPACE;
95+
import static com.aws.greengrass.testcommons.testutilities.ExceptionLogProtector.ignoreExceptionOfType;
7996
import static com.aws.greengrass.testcommons.testutilities.SudoUtil.assumeCanSudoShell;
8097
import static com.aws.greengrass.testcommons.testutilities.TestUtils.createCloseableLogListener;
8198
import static com.aws.greengrass.testcommons.testutilities.TestUtils.createServiceStateChangeWaiter;
@@ -92,11 +109,25 @@
92109
import static org.junit.jupiter.api.Assertions.assertThrows;
93110
import static org.junit.jupiter.api.Assertions.assertTrue;
94111
import static org.junit.jupiter.api.Assertions.fail;
112+
import static org.mockito.ArgumentMatchers.any;
113+
import static org.mockito.ArgumentMatchers.eq;
114+
import static org.mockito.Mockito.when;
95115
import static software.amazon.awssdk.services.greengrassv2.model.DeploymentComponentUpdatePolicyAction.NOTIFY_COMPONENTS;
96116
import static software.amazon.awssdk.services.greengrassv2.model.DeploymentComponentUpdatePolicyAction.SKIP_NOTIFY_COMPONENTS;
97117

98118
@ExtendWith(GGExtension.class)
99119
class DeploymentConfigMergingTest extends BaseITCase {
120+
@Mock
121+
private MqttClient mqttClient;
122+
@Mock
123+
private AwsIotMqttConnectionBuilder awsIotMqttConnectionBuilder;
124+
@Mock
125+
private MqttClientConnection mqttClientConnection;
126+
@Mock
127+
private GreengrassServiceClientFactory gscFactory;
128+
@Mock
129+
private GreengrassV2DataClient greengrassV2DataClient;
130+
100131
private Kernel kernel;
101132
private DeploymentConfigMerger deploymentConfigMerger;
102133
private static SocketOptions socketOptions;
@@ -111,6 +142,8 @@ static void initialize() {
111142
void before() {
112143
kernel = new Kernel();
113144
NoOpPathOwnershipHandler.register(kernel);
145+
kernel.getContext().put(MqttClient.class, mqttClient);
146+
kernel.getContext().put(GreengrassServiceClientFactory.class, gscFactory);
114147
deploymentConfigMerger = kernel.getContext().get(DeploymentConfigMerger.class);
115148
}
116149

@@ -371,14 +404,14 @@ void GIVEN_kernel_running_single_service_WHEN_merge_same_doc_happens_twice_THEN_
371404
put(SERVICES_NAMESPACE_TOPIC, new HashMap<String, Object>() {{
372405
put("main", new HashMap<String, Object>() {{
373406
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC,
374-
Arrays.asList("new_service", DEFAULT_NUCLEUS_COMPONENT_NAME));
407+
new ArrayList<>(Arrays.asList("new_service", DEFAULT_NUCLEUS_COMPONENT_NAME)));
375408
}});
376409

377410
put("new_service", new HashMap<String, Object>() {{
378411
put(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, new HashMap<String, Object>() {{
379412
put(LIFECYCLE_RUN_NAMESPACE_TOPIC, "echo done");
380413
}});
381-
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, Arrays.asList("new_service2"));
414+
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, new ArrayList<>(Arrays.asList("new_service2")));
382415
}});
383416

384417
put("new_service2", new HashMap<String, Object>() {{
@@ -709,6 +742,144 @@ void GIVEN_kernel_running_service_WHEN_run_with_change_THEN_service_restarts() t
709742
}
710743
}
711744
}
745+
746+
@Test
747+
void GIVEN_kernel_running_with_some_config_WHEN_connectivity_validation_successful_THEN_config_is_updated()
748+
throws Throwable {
749+
// GIVEN
750+
DeviceConfiguration deviceConfiguration = new DeviceConfiguration(kernel.getConfig(), kernel.getKernelCommandLine(), "ThingName", "xxxxxx-ats.iot.us-east-1.amazonaws.com",
751+
"xxxxxx.credentials.iot.us-east-1.amazonaws.com", "privKeyFilePath", "certFilePath", "caFilePath",
752+
"us-east-1", "roleAliasName");
753+
kernel.getContext().put(DeviceConfiguration.class, deviceConfiguration);
754+
ConfigPlatformResolver.initKernelWithMultiPlatformConfig(kernel, getClass().getResource("config.yaml"));
755+
756+
// Mock Fss trigger at kernel launch
757+
when(mqttClient.publish(any(PublishRequest.class))).thenReturn(CompletableFuture.completedFuture(0));
758+
kernel.launch();
759+
760+
// Read deployment config from yaml file
761+
Map<String, Object> deploymentConfig = ConfigPlatformResolver
762+
.resolvePlatformMap(getClass().getResource("connectivityValidationConfig.yaml"));
763+
int expectedMqttPort = 8080; // mqtt.port
764+
int expectedDataPlanePort = 443; // greengrassDataPlanePort
765+
766+
// Mock Successful Mqtt Validation
767+
when(mqttClient.createMqttConnectionBuilder(any(), any(), eq(null))).thenReturn(awsIotMqttConnectionBuilder);
768+
when(awsIotMqttConnectionBuilder.withClientId(any())).thenReturn(awsIotMqttConnectionBuilder);
769+
when(awsIotMqttConnectionBuilder.build()).thenReturn(mqttClientConnection);
770+
CompletableFuture<Boolean> mqttConnection = CompletableFuture.completedFuture(true);
771+
when(mqttClientConnection.connect()).thenReturn(mqttConnection);
772+
773+
// Mock Successful Http Validation
774+
Pair<SdkHttpClient, GreengrassV2DataClient> pair = new Pair<>(null, greengrassV2DataClient);
775+
when(gscFactory.createClientFromConfig(any())).thenReturn(pair);
776+
when(greengrassV2DataClient.listThingGroupsForCoreDevice((ListThingGroupsForCoreDeviceRequest) any()))
777+
.thenReturn(null);
778+
779+
// WHEN
780+
Topics t = kernel.findServiceTopic(FleetStatusService.FLEET_STATUS_SERVICE_TOPICS);
781+
assertNotNull(t, "FSS Topics should not be null before merging");
782+
deploymentConfigMerger.mergeInNewConfig(testDeployment(), deploymentConfig).get(60, TimeUnit.SECONDS);
783+
t = kernel.findServiceTopic(FleetStatusService.FLEET_STATUS_SERVICE_TOPICS);
784+
assertNotNull(t, "FSS Topics should not be null after merging");
785+
786+
// THEN
787+
deviceConfiguration = kernel.getContext().get(DeviceConfiguration.class);
788+
int actualMqttPort = Coerce.toInt(deviceConfiguration.getMQTTNamespace().find(MqttClient.MQTT_PORT_KEY));
789+
assertEquals(expectedMqttPort, actualMqttPort);
790+
int actualDataPlanePort = Coerce.toInt(deviceConfiguration.getGreengrassDataPlanePort());
791+
assertEquals(expectedDataPlanePort, actualDataPlanePort);
792+
}
793+
794+
@Test
795+
void GIVEN_kernel_running_with_some_config_WHEN_mqtt_validation_fails_THEN_config_is_not_updated(ExtensionContext context)
796+
throws Throwable {
797+
ignoreExceptionOfType(context, MqttException.class);
798+
// GIVEN
799+
DeviceConfiguration deviceConfiguration = new DeviceConfiguration(kernel.getConfig(), kernel.getKernelCommandLine(), "ThingName", "xxxxxx-ats.iot.us-east-1.amazonaws.com",
800+
"xxxxxx.credentials.iot.us-east-1.amazonaws.com", "privKeyFilePath", "certFilePath", "caFilePath",
801+
"us-east-1", "roleAliasName");
802+
kernel.getContext().put(DeviceConfiguration.class, deviceConfiguration);
803+
ConfigPlatformResolver.initKernelWithMultiPlatformConfig(kernel, getClass().getResource("config.yaml"));
804+
805+
// Mock fss trigger at kernel launch
806+
when(mqttClient.publish(any(PublishRequest.class))).thenReturn(CompletableFuture.completedFuture(0));
807+
kernel.launch();
808+
809+
// Read deployment config from yaml file
810+
Map<String, Object> deploymentConfig = ConfigPlatformResolver
811+
.resolvePlatformMap(getClass().getResource("connectivityValidationConfig.yaml"));
812+
813+
// Mock Failed Mqtt Validation
814+
when(mqttClient.createMqttConnectionBuilder(any(), any(), eq(null))).thenReturn(awsIotMqttConnectionBuilder);
815+
when(awsIotMqttConnectionBuilder.withClientId(any())).thenReturn(awsIotMqttConnectionBuilder);
816+
when(awsIotMqttConnectionBuilder.build()).thenReturn(mqttClientConnection);
817+
when(mqttClientConnection.connect()).thenThrow(new MqttException("mocked failure"));
818+
819+
// WHEN
820+
Topics t = kernel.findServiceTopic(FleetStatusService.FLEET_STATUS_SERVICE_TOPICS);
821+
assertNotNull(t, "FSS Topics should not be null before merging");
822+
deploymentConfigMerger.mergeInNewConfig(testDeployment(), deploymentConfig).get(60, TimeUnit.SECONDS);
823+
t = kernel.findServiceTopic(FleetStatusService.FLEET_STATUS_SERVICE_TOPICS);
824+
assertNotNull(t, "FSS Topics should not be null after merging");
825+
826+
// THEN
827+
deviceConfiguration = kernel.getContext().get(DeviceConfiguration.class);
828+
int actualMqttPort = Coerce.toInt(deviceConfiguration.getMQTTNamespace().find(MqttClient.MQTT_PORT_KEY));
829+
assertEquals(0, actualMqttPort);
830+
int actualDataPlanePort = Coerce.toInt(deviceConfiguration.getGreengrassDataPlanePort());
831+
assertEquals(8443, actualDataPlanePort);
832+
}
833+
834+
@Test
835+
void GIVEN_kernel_running_with_some_config_WHEN_http_validation_fails_THEN_config_is_not_updated(ExtensionContext context)
836+
throws Throwable {
837+
ignoreExceptionOfType(context, GreengrassV2DataException.class);
838+
// GIVEN
839+
DeviceConfiguration deviceConfiguration = new DeviceConfiguration(kernel.getConfig(), kernel.getKernelCommandLine(), "ThingName", "xxxxxx-ats.iot.us-east-1.amazonaws.com",
840+
"xxxxxx.credentials.iot.us-east-1.amazonaws.com", "privKeyFilePath", "certFilePath", "caFilePath",
841+
"us-east-1", "roleAliasName");
842+
kernel.getContext().put(DeviceConfiguration.class, deviceConfiguration);
843+
ConfigPlatformResolver.initKernelWithMultiPlatformConfig(kernel, getClass().getResource("config.yaml"));
844+
845+
// Mock fss trigger at kernel launch
846+
when(mqttClient.publish(any(PublishRequest.class))).thenReturn(CompletableFuture.completedFuture(0));
847+
kernel.launch();
848+
849+
// Read deployment config from yaml file
850+
Map<String, Object> deploymentConfig = ConfigPlatformResolver
851+
.resolvePlatformMap(getClass().getResource("connectivityValidationConfig.yaml"));
852+
853+
// Mock Successful Mqtt Validation
854+
when(mqttClient.createMqttConnectionBuilder(any(), any(), eq(null))).thenReturn(awsIotMqttConnectionBuilder);
855+
when(awsIotMqttConnectionBuilder.withClientId(any())).thenReturn(awsIotMqttConnectionBuilder);
856+
when(awsIotMqttConnectionBuilder.build()).thenReturn(mqttClientConnection);
857+
CompletableFuture<Boolean> mqttConnection = new CompletableFuture<>();
858+
mqttConnection.complete(true);
859+
when(mqttClientConnection.connect()).thenReturn(mqttConnection);
860+
861+
// Mock Failed Http Validation
862+
Pair<SdkHttpClient, GreengrassV2DataClient> pair = new Pair<>(null, greengrassV2DataClient);
863+
when(gscFactory.createClientFromConfig(any())).thenReturn(pair);
864+
AwsServiceException exception = GreengrassV2DataException.builder().message("mocked failure").build();
865+
when(greengrassV2DataClient.listThingGroupsForCoreDevice((ListThingGroupsForCoreDeviceRequest) any()))
866+
.thenThrow(exception);
867+
868+
// WHEN
869+
Topics t = kernel.findServiceTopic(FleetStatusService.FLEET_STATUS_SERVICE_TOPICS);
870+
assertNotNull(t, "FSS Topics should not be null before merging");
871+
deploymentConfigMerger.mergeInNewConfig(testDeployment(), deploymentConfig).get(60, TimeUnit.SECONDS);
872+
t = kernel.findServiceTopic(FleetStatusService.FLEET_STATUS_SERVICE_TOPICS);
873+
assertNotNull(t, "FSS Topics should not be null after merging");
874+
875+
// THEN
876+
deviceConfiguration = kernel.getContext().get(DeviceConfiguration.class);
877+
int actualMqttPort = Coerce.toInt(deviceConfiguration.getMQTTNamespace().find(MqttClient.MQTT_PORT_KEY));
878+
assertEquals(0, actualMqttPort);
879+
int actualDataPlanePort = Coerce.toInt(deviceConfiguration.getGreengrassDataPlanePort());
880+
assertEquals(8443, actualDataPlanePort);
881+
}
882+
712883
private Deployment testDeployment() {
713884
DeploymentDocument doc = DeploymentDocument.builder().timestamp(System.currentTimeMillis()).deploymentId("id")
714885
.failureHandlingPolicy(FailureHandlingPolicy.DO_NOTHING)

src/integrationtests/java/com/aws/greengrass/integrationtests/lifecyclemanager/ServiceDependencyLifecycleTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import software.amazon.awssdk.services.greengrassv2.model.DeploymentConfigurationValidationPolicy;
3131

3232
import java.net.URL;
33+
import java.util.ArrayList;
3334
import java.util.Arrays;
3435
import java.util.Collections;
3536
import java.util.HashMap;
@@ -181,8 +182,8 @@ void GIVEN_hard_dependency_WHEN_dependency_goes_through_lifecycle_events_THEN_cu
181182
Map<String, Object> configRemoveDep = new HashMap<String, Object>() {{
182183
put(SERVICES_NAMESPACE_TOPIC, new HashMap<String, Object>() {{
183184
put("main", new HashMap<String, Object>() {{
184-
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, Arrays.asList(CustomerApp,
185-
DEFAULT_NUCLEUS_COMPONENT_NAME));
185+
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, new ArrayList<>(Arrays.asList(CustomerApp,
186+
DEFAULT_NUCLEUS_COMPONENT_NAME)));
186187
}});
187188
put(CustomerApp, new HashMap<String, Object>() {{
188189
putAll(kernel.findServiceTopic(CustomerApp).toPOJO());
@@ -294,8 +295,8 @@ void GIVEN_soft_dependency_WHEN_dependency_goes_through_lifecycle_events_THEN_cu
294295
Map<String, Object> configRemoveDep = new HashMap<String, Object>() {{
295296
put(SERVICES_NAMESPACE_TOPIC, new HashMap<String, Object>() {{
296297
put("main", new HashMap<String, Object>() {{
297-
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, Arrays.asList(CustomerApp,
298-
DEFAULT_NUCLEUS_COMPONENT_NAME));
298+
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, new ArrayList<>(Arrays.asList(CustomerApp,
299+
DEFAULT_NUCLEUS_COMPONENT_NAME)));
299300
}});
300301
put(CustomerApp, new HashMap<String, Object>() {{
301302
putAll(kernel.findServiceTopic(CustomerApp).toPOJO());

src/integrationtests/java/com/aws/greengrass/integrationtests/status/EventFleetStatusServiceTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.aws.greengrass.componentmanager.exceptions.PackageLoadingException;
1111
import com.aws.greengrass.dependency.ComponentStatusCode;
1212
import com.aws.greengrass.dependency.State;
13+
import com.aws.greengrass.deployment.ConnectivityValidator;
1314
import com.aws.greengrass.deployment.DeploymentQueue;
1415
import com.aws.greengrass.deployment.DeploymentService;
1516
import com.aws.greengrass.deployment.DeviceConfiguration;
@@ -126,6 +127,8 @@ class EventFleetStatusServiceTest extends BaseITCase {
126127
private IotJobsClientWrapper mockIotJobsClientWrapper;
127128
@Mock
128129
private ThingGroupHelper thingGroupHelper;
130+
@Mock
131+
private ConnectivityValidator connectivityValidator;
129132

130133
private AtomicReference<List<FleetStatusDetails>> fleetStatusDetailsList;
131134
private final CountDownLatch mainFinished = new CountDownLatch(1);
@@ -177,6 +180,7 @@ void setupKernel(ExtensionContext context) throws Exception {
177180
EventFleetStatusServiceTest.class.getResource("onlyMain.yaml"));
178181
kernel.getContext().put(MqttClient.class, mqttClient);
179182
kernel.getContext().put(ThingGroupHelper.class, thingGroupHelper);
183+
kernel.getContext().put(ConnectivityValidator.class, connectivityValidator);
180184

181185
// Mock out cloud communication
182186
GreengrassServiceClientFactory mgscf = mock(GreengrassServiceClientFactory.class);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
services:
2+
main:
3+
dependencies:
4+
- aws.greengrass.Nucleus
5+
aws.greengrass.Nucleus:
6+
componentType: NUCLEUS
7+
configuration:
8+
awsRegion: us-east-1
9+
greengrassDataPlanePort: 443
10+
iotCredEndpoint: xxxxxx.credentials.iot.us-east-1.amazonaws.com
11+
iotDataEndpoint: xxxxxx-ats.iot.us-east-1.amazonaws.com
12+
iotRoleAlias: roleAliasName
13+
mqtt:
14+
port: 8080
15+
runWithDefault:
16+
posixUser: nobody
17+
windowsUser: integ-tester

0 commit comments

Comments
 (0)