2727import com .aws .greengrass .lifecyclemanager .exceptions .ServiceLoadException ;
2828import com .aws .greengrass .logging .api .Logger ;
2929import com .aws .greengrass .logging .impl .LogManager ;
30+ import com .aws .greengrass .mqttclient .MqttClient ;
31+ import com .aws .greengrass .security .SecurityService ;
32+ import com .aws .greengrass .security .exceptions .MqttConnectionProviderException ;
3033import com .aws .greengrass .util .Coerce ;
34+ import com .aws .greengrass .util .ProxyUtils ;
35+ import com .aws .greengrass .util .Utils ;
3136import lombok .AccessLevel ;
3237import lombok .AllArgsConstructor ;
3338import lombok .Getter ;
39+ import software .amazon .awssdk .crt .http .HttpProxyOptions ;
40+ import software .amazon .awssdk .crt .io .ClientTlsContext ;
41+ import software .amazon .awssdk .crt .io .SocketOptions ;
42+ import software .amazon .awssdk .crt .io .TlsContextOptions ;
43+ import software .amazon .awssdk .crt .mqtt .MqttClientConnection ;
44+ import software .amazon .awssdk .crt .mqtt .MqttException ;
45+ import software .amazon .awssdk .iot .AwsIotMqttConnectionBuilder ;
3446import software .amazon .awssdk .services .greengrassv2 .model .DeploymentComponentUpdatePolicyAction ;
3547
48+ import java .time .Duration ;
49+ import java .util .Arrays ;
3650import java .util .Collection ;
3751import java .util .HashMap ;
3852import java .util .HashSet ;
4761import javax .inject .Inject ;
4862
4963import static com .aws .greengrass .componentmanager .KernelConfigResolver .CONFIGURATION_CONFIG_KEY ;
64+ import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_MQTT_NAMESPACE ;
65+ import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_NETWORK_PROXY_NAMESPACE ;
5066import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PARAM_AWS_REGION ;
5167import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PARAM_IOT_CRED_ENDPOINT ;
5268import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PARAM_IOT_DATA_ENDPOINT ;
69+ import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PARAM_NO_PROXY_ADDRESSES ;
70+ import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PARAM_PROXY_URL ;
71+ import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PARAM_PROXY_USERNAME ;
72+ import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PARAM_PROXY_PASSWORD ;
73+ import static com .aws .greengrass .deployment .DeviceConfiguration .DEVICE_PROXY_NAMESPACE ;
74+ import static com .aws .greengrass .deployment .DynamicComponentConfigurationValidator .DEFAULT_TIMEOUT_SECOND ;
5375import static com .aws .greengrass .lifecyclemanager .GreengrassService .SERVICES_NAMESPACE_TOPIC ;
5476import static com .aws .greengrass .lifecyclemanager .GreengrassService .SERVICE_NAME_KEY ;
5577
@@ -66,6 +88,9 @@ public class DeploymentConfigMerger {
6688 private Kernel kernel ;
6789 private DeviceConfiguration deviceConfiguration ;
6890 private DynamicComponentConfigurationValidator validator ;
91+ private MqttClient mqttClient ;
92+ private ThingGroupHelper thingGroupHelper ;
93+ private SecurityService securityService ;
6994
7095 /**
7196 * Merge in new configuration values and new services.
@@ -143,7 +168,8 @@ private void updateActionForDeployment(Map<String, Object> newConfig, Deployment
143168 }
144169
145170 // Validate the AWS region, IoT credentials endpoint as well as the IoT data endpoint.
146- if (!validateNucleusConfig (totallyCompleteFuture , nucleusConfig )) {
171+ if (!validateNucleusConfig (totallyCompleteFuture , nucleusConfig ,
172+ deployment .getDeploymentDocumentObj ().getConfigurationValidationPolicy ().timeoutInSeconds ())) {
147173 return ;
148174 }
149175
@@ -153,7 +179,7 @@ private void updateActionForDeployment(Map<String, Object> newConfig, Deployment
153179 }
154180
155181 private boolean validateNucleusConfig (CompletableFuture <DeploymentResult > totallyCompleteFuture ,
156- Map <String , Object > nucleusConfig ) {
182+ Map <String , Object > nucleusConfig , Integer timeoutSec ) {
157183 if (nucleusConfig != null ) {
158184 String awsRegion = tryGetAwsRegionFromNewConfig (nucleusConfig );
159185 String iotCredEndpoint = tryGetIoTCredEndpointFromNewConfig (nucleusConfig );
@@ -166,10 +192,111 @@ private boolean validateNucleusConfig(CompletableFuture<DeploymentResult> totall
166192 .complete (new DeploymentResult (DeploymentResult .DeploymentStatus .FAILED_NO_STATE_CHANGE , e ));
167193 return false ;
168194 }
195+
196+ long configTimeout = Duration .ofSeconds (DEFAULT_TIMEOUT_SECOND ).toMillis ();
197+ if (timeoutSec != null ) {
198+ configTimeout = Duration .ofSeconds (timeoutSec ).toMillis ();
199+ }
200+
201+ if (configTimeout == 0
202+ || !deviceConfiguration .isDeviceConfiguredToTalkToCloud ()) {
203+ logger .atDebug ().log ("Skipping connectivity validation" );
204+ return true ;
205+ }
206+ try {
207+ // Check that MQTT client has reconnected
208+ logger .atDebug ().log ("Checking MQTT Reconnected" );
209+ // mqttClient.waitForReconnect(configTimeout);
210+ MqttClientConnection connection = createMqttConnection (nucleusConfig );
211+ // TODO - pass connection into ScheduledExecuterService for wait and retry
212+ connection .connect ().get ();
213+
214+ // Check that HTTP client works
215+ logger .atDebug ().log ("Checking HTTP Reconnected" );
216+ // TODO - create new http client
217+ // thingGroupHelper.waitForReconnect(configTimeout);
218+ } catch (Exception e ) {
219+ logger .atError ().cause (e ).log ("Nucleus connectivity validation failed" );
220+ totallyCompleteFuture
221+ .complete (new DeploymentResult (DeploymentResult .DeploymentStatus .FAILED_NO_STATE_CHANGE , e ));
222+ return false ;
223+ }
169224 }
170225 return true ;
171226 }
172227
228+ private MqttClientConnection createMqttConnection (Map <String , Object > nucleusConfig ) {
229+ AwsIotMqttConnectionBuilder builder ;
230+ try {
231+ builder = securityService .getDeviceIdentityMqttConnectionBuilder ();
232+ } catch (MqttConnectionProviderException e ) {
233+ throw new MqttException (e .getMessage ());
234+ }
235+
236+ // get mqtt values from nucleus config to construct a MQTT client
237+ Map <String , Object > mqtt = (Map <String , Object >) nucleusConfig .get (DEVICE_MQTT_NAMESPACE );
238+ int pingTimeoutMs = Coerce .toInt (mqtt .getOrDefault (MqttClient .MQTT_PING_TIMEOUT_KEY , MqttClient .DEFAULT_MQTT_PING_TIMEOUT ));
239+ int keepAliveMs = Coerce .toInt (mqtt .getOrDefault (MqttClient .MQTT_KEEP_ALIVE_TIMEOUT_KEY , MqttClient .DEFAULT_MQTT_KEEP_ALIVE_TIMEOUT ));
240+ if (keepAliveMs != 0 && keepAliveMs <= pingTimeoutMs ) {
241+ throw new MqttException (String .format ("%s must be greater than %s" ,
242+ MqttClient .MQTT_KEEP_ALIVE_TIMEOUT_KEY , MqttClient .MQTT_PING_TIMEOUT_KEY ));
243+ }
244+ String rootCaPath = Coerce .toString (deviceConfiguration .getRootCAFilePath ());
245+ String endpoint = Coerce .toString (nucleusConfig .get (DEVICE_PARAM_IOT_DATA_ENDPOINT ));
246+ short port = (short ) Coerce .toInt (mqtt .getOrDefault (MqttClient .MQTT_PORT_KEY , MqttClient .DEFAULT_MQTT_PORT ));
247+ int operationTimeout = Coerce .toInt (mqtt .getOrDefault (MqttClient .MQTT_OPERATION_TIMEOUT_KEY , MqttClient .DEFAULT_MQTT_OPERATION_TIMEOUT ));
248+ int socketTimeout = Coerce .toInt (mqtt .getOrDefault (MqttClient .MQTT_SOCKET_TIMEOUT_KEY , MqttClient .DEFAULT_MQTT_SOCKET_TIMEOUT ));
249+
250+ builder .withCertificateAuthorityFromPath (null , rootCaPath )
251+ .withClientId ("test-id" )
252+ .withEndpoint (endpoint )
253+ .withPort (port )
254+ .withCleanSession (true )
255+ .withKeepAliveMs (keepAliveMs )
256+ .withProtocolOperationTimeoutMs (operationTimeout )
257+ .withPingTimeoutMs (pingTimeoutMs )
258+ .withSocketOptions (new SocketOptions ()).withTimeoutMs (socketTimeout );
259+
260+ // add proxy settings if needed
261+ Map <String , Object > networkProxy = (Map <String , Object >) nucleusConfig .get (DEVICE_NETWORK_PROXY_NAMESPACE );
262+ Map <String , Object > proxy = (Map <String , Object >) networkProxy .get (DEVICE_PROXY_NAMESPACE );
263+ String proxyUrl = Coerce .toString (proxy .get (DEVICE_PARAM_PROXY_URL ));
264+ if (!Utils .isEmpty (proxyUrl )) {
265+ HttpProxyOptions httpProxyOptions = new HttpProxyOptions ();
266+ httpProxyOptions .setHost (ProxyUtils .getHostFromProxyUrl (proxyUrl ));
267+ httpProxyOptions .setPort (ProxyUtils .getPortFromProxyUrl (proxyUrl ));
268+ httpProxyOptions .setConnectionType (HttpProxyOptions .HttpProxyConnectionType .Tunneling );
269+
270+ if ("https" .equalsIgnoreCase (ProxyUtils .getSchemeFromProxyUrl (proxyUrl ))) {
271+ TlsContextOptions proxyTlsOptions = MqttClient .getTlsContextOptions (rootCaPath );
272+ ClientTlsContext tlsContext = new ClientTlsContext (proxyTlsOptions );
273+ httpProxyOptions .setTlsContext (tlsContext );
274+ }
275+
276+ String username = Coerce .toString (proxy .getOrDefault (DEVICE_PARAM_PROXY_USERNAME , "" ));
277+ String password = Coerce .toString (proxy .getOrDefault (DEVICE_PARAM_PROXY_PASSWORD , "" ));
278+ String proxyUsername = ProxyUtils .getProxyUsername (proxyUrl , username );
279+ if (Utils .isNotEmpty (proxyUsername )) {
280+ httpProxyOptions .setAuthorizationType (HttpProxyOptions .HttpProxyAuthorizationType .Basic );
281+ httpProxyOptions .setAuthorizationUsername (proxyUsername );
282+ httpProxyOptions
283+ .setAuthorizationPassword (ProxyUtils .getProxyPassword (proxyUrl , password ));
284+ }
285+
286+ String noProxy = Coerce .toString (proxy .getOrDefault (DEVICE_PARAM_NO_PROXY_ADDRESSES , "" ));
287+ boolean useProxy = true ;
288+ // Only use the proxy when the endpoint we're connecting to is not in the NoProxyAddress list
289+ if (Utils .isNotEmpty (noProxy ) && Utils .isNotEmpty (endpoint )) {
290+ useProxy = Arrays .stream (noProxy .split ("," )).noneMatch (endpoint ::matches );
291+ }
292+ if (useProxy ) {
293+ builder .withHttpProxyOptions (httpProxyOptions );
294+ }
295+ }
296+
297+ return builder .build ();
298+ }
299+
173300 /**
174301 * Completes the provided future when all of the listed services are running.
175302 *
@@ -307,7 +434,6 @@ public AggregateServicesChangeManager createRollbackManager() {
307434
308435 /**
309436 * Start the new services the merge intends to add.
310- *
311437 */
312438 public void startNewServices () {
313439 for (String serviceName : servicesToAdd ) {
0 commit comments