Auto Scaling examples using SDK for Rust (original) (raw)
[package]
name = "autoscaling-code-examples"
version = "0.1.0"
authors = ["Doug Schwartz <dougsch@amazon.com>", "David Souther <dpsouth@amazon.com>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
aws-config = { version = "1.0.1", features = ["behavior-version-latest"] }
aws-sdk-autoscaling = { version = "1.3.0" }
aws-sdk-ec2 = { version = "1.3.0" }
aws-types = { version = "1.0.1" }
tokio = { version = "1.20.1", features = ["full"] }
clap = { version = "4.4", features = ["derive"] }
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
anyhow = "1.0.75"
tracing = "0.1.37"
tokio-stream = "0.1.14"
use std::{collections::BTreeSet, fmt::Display};
use anyhow::anyhow;
use autoscaling_code_examples::scenario::{AutoScalingScenario, ScenarioError};
use tracing::{info, warn};
async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) {
let description = scenario.describe_scenario().await;
info!("DescribeAutoScalingInstances: {event}\n{description}");
}
#[derive(Default, Debug)]
struct Warnings(Vec<String>);
impl Warnings {
pub fn push(&mut self, warning: &str, error: ScenarioError) {
let formatted = format!("{warning}: {error}");
warn!("{formatted}");
self.0.push(formatted);
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Display for Warnings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Warnings:")?;
for warning in &self.0 {
writeln!(f, "{: >4}- {warning}", "")?;
}
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
tracing_subscriber::fmt::init();
let shared_config = aws_config::from_env().load().await;
let mut warnings = Warnings::default();
// 1. Create an EC2 launch template that you'll use to create an auto scaling group. Bonus: use SDK with EC2.CreateLaunchTemplate to create the launch template.
// 2. CreateAutoScalingGroup: pass it the launch template you created in step 0. Give it min/max of 1 instance.
// 4. EnableMetricsCollection: enable all metrics or a subset.
let scenario = match AutoScalingScenario::prepare_scenario(&shared_config).await {
Ok(scenario) => scenario,
Err(errs) => {
let err_str = errs
.into_iter()
.map(|e| e.to_string())
.collect::<Vec<String>>()
.join(", ");
return Err(anyhow!("Failed to initialize scenario: {err_str}"));
}
};
info!("Prepared autoscaling scenario:\n{scenario}");
let stable = scenario.wait_for_stable(1).await;
if let Err(err) = stable {
warnings.push(
"There was a problem while waiting for group to be stable",
err,
);
}
// 3. DescribeAutoScalingInstances: show that one instance has launched.
show_scenario_description(
&scenario,
"show that the group was created and one instance has launched",
)
.await;
// 5. UpdateAutoScalingGroup: update max size to 3.
let scale_max_size = scenario.scale_max_size(3).await;
if let Err(err) = scale_max_size {
warnings.push("There was a problem scaling max size", err);
}
// 6. DescribeAutoScalingGroups: the current state of the group
show_scenario_description(
&scenario,
"show the current state of the group after setting max size",
)
.await;
// 7. SetDesiredCapacity: set desired capacity to 2.
let scale_desired_capacity = scenario.scale_desired_capacity(2).await;
if let Err(err) = scale_desired_capacity {
warnings.push("There was a problem setting desired capacity", err);
}
// Wait for a second instance to launch.
let stable = scenario.wait_for_stable(2).await;
if let Err(err) = stable {
warnings.push(
"There was a problem while waiting for group to be stable",
err,
);
}
// 8. DescribeAutoScalingInstances: show that two instances are launched.
show_scenario_description(
&scenario,
"show that two instances are launched after setting desired capacity",
)
.await;
let ids_before = scenario
.list_instances()
.await
.map(|v| v.into_iter().collect::<BTreeSet<_>>())
.unwrap_or_default();
// 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group.
let terminate_some_instance = scenario.terminate_some_instance().await;
if let Err(err) = terminate_some_instance {
warnings.push("There was a problem replacing an instance", err);
}
let wait_after_terminate = scenario.wait_for_stable(1).await;
if let Err(err) = wait_after_terminate {
warnings.push(
"There was a problem waiting after terminating an instance",
err,
);
}
let wait_scale_up_after_terminate = scenario.wait_for_stable(2).await;
if let Err(err) = wait_scale_up_after_terminate {
warnings.push(
"There was a problem waiting for scale up after terminating an instance",
err,
);
}
let ids_after = scenario
.list_instances()
.await
.map(|v| v.into_iter().collect::<BTreeSet<_>>())
.unwrap_or_default();
let difference = ids_after.intersection(&ids_before).count();
if !(difference == 1 && ids_before.len() == 2 && ids_after.len() == 2) {
warnings.push(
"Before and after set not different",
ScenarioError::with(format!("{difference}")),
);
}
// 10. DescribeScalingActivities: list the scaling activities that have occurred for the group so far.
show_scenario_description(
&scenario,
"list the scaling activities that have occurred for the group so far",
)
.await;
// 11. DisableMetricsCollection
let scale_group = scenario.scale_group_to_zero().await;
if let Err(err) = scale_group {
warnings.push("There was a problem scaling the group to 0", err);
}
show_scenario_description(&scenario, "Scenario scaled to 0").await;
// 12. DeleteAutoScalingGroup (to delete the group you must stop all instances):
// 13. Delete LaunchTemplate.
let clean_scenario = scenario.clean_scenario().await;
if let Err(errs) = clean_scenario {
for err in errs {
warnings.push("There was a problem cleaning the scenario", err);
}
} else {
info!("The scenario has been cleaned up!");
}
if warnings.is_empty() {
Ok(())
} else {
Err(anyhow!(
"There were warnings during scenario execution:\n{warnings}"
))
}
}
pub mod scenario;
use std::{
error::Error,
fmt::{Debug, Display},
time::{Duration, SystemTime},
};
use anyhow::anyhow;
use aws_config::SdkConfig;
use aws_sdk_autoscaling::{
error::{DisplayErrorContext, ProvideErrorMetadata},
types::{Activity, AutoScalingGroup, LaunchTemplateSpecification},
};
use aws_sdk_ec2::types::RequestLaunchTemplateData;
use tracing::trace;
const LAUNCH_TEMPLATE_NAME: &str = "SDK_Code_Examples_EC2_Autoscaling_template_from_Rust_SDK";
const AUTOSCALING_GROUP_NAME: &str = "SDK_Code_Examples_EC2_Autoscaling_Group_from_Rust_SDK";
const MAX_WAIT: Duration = Duration::from_secs(5 * 60); // Wait at most 25 seconds.
const WAIT_TIME: Duration = Duration::from_millis(500); // Wait half a second at a time.
struct Waiter {
start: SystemTime,
max: Duration,
}
impl Waiter {
fn new() -> Self {
Waiter {
start: SystemTime::now(),
max: MAX_WAIT,
}
}
async fn sleep(&self) -> Result<(), ScenarioError> {
if SystemTime::now()
.duration_since(self.start)
.unwrap_or(Duration::MAX)
> self.max
{
Err(ScenarioError::with(
"Exceeded maximum wait duration for stable group",
))
} else {
tokio::time::sleep(WAIT_TIME).await;
Ok(())
}
}
}
pub struct AutoScalingScenario {
ec2: aws_sdk_ec2::Client,
autoscaling: aws_sdk_autoscaling::Client,
launch_template_arn: String,
auto_scaling_group_name: String,
}
impl Display for AutoScalingScenario {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"\tLaunch Template ID: {}\n",
self.launch_template_arn
))?;
f.write_fmt(format_args!(
"\tScaling Group Name: {}\n",
self.auto_scaling_group_name
))?;
Ok(())
}
}
pub struct AutoScalingScenarioDescription {
group: Result<Vec<String>, ScenarioError>,
instances: Result<Vec<String>, anyhow::Error>,
activities: Result<Vec<Activity>, anyhow::Error>,
}
impl Display for AutoScalingScenarioDescription {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "\t Group status:")?;
match &self.group {
Ok(groups) => {
for status in groups {
writeln!(f, "\t\t- {status}")?;
}
}
Err(e) => writeln!(f, "\t\t! - {e}")?,
}
writeln!(f, "\t Instances:")?;
match &self.instances {
Ok(instances) => {
for instance in instances {
writeln!(f, "\t\t- {instance}")?;
}
}
Err(e) => writeln!(f, "\t\t! {e}")?,
}
writeln!(f, "\t Activities:")?;
match &self.activities {
Ok(activities) => {
for activity in activities {
writeln!(
f,
"\t\t- {} Progress: {}% Status: {:?} End: {:?}",
activity.cause().unwrap_or("Unknown"),
activity.progress.unwrap_or(-1),
activity.status_code(),
// activity.status_message().unwrap_or_default()
activity.end_time(),
)?;
}
}
Err(e) => writeln!(f, "\t\t! {e}")?,
}
Ok(())
}
}
#[derive(Debug)]
struct MetadataError {
message: Option<String>,
code: Option<String>,
}
impl MetadataError {
fn from(err: &dyn ProvideErrorMetadata) -> Self {
MetadataError {
message: err.message().map(|s| s.to_string()),
code: err.code().map(|s| s.to_string()),
}
}
}
impl Display for MetadataError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let display = match (&self.message, &self.code) {
(None, None) => "Unknown".to_string(),
(None, Some(code)) => format!("({code})"),
(Some(message), None) => message.to_string(),
(Some(message), Some(code)) => format!("{message} ({code})"),
};
write!(f, "{display}")
}
}
#[derive(Debug)]
pub struct ScenarioError {
message: String,
context: Option<MetadataError>,
}
impl ScenarioError {
pub fn with(message: impl Into<String>) -> Self {
ScenarioError {
message: message.into(),
context: None,
}
}
pub fn new(message: impl Into<String>, err: &dyn ProvideErrorMetadata) -> Self {
ScenarioError {
message: message.into(),
context: Some(MetadataError::from(err)),
}
}
}
impl Error for ScenarioError {
// While `Error` can capture `source` information about the underlying error, for this example
// the ScenarioError captures the underlying information in MetadataError and treats it as a
// single Error from this Crate. In other contexts, it may be appropriate to model the error
// as including the SdkError as its source.
}
impl Display for ScenarioError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.context {
Some(c) => write!(f, "{}: {}", self.message, c),
None => write!(f, "{}", self.message),
}
}
}
impl AutoScalingScenario {
pub async fn prepare_scenario(sdk_config: &SdkConfig) -> Result<Self, Vec<ScenarioError>> {
let ec2 = aws_sdk_ec2::Client::new(sdk_config);
let autoscaling = aws_sdk_autoscaling::Client::new(sdk_config);
let auto_scaling_group_name = String::from(AUTOSCALING_GROUP_NAME);
// Before creating any resources, prepare the list of AZs
let availablity_zones = ec2.describe_availability_zones().send().await;
if let Err(err) = availablity_zones {
return Err(vec![ScenarioError::new("Failed to find AZs", &err)]);
}
let availability_zones: Vec<String> = availablity_zones
.unwrap()
.availability_zones
.unwrap_or_default()
.iter()
.take(3)
.map(|z| z.zone_name.clone().unwrap())
.collect();
// 1. Create an EC2 launch template that you'll use to create an auto scaling group. Bonus: use SDK with EC2.CreateLaunchTemplate to create the launch template.
// * Recommended: InstanceType='t1.micro', ImageId='ami-0ca285d4c2cda3300'
let create_launch_template = ec2
.create_launch_template()
.launch_template_name(LAUNCH_TEMPLATE_NAME)
.launch_template_data(
RequestLaunchTemplateData::builder()
.instance_type(aws_sdk_ec2::types::InstanceType::T1Micro)
.image_id("ami-0ca285d4c2cda3300")
.build(),
)
.send()
.await
.map_err(|err| vec![ScenarioError::new("Failed to create launch template", &err)])?;
let launch_template_arn = match create_launch_template.launch_template {
Some(launch_template) => launch_template.launch_template_id.unwrap_or_default(),
None => {
// Try to delete the launch template
let _ = ec2
.delete_launch_template()
.launch_template_name(LAUNCH_TEMPLATE_NAME)
.send()
.await;
return Err(vec![ScenarioError::with("Failed to load launch template")]);
}
};
// 2. CreateAutoScalingGroup: pass it the launch template you created in step 0. Give it min/max of 1 instance.
// You can use EC2.describe_availability_zones() to get a list of AZs (you have to specify an AZ when you create the group).
// Wait for instance to launch. Use a waiter if you have one, otherwise DescribeAutoScalingInstances until LifecycleState='InService'
if let Err(err) = autoscaling
.create_auto_scaling_group()
.auto_scaling_group_name(auto_scaling_group_name.as_str())
.launch_template(
LaunchTemplateSpecification::builder()
.launch_template_id(launch_template_arn.clone())
.version("$Latest")
.build(),
)
.max_size(1)
.min_size(1)
.set_availability_zones(Some(availability_zones))
.send()
.await
{
let mut errs = vec![ScenarioError::new(
"Failed to create autoscaling group",
&err,
)];
if let Err(err) = autoscaling
.delete_auto_scaling_group()
.auto_scaling_group_name(auto_scaling_group_name.as_str())
.send()
.await
{
errs.push(ScenarioError::new(
"Failed to clean up autoscaling group",
&err,
));
}
if let Err(err) = ec2
.delete_launch_template()
.launch_template_id(launch_template_arn.clone())
.send()
.await
{
errs.push(ScenarioError::new(
"Failed to clean up launch template",
&err,
));
}
return Err(errs);
}
let scenario = AutoScalingScenario {
ec2,
autoscaling: autoscaling.clone(), // Clients are cheap so cloning here to prevent a move is ok.
auto_scaling_group_name: auto_scaling_group_name.clone(),
launch_template_arn,
};
let enable_metrics_collection = autoscaling
.enable_metrics_collection()
.auto_scaling_group_name(auto_scaling_group_name.as_str())
.granularity("1Minute")
.set_metrics(Some(vec![
String::from("GroupMinSize"),
String::from("GroupMaxSize"),
String::from("GroupDesiredCapacity"),
String::from("GroupInServiceInstances"),
String::from("GroupTotalInstances"),
]))
.send()
.await;
match enable_metrics_collection {
Ok(_) => Ok(scenario),
Err(err) => {
scenario.clean_scenario().await?;
Err(vec![ScenarioError::new(
"Failed to enable metrics collections for group",
&err,
)])
}
}
}
pub async fn clean_scenario(self) -> Result<(), Vec<ScenarioError>> {
let _ = self.wait_for_no_scaling().await;
let delete_group = self
.autoscaling
.delete_auto_scaling_group()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.send()
.await;
// 14. Delete LaunchTemplate.
let delete_launch_template = self
.ec2
.delete_launch_template()
.launch_template_id(self.launch_template_arn.clone())
.send()
.await;
let early_exit = match (delete_group, delete_launch_template) {
(Ok(_), Ok(_)) => Ok(()),
(Ok(_), Err(e)) => Err(vec![ScenarioError::new(
"There was an error cleaning the launch template",
&e,
)]),
(Err(e), Ok(_)) => Err(vec![ScenarioError::new(
"There was an error cleaning the scale group",
&e,
)]),
(Err(e1), Err(e2)) => Err(vec![
ScenarioError::new("Multiple error cleaning the scenario Scale Group", &e1),
ScenarioError::new("Multiple error cleaning the scenario Launch Template", &e2),
]),
};
if early_exit.is_err() {
early_exit
} else {
// Wait for delete_group to finish
let waiter = Waiter::new();
let mut errors = Vec::<ScenarioError>::new();
while errors.len() < 3 {
if let Err(e) = waiter.sleep().await {
errors.push(e);
continue;
}
let describe_group = self
.autoscaling
.describe_auto_scaling_groups()
.auto_scaling_group_names(self.auto_scaling_group_name.clone())
.send()
.await;
match describe_group {
Ok(group) => match group.auto_scaling_groups().first() {
Some(group) => {
if group.status() != Some("Delete in progress") {
errors.push(ScenarioError::with(format!(
"Group in an unknown state while deleting: {}",
group.status().unwrap_or("unknown error")
)));
return Err(errors);
}
}
None => return Ok(()),
},
Err(err) => {
errors.push(ScenarioError::new("Failed to describe autoscaling group during cleanup 3 times, last error", &err));
}
}
if errors.len() > 3 {
return Err(errors);
}
}
Err(vec![ScenarioError::with(
"Exited cleanup wait loop without retuning success or failing after three rounds",
)])
}
}
pub async fn describe_scenario(&self) -> AutoScalingScenarioDescription {
let group = self
.autoscaling
.describe_auto_scaling_groups()
.auto_scaling_group_names(self.auto_scaling_group_name.clone())
.send()
.await
.map(|s| {
s.auto_scaling_groups()
.iter()
.map(|s| {
format!(
"{}: {}",
s.auto_scaling_group_name().unwrap_or("Unknown"),
s.status().unwrap_or("Unknown")
)
})
.collect::<Vec<String>>()
})
.map_err(|e| {
ScenarioError::new("Failed to describe auto scaling groups for scenario", &e)
});
let instances = self
.list_instances()
.await
.map_err(|e| anyhow!("There was an error listing instances: {e}",));
// 10. DescribeScalingActivities: list the scaling activities that have occurred for the group so far.
// Bonus: use CloudWatch API to get and show some metrics collected for the group.
// CW.ListMetrics with Namespace='AWS/AutoScaling' and Dimensions=[{'Name': 'AutoScalingGroupName', 'Value': }]
// CW.GetMetricStatistics with Statistics='Sum'. Start and End times must be in UTC!
let activities = self
.autoscaling
.describe_scaling_activities()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.into_paginator()
.items()
.send()
.collect::<Result<Vec<_>, _>>()
.await
.map_err(|e| {
anyhow!(
"There was an error retrieving scaling activities: {}",
DisplayErrorContext(&e)
)
});
AutoScalingScenarioDescription {
group,
instances,
activities,
}
}
async fn get_group(&self) -> Result<AutoScalingGroup, ScenarioError> {
let describe_auto_scaling_groups = self
.autoscaling
.describe_auto_scaling_groups()
.auto_scaling_group_names(self.auto_scaling_group_name.clone())
.send()
.await;
if let Err(err) = describe_auto_scaling_groups {
return Err(ScenarioError::new(
format!(
"Failed to get status of autoscaling group {}",
self.auto_scaling_group_name.clone()
)
.as_str(),
&err,
));
}
let describe_auto_scaling_groups_output = describe_auto_scaling_groups.unwrap();
let auto_scaling_groups = describe_auto_scaling_groups_output.auto_scaling_groups();
let auto_scaling_group = auto_scaling_groups.first();
if auto_scaling_group.is_none() {
return Err(ScenarioError::with(format!(
"Could not find autoscaling group {}",
self.auto_scaling_group_name.clone()
)));
}
Ok(auto_scaling_group.unwrap().clone())
}
pub async fn wait_for_no_scaling(&self) -> Result<(), ScenarioError> {
let waiter = Waiter::new();
let mut scaling = true;
while scaling {
waiter.sleep().await?;
let describe_activities = self
.autoscaling
.describe_scaling_activities()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.send()
.await
.map_err(|e| {
ScenarioError::new("Failed to get autoscaling activities for group", &e)
})?;
let activities = describe_activities.activities();
trace!(
"Waiting for no scaling found {} activities",
activities.len()
);
scaling = activities.iter().any(|a| a.progress() < Some(100));
}
Ok(())
}
pub async fn wait_for_stable(&self, size: usize) -> Result<(), ScenarioError> {
self.wait_for_no_scaling().await?;
let mut group = self.get_group().await?;
let mut count = count_group_instances(&group);
let waiter = Waiter::new();
while count != size {
trace!("Waiting for stable {size} (current: {count})");
waiter.sleep().await?;
group = self.get_group().await?;
count = count_group_instances(&group);
}
Ok(())
}
pub async fn list_instances(&self) -> Result<Vec<String>, ScenarioError> {
// The direct way to list instances is by using DescribeAutoScalingGroup's instances property. However, this returns a Vec<Instance>, as opposed to a Vec<AutoScalingInstanceDetails>.
// Ok(self.get_group().await?.instances.unwrap_or_default().map(|i| i.instance_id.clone().unwrap_or_default()).filter(|id| !id.is_empty()).collect())
// Alternatively, and for the sake of example, DescribeAutoScalingInstances returns a list that can be filtered by the client.
self.autoscaling
.describe_auto_scaling_instances()
.into_paginator()
.items()
.send()
.try_collect()
.await
.map(|items| {
items
.into_iter()
.filter(|i| {
i.auto_scaling_group_name.as_deref()
== Some(self.auto_scaling_group_name.as_str())
})
.map(|i| i.instance_id.unwrap_or_default())
.filter(|id| !id.is_empty())
.collect::<Vec<String>>()
})
.map_err(|err| ScenarioError::new("Failed to get list of auto scaling instances", &err))
}
pub async fn scale_min_size(&self, size: i32) -> Result<(), ScenarioError> {
let update_group = self
.autoscaling
.update_auto_scaling_group()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.min_size(size)
.send()
.await;
if let Err(err) = update_group {
return Err(ScenarioError::new(
format!("Failer to update group to min size ({size}))").as_str(),
&err,
));
}
Ok(())
}
pub async fn scale_max_size(&self, size: i32) -> Result<(), ScenarioError> {
// 5. UpdateAutoScalingGroup: update max size to 3.
let update_group = self
.autoscaling
.update_auto_scaling_group()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.max_size(size)
.send()
.await;
if let Err(err) = update_group {
return Err(ScenarioError::new(
format!("Failed to update group to max size ({size})").as_str(),
&err,
));
}
Ok(())
}
pub async fn scale_desired_capacity(&self, capacity: i32) -> Result<(), ScenarioError> {
// 7. SetDesiredCapacity: set desired capacity to 2.
// Wait for a second instance to launch.
let update_group = self
.autoscaling
.set_desired_capacity()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.desired_capacity(capacity)
.send()
.await;
if let Err(err) = update_group {
return Err(ScenarioError::new(
format!("Failed to update group to desired capacity ({capacity}))").as_str(),
&err,
));
}
Ok(())
}
pub async fn scale_group_to_zero(&self) -> Result<(), ScenarioError> {
// If this fails it's fine, just means there are extra cloudwatch metrics events for the scale-down.
let _ = self
.autoscaling
.disable_metrics_collection()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.send()
.await;
// 12. DeleteAutoScalingGroup (to delete the group you must stop all instances):
// UpdateAutoScalingGroup with MinSize=0
let update_group = self
.autoscaling
.update_auto_scaling_group()
.auto_scaling_group_name(self.auto_scaling_group_name.clone())
.min_size(0)
.desired_capacity(0)
.send()
.await;
if let Err(err) = update_group {
return Err(ScenarioError::new(
"Failed to update group for scaling down&",
&err,
));
}
let stable = self.wait_for_stable(0).await;
if let Err(err) = stable {
return Err(ScenarioError::with(format!(
"Error while waiting for group to be stable on scale down: {err}"
)));
}
Ok(())
}
pub async fn terminate_some_instance(&self) -> Result<(), ScenarioError> {
// Retrieve a list of instances in the auto scaling group.
let auto_scaling_group = self.get_group().await?;
let instances = auto_scaling_group.instances();
// Or use other logic to find an instance to terminate.
let instance = instances.first();
if let Some(instance) = instance {
let instance_id = if let Some(instance_id) = instance.instance_id() {
instance_id
} else {
return Err(ScenarioError::with("Missing instance id"));
};
let termination = self
.ec2
.terminate_instances()
.instance_ids(instance_id)
.send()
.await;
if let Err(err) = termination {
Err(ScenarioError::new(
"There was a problem terminating an instance",
&err,
))
} else {
Ok(())
}
} else {
Err(ScenarioError::with("There was no instance to terminate"))
}
}
}
fn count_group_instances(group: &AutoScalingGroup) -> usize {
group.instances.as_ref().map(|i| i.len()).unwrap_or(0)
}
For API details, see the following topics in AWS SDK for Rust API reference.