use of com.aws.greengrass.dependency.State in project aws-greengrass-nucleus by aws-greengrass.
the class DeploymentTaskIntegrationTest method GIVEN_services_running_WHEN_new_service_breaks_failure_handling_policy_rollback_THEN_services_are_rolled_back.
/**
* First deployment starts some services. Second deployment tries to add a service that breaks and removes an
* existing service and the failure handling policy is to rollback As a result, kernel should be reverted to the
* state before deployment
*
* @throws Exception
*/
@Test
@Order(11)
void GIVEN_services_running_WHEN_new_service_breaks_failure_handling_policy_rollback_THEN_services_are_rolled_back(ExtensionContext context) throws Exception {
Map<String, Object> pkgDetails = new HashMap<>();
pkgDetails.put(GROUP_TO_ROOT_COMPONENTS_VERSION_KEY, "1.0.0");
groupToRootComponentsTopics.lookupTopics("RedSignal").replaceAndWait(pkgDetails);
groupToRootComponentsTopics.lookupTopics("YellowSignal").replaceAndWait(pkgDetails);
Future<DeploymentResult> resultFuture = submitSampleJobDocument(DeploymentTaskIntegrationTest.class.getResource("YellowAndRedSignal.json").toURI(), System.currentTimeMillis());
resultFuture.get(DEPLOYMENT_TIMEOUT, TimeUnit.SECONDS);
List<String> services = kernel.orderedDependencies().stream().filter(greengrassService -> greengrassService instanceof GenericExternalService).map(GreengrassService::getName).collect(Collectors.toList());
// should contain main, Nucleus, YellowSignal and RedSignal
assertEquals(4, services.size());
assertThat(services, containsInAnyOrder("main", DEFAULT_NUCLEUS_COMPONENT_NAME, "YellowSignal", "RedSignal"));
ignoreExceptionUltimateCauseOfType(context, ServiceUpdateException.class);
groupToRootComponentsTopics.lookupTopics("YellowSignal").remove();
groupToRootComponentsTopics.lookupTopics("BreakingService").replaceAndWait(ImmutableMap.of(GROUP_TO_ROOT_COMPONENTS_VERSION_KEY, "1.0.0"));
preloadLocalStoreContent();
resultFuture = submitSampleJobDocument(DeploymentTaskIntegrationTest.class.getResource("FailureRollbackDeployment.json").toURI(), System.currentTimeMillis());
DeploymentResult result = resultFuture.get(60, TimeUnit.SECONDS);
services = kernel.orderedDependencies().stream().filter(greengrassService -> greengrassService instanceof GenericExternalService).map(GreengrassService::getName).collect(Collectors.toList());
// should contain main, Nucleus, YellowSignal, RedSignal
assertEquals(4, services.size());
assertThat(services, containsInAnyOrder("main", DEFAULT_NUCLEUS_COMPONENT_NAME, "YellowSignal", "RedSignal"));
assertThrows(ServiceLoadException.class, () -> kernel.locate("BreakingService"));
assertThrows(ServiceLoadException.class, () -> kernel.locate("Mosquitto"));
assertThrows(ServiceLoadException.class, () -> kernel.locate("GreenSignal"));
assertEquals(DeploymentResult.DeploymentStatus.FAILED_ROLLBACK_COMPLETE, result.getDeploymentStatus());
}
use of com.aws.greengrass.dependency.State in project aws-greengrass-nucleus by aws-greengrass.
the class DeploymentTaskIntegrationTest method GIVEN_broken_service_WHEN_new_service_breaks_failure_handling_policy_rollback_THEN_services_are_rolled_back.
/**
* This test verifies that if a deployment has a broken service and then a new deployment comes which removes that
* one, but fails for a different reason and rolls back, then it is able to roll back successfully.
*/
@Test
@Order(12)
void GIVEN_broken_service_WHEN_new_service_breaks_failure_handling_policy_rollback_THEN_services_are_rolled_back(ExtensionContext context) throws Exception {
ignoreExceptionUltimateCauseOfType(context, ServiceUpdateException.class);
// Deploy a broken config with no rollback
Future<DeploymentResult> resultFuture = submitSampleJobDocument(DeploymentTaskIntegrationTest.class.getResource("FailureDoNothingDeployment.json").toURI(), System.currentTimeMillis());
resultFuture.get(60, TimeUnit.SECONDS);
List<String> services = kernel.orderedDependencies().stream().filter(greengrassService -> greengrassService instanceof GenericExternalService).map(GreengrassService::getName).collect(Collectors.toList());
assertThat(services, containsInAnyOrder("main", DEFAULT_NUCLEUS_COMPONENT_NAME, "BreakingService", "RedSignal", "GreenSignal", "Mosquitto"));
// Deploy a new broken config (using a different service) which does rollback
preloadLocalStoreContent();
resultFuture = submitSampleJobDocument(DeploymentTaskIntegrationTest.class.getResource("Failure2RollbackDeployment.json").toURI(), System.currentTimeMillis());
DeploymentResult result = resultFuture.get(60, TimeUnit.SECONDS);
services = kernel.orderedDependencies().stream().filter(greengrassService -> greengrassService instanceof GenericExternalService).map(GreengrassService::getName).collect(Collectors.toList());
// Make sure that it rolls back to the previous state
assertThat(services, containsInAnyOrder("main", DEFAULT_NUCLEUS_COMPONENT_NAME, "BreakingService", "RedSignal", "GreenSignal", "Mosquitto"));
assertEquals(DeploymentResult.DeploymentStatus.FAILED_ROLLBACK_COMPLETE, result.getDeploymentStatus());
}
use of com.aws.greengrass.dependency.State in project aws-greengrass-nucleus by aws-greengrass.
the class DeploymentConfigMergingTest method GIVEN_kernel_running_single_service_WHEN_merge_same_doc_happens_twice_THEN_second_merge_should_not_restart_services.
@Test
void GIVEN_kernel_running_single_service_WHEN_merge_same_doc_happens_twice_THEN_second_merge_should_not_restart_services() throws Throwable {
// GIVEN
ConfigPlatformResolver.initKernelWithMultiPlatformConfig(kernel, getClass().getResource("single_service.yaml"));
// launch Nucleus
CountDownLatch mainRunning = new CountDownLatch(1);
kernel.getContext().addGlobalStateChangeListener((service, oldState, newState) -> {
if (service.getName().equals("main") && newState.equals(State.RUNNING)) {
mainRunning.countDown();
}
});
kernel.launch();
assertTrue(mainRunning.await(5, TimeUnit.SECONDS));
Map<String, Object> nucleusConfig = getNucleusConfig();
HashMap<String, Object> newConfig = new HashMap<String, Object>() {
{
put(SERVICES_NAMESPACE_TOPIC, new HashMap<String, Object>() {
{
put("main", new HashMap<String, Object>() {
{
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, Arrays.asList("new_service", DEFAULT_NUCLEUS_COMPONENT_NAME));
}
});
put("new_service", new HashMap<String, Object>() {
{
put(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, new HashMap<String, Object>() {
{
put(LIFECYCLE_RUN_NAMESPACE_TOPIC, "echo done");
}
});
put(SERVICE_DEPENDENCIES_NAMESPACE_TOPIC, Arrays.asList("new_service2"));
}
});
put("new_service2", new HashMap<String, Object>() {
{
put(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, new HashMap<String, Object>() {
{
put(LIFECYCLE_RUN_NAMESPACE_TOPIC, "echo done");
}
});
}
});
put(DEFAULT_NUCLEUS_COMPONENT_NAME, nucleusConfig);
}
});
}
};
// do first merge
CountDownLatch mainRestarted = new CountDownLatch(1);
AtomicBoolean newService2Started = new AtomicBoolean(false);
AtomicBoolean newServiceStarted = new AtomicBoolean(false);
GlobalStateChangeListener listener = (service, oldState, newState) -> {
if (service.getName().equals("new_service2") && newState.equals(State.RUNNING)) {
newService2Started.set(true);
}
if (newService2Started.get() && service.getName().equals("new_service") && newState.equals(State.RUNNING)) {
newServiceStarted.set(true);
}
// Only count main as started if its dependency (new_service) has already been started
if (newServiceStarted.get() && service.getName().equals("main") && newState.equals(State.FINISHED) && oldState.equals(State.STARTING)) {
mainRestarted.countDown();
}
};
kernel.getContext().addGlobalStateChangeListener(listener);
GreengrassService main = kernel.locate("main");
deploymentConfigMerger.mergeInNewConfig(testDeployment(), newConfig).get(60, TimeUnit.SECONDS);
// Verify that first merge succeeded.
assertTrue(newService2Started.get());
assertTrue(newServiceStarted.get());
assertTrue(mainRestarted.await(10, TimeUnit.SECONDS));
assertThat(kernel.orderedDependencies().stream().map(GreengrassService::getName).collect(Collectors.toList()), containsInRelativeOrder("new_service2", "new_service", "main"));
// Wait for main to finish before continuing, otherwise the state change listner may cause a failure
assertThat(main::getState, eventuallyEval(is(State.FINISHED)));
// WHEN
AtomicBoolean stateChanged = new AtomicBoolean(false);
listener = (service, oldState, newState) -> {
System.err.println("State shouldn't change in merging the same config: " + service.getName() + " " + oldState + " => " + newState);
stateChanged.set(true);
};
kernel.getContext().addGlobalStateChangeListener(listener);
// THEN
// merge in the same config the second time
// merge shouldn't block
deploymentConfigMerger.mergeInNewConfig(testDeployment(), newConfig).get(60, TimeUnit.SECONDS);
// main should be finished
assertEquals(State.FINISHED, main.getState());
assertFalse(stateChanged.get(), "State shouldn't change in merging the same config.");
// remove listener
kernel.getContext().removeGlobalStateChangeListener(listener);
}
use of com.aws.greengrass.dependency.State in project aws-greengrass-nucleus by aws-greengrass.
the class ShadowDeploymentE2ETest method GIVEN_device_deployment_WHEN_shadow_update_messages_gets_delivered_out_of_order_THEN_shadow_updated_with_latest_deployment_status.
@Test
void GIVEN_device_deployment_WHEN_shadow_update_messages_gets_delivered_out_of_order_THEN_shadow_updated_with_latest_deployment_status() throws Exception {
CreateDeploymentRequest createDeploymentRequest = CreateDeploymentRequest.builder().targetArn(thingInfo.getThingArn()).components(Utils.immutableMap("CustomerApp", ComponentDeploymentSpecification.builder().componentVersion("1.0.0").build(), "SomeService", ComponentDeploymentSpecification.builder().componentVersion("1.0.0").build())).build();
draftAndCreateDeployment(createDeploymentRequest);
assertThat(kernel.getMain()::getState, eventuallyEval(is(State.FINISHED)));
IotShadowClient shadowClient = new IotShadowClient(new WrapperMqttClientConnection(kernel.getContext().get(MqttClient.class)));
UpdateNamedShadowSubscriptionRequest req = new UpdateNamedShadowSubscriptionRequest();
req.shadowName = DEPLOYMENT_SHADOW_NAME;
req.thingName = thingInfo.getThingName();
CountDownLatch reportSucceededCdl = new CountDownLatch(1);
CountDownLatch deviceSyncedStateToSucceededCdl = new CountDownLatch(1);
AtomicReference<HashMap<String, Object>> reportedSection = new AtomicReference<>();
AtomicReference<Integer> shadowVersionWhenDeviceFirstReportedSuccess = new AtomicReference<>();
AtomicReference<Integer> shadowVersionWhenDeviceReportedInProgress = new AtomicReference<>();
shadowClient.SubscribeToUpdateNamedShadowAccepted(req, QualityOfService.AT_LEAST_ONCE, (response) -> {
try {
logger.info("Got shadow update: {}", new ObjectMapper().writeValueAsString(response));
} catch (JsonProcessingException e) {
// ignore
}
if (response.state.reported == null) {
return;
}
String reportedStatus = (String) response.state.reported.get(STATUS_KEY);
if (JobStatus.IN_PROGRESS.toString().equals(reportedStatus)) {
reportedSection.set(response.state.reported);
shadowVersionWhenDeviceReportedInProgress.set(response.version);
} else if (JobStatus.SUCCEEDED.toString().equals(reportedStatus)) {
// state to SUCCESS second time the shadow version
if (reportSucceededCdl.getCount() == 0 && response.version > shadowVersionWhenDeviceFirstReportedSuccess.get()) {
deviceSyncedStateToSucceededCdl.countDown();
}
shadowVersionWhenDeviceFirstReportedSuccess.set(response.version);
reportSucceededCdl.countDown();
}
});
// waiting for the device to report success
assertTrue(reportSucceededCdl.await(60, TimeUnit.SECONDS));
// Updating the shadow with deployment status IN_PROGRESS to simulate out-of-order update of shadow
ShadowState shadowState = new ShadowState();
shadowState.reported = reportedSection.get();
UpdateNamedShadowRequest updateNamedShadowRequest = new UpdateNamedShadowRequest();
updateNamedShadowRequest.shadowName = DEPLOYMENT_SHADOW_NAME;
updateNamedShadowRequest.thingName = thingInfo.getThingName();
updateNamedShadowRequest.state = shadowState;
shadowClient.PublishUpdateNamedShadow(updateNamedShadowRequest, QualityOfService.AT_LEAST_ONCE).get(30, TimeUnit.SECONDS);
// verify that the device updates shadow state to SUCCEEDED
assertTrue(deviceSyncedStateToSucceededCdl.await(60, TimeUnit.SECONDS));
// Updating the shadow with a lower version number to trigger a message to /update/rejected event
shadowState = new ShadowState();
shadowState.reported = reportedSection.get();
updateNamedShadowRequest = new UpdateNamedShadowRequest();
updateNamedShadowRequest.shadowName = DEPLOYMENT_SHADOW_NAME;
updateNamedShadowRequest.thingName = thingInfo.getThingName();
updateNamedShadowRequest.state = shadowState;
updateNamedShadowRequest.version = shadowVersionWhenDeviceReportedInProgress.get();
shadowClient.PublishUpdateNamedShadow(updateNamedShadowRequest, QualityOfService.AT_LEAST_ONCE).get(30, TimeUnit.SECONDS);
CountDownLatch deviceRetrievedShadowCdl = new CountDownLatch(1);
GetNamedShadowSubscriptionRequest getNamedShadowSubscriptionRequest = new GetNamedShadowSubscriptionRequest();
getNamedShadowSubscriptionRequest.shadowName = DEPLOYMENT_SHADOW_NAME;
getNamedShadowSubscriptionRequest.thingName = thingInfo.getThingName();
shadowClient.SubscribeToGetNamedShadowAccepted(getNamedShadowSubscriptionRequest, QualityOfService.AT_MOST_ONCE, getShadowResponse -> {
deviceRetrievedShadowCdl.countDown();
}).get(30, TimeUnit.SECONDS);
// verify that the device retrieved the shadow when an update operation was rejected.
assertTrue(deviceRetrievedShadowCdl.await(60, TimeUnit.SECONDS));
}
use of com.aws.greengrass.dependency.State in project aws-greengrass-nucleus by aws-greengrass.
the class DeploymentConfigMerger method waitForServicesToStart.
/**
* Completes the provided future when all of the listed services are running.
*
* @param servicesToTrack services to track
* @param mergeTime time the merge was started, used to check if a service is broken due to the merge
* @throws InterruptedException if the thread is interrupted while waiting here
* @throws ServiceUpdateException if a service could not be updated
*/
public static void waitForServicesToStart(Collection<GreengrassService> servicesToTrack, long mergeTime) throws InterruptedException, ServiceUpdateException {
// assuming this loop will not get stuck waiting forever
while (true) {
boolean allServicesRunning = true;
for (GreengrassService service : servicesToTrack) {
State state = service.getState();
// executes. Therefore we first check the service state has been updated since merge map occurs.
if (service.getStateModTime() > mergeTime && State.BROKEN.equals(state)) {
logger.atWarn(MERGE_CONFIG_EVENT_KEY).kv(SERVICE_NAME_LOG_KEY, service.getName()).log("merge-config-service BROKEN");
throw new ServiceUpdateException(String.format("Service %s in broken state after deployment", service.getName()));
}
if (!service.reachedDesiredState()) {
allServicesRunning = false;
continue;
}
if (State.RUNNING.equals(state) || State.FINISHED.equals(state) || !service.shouldAutoStart() && service.reachedDesiredState()) {
continue;
}
allServicesRunning = false;
}
if (allServicesRunning) {
return;
}
// hardcoded
Thread.sleep(WAIT_SVC_START_POLL_INTERVAL_MILLISEC);
}
}
Aggregations