From 0018d46ea2af16f6581eedeedcd19c850bccb36f Mon Sep 17 00:00:00 2001 From: David Souther Date: Tue, 5 Sep 2023 17:44:22 -0400 Subject: [PATCH 01/10] Structure and outline for ec2 autoscaling scenario --- rust_dev_preview/examples/ec2/Cargo.toml | 5 +- .../examples/ec2/src/autoscaling.rs | 112 ++++++++++++++++++ .../examples/ec2/src/bin/autoscaling.rs | 74 ++++++++++++ rust_dev_preview/examples/ec2/src/lib.rs | 1 + 4 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 rust_dev_preview/examples/ec2/src/autoscaling.rs create mode 100644 rust_dev_preview/examples/ec2/src/bin/autoscaling.rs create mode 100644 rust_dev_preview/examples/ec2/src/lib.rs diff --git a/rust_dev_preview/examples/ec2/Cargo.toml b/rust_dev_preview/examples/ec2/Cargo.toml index f1e3575f66b..4b080f51b8f 100644 --- a/rust_dev_preview/examples/ec2/Cargo.toml +++ b/rust_dev_preview/examples/ec2/Cargo.toml @@ -9,10 +9,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] aws-config = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } aws-sdk-ec2 = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } aws-types = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } tokio = { version = "1.20.1", features = ["full"] } clap = { version = "~4.2", features = ["derive"] } -tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } +anyhow = "1.0.75" +tracing = "0.1.37" diff --git a/rust_dev_preview/examples/ec2/src/autoscaling.rs b/rust_dev_preview/examples/ec2/src/autoscaling.rs new file mode 100644 index 00000000000..3a2d9756ec1 --- /dev/null +++ b/rust_dev_preview/examples/ec2/src/autoscaling.rs @@ -0,0 +1,112 @@ +use aws_sdk_ec2::types::RequestLaunchTemplateData; +use aws_types::SdkConfig; + +pub struct AutoScalingScenario { + client: aws_sdk_ec2::Client, + launch_template_arn: String, + auto_scaling_group_arn: String, +} + +pub struct AutoScalingScenarioDescription {} + +impl AutoScalingScenario { + pub fn launch_template_arn(&self) -> &str { + self.launch_template_arn.as_str() + } + + pub fn auto_scaling_group_arn(&self) -> &str { + self.auto_scaling_group_arn.as_str() + } + + pub async fn prepare_scenario(config: SdkConfig) -> Result { + let client = aws_sdk_ec2::Client::new(&config); + + // 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 = client + .create_launch_template() + .launch_template_data( + RequestLaunchTemplateData::builder() + .instance_type(aws_sdk_ec2::types::InstanceType::T1Micro) + .image_id("ami-0ca285d4c2cda3300") + .build(), + ) + .send() + .await?; + + let launch_template_arn = match create_launch_template.launch_template { + Some(launch_template) => launch_template.launch_template_id.unwrap_or_default(), + None => { + return Err(anyhow::anyhow!("Failed to load launch template"))-0p;hj87´ªº + } + }; + + // 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' + let auto_scaling_group_arn = String::new(); + + // 4. EnableMetricsCollection: enable all metrics or a subset. + // If a subset, this list makes the most sense: 'GroupMinSize', 'GroupMaxSize', 'GroupDesiredCapacity', 'GroupInServiceInstances', 'GroupTotalInstances'. + + Ok(AutoScalingScenario { + client, + auto_scaling_group_arn, + launch_template_arn, + }) + } + + pub async fn clean_scenario(self) -> Result<(), anyhow::Error> { + self.scale_group().await?; + + // 14. Delete LaunchTemplate. + todo!() + } + + pub async fn describe_scenario(&self) -> Result { + // 3. DescribeAutoScalingInstances: show that one instance has launched. + // 8. DescribeAutoScalingInstances: show that two instances are launched. + // 6. DescribeAutoScalingGroups: show the current state of the group. + // 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! + + todo!() + } + + pub async fn list_instance_arns(&self) -> Result, anyhow::Error> { + todo!(); + } + + pub async fn scale_max_size(&self, size: usize) -> Result<(), anyhow::Error> { + // 5. UpdateAutoScalingGroup: update max size to 3. + todo!(); + } + + pub async fn scale_desired_capacity(&self, capacity: usize) -> Result<(), anyhow::Error> { + // 7. SetDesiredCapacity: set desired capacity to 2. + // Wait for a second instance to launch. + todo!() + } + + pub async fn scale_group(&self) -> Result<(), anyhow::Error> { + // 11. DisableMetricsCollection + + // 12. DeleteAutoScalingGroup (to delete the group you must stop all instances): + // UpdateAutoScalingGroup with MinSize=0 + // TerminateInstanceInAutoScalingGroup for each instance, specify ShouldDecrementDesiredCapacity=True. Wait for instances to stop. + // Now you can delete the group. + todo!(); + } + + pub async fn terminate_instance( + &self, + instance_arn: String, + reduce_capacity: bool, + ) -> Result<(), anyhow::Error> { + // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. + // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. + todo!() + } +} diff --git a/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs b/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs new file mode 100644 index 00000000000..d9b1a84b152 --- /dev/null +++ b/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs @@ -0,0 +1,74 @@ +use aws_config::meta::region::RegionProviderChain; +use aws_types::region::Region; +use ec2_code_examples::autoscaling::AutoScalingScenario; +use tracing::info; + +async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) { + match scenario.describe_scenario().await { + Ok(description) => info!(description, "DescribeAutoScalingInstances: {event}"), + Err(err) => info!(err, "Error in DescribeAutoScalingInstances: {event}"), + } +} + +#[tokio::main] +async fn main() -> Result<(), anyhow::Error> { + tracing_subscriber::fmt::init(); + + let region_provider = RegionProviderChain::first_try(region.map(Region::new)) + .or_default_provider() + .or_else(Region::new("us-west-2")); + let shared_config = aws_config::from_env().region(region_provider).load().await; + + // 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 = AutoScalingScenario::prepare_scenario(shared_config).await?; + + // 3. DescribeAutoScalingInstances: show that one instance has launched. + show_scenario_description(&scenario, &"show that one instance has launched").await; + + // 5. UpdateAutoScalingGroup: update max size to 3. + if let Err(err) = scenario.scale_max_size(3).await { + info!(err, "there was a problem scaling max size"); + } + + // 6. DescribeAutoScalingGroups: the current state of the group + show_scenario_description(&scenario, &"show the current state of the group").await; + + // 7. SetDesiredCapacity: set desired capacity to 2. + if let Err(err) = scenario.scale_desired_capacity(2).await { + info!(err, "there was a problem setting desired capacity"); + } + // Wait for a second instance to launch. + + // 8. DescribeAutoScalingInstances: show that two instances are launched. + show_scenario_description(&scenario, &"show that two instances are launched").await; + + // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. + match scenario.list_instance_arns().await { + Ok(instances) => { + if let Some(instance) = instances.iter().next() { + scenario.terminate_instance(instance, false).await; + } + // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. + } + Err(err) => { + info!(err, "error getting Autoscaling Group Instances"); + } + } + + // 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 + // 12. DeleteAutoScalingGroup (to delete the group you must stop all instances): + // 13. TerminateInstanceInAutoScalingGroup for each instance, specify ShouldDecrementDesiredCapacity=True. Wait for instances to stop. + // 14. Delete LaunchTemplate. + scenario.clean_scenario().await?; + + Ok(()) +} diff --git a/rust_dev_preview/examples/ec2/src/lib.rs b/rust_dev_preview/examples/ec2/src/lib.rs new file mode 100644 index 00000000000..298750f01ea --- /dev/null +++ b/rust_dev_preview/examples/ec2/src/lib.rs @@ -0,0 +1 @@ +pub mod autoscaling; From ce0ad962117bd8420d43989b34b30a7363993487 Mon Sep 17 00:00:00 2001 From: David Souther Date: Wed, 6 Sep 2023 14:44:29 -0400 Subject: [PATCH 02/10] Autoscaling scenario creates and destroys a group --- rust_dev_preview/examples/ec2/Cargo.toml | 1 + .../examples/ec2/src/autoscaling.rs | 189 +++++++++++++++--- .../examples/ec2/src/bin/autoscaling.rs | 67 ++++--- 3 files changed, 197 insertions(+), 60 deletions(-) diff --git a/rust_dev_preview/examples/ec2/Cargo.toml b/rust_dev_preview/examples/ec2/Cargo.toml index 4b080f51b8f..b3a0121ecd6 100644 --- a/rust_dev_preview/examples/ec2/Cargo.toml +++ b/rust_dev_preview/examples/ec2/Cargo.toml @@ -19,3 +19,4 @@ clap = { version = "~4.2", features = ["derive"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } anyhow = "1.0.75" tracing = "0.1.37" +valuable = "0.1.0" diff --git a/rust_dev_preview/examples/ec2/src/autoscaling.rs b/rust_dev_preview/examples/ec2/src/autoscaling.rs index 3a2d9756ec1..345dcad222a 100644 --- a/rust_dev_preview/examples/ec2/src/autoscaling.rs +++ b/rust_dev_preview/examples/ec2/src/autoscaling.rs @@ -1,30 +1,81 @@ -use aws_sdk_ec2::types::RequestLaunchTemplateData; -use aws_types::SdkConfig; +use std::fmt::Display; + +use anyhow::anyhow; +use aws_sdk_autoscaling::types::LaunchTemplateSpecification; +use aws_sdk_ec2::types::{AvailabilityZone, RequestLaunchTemplateData}; + +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"; pub struct AutoScalingScenario { - client: aws_sdk_ec2::Client, + ec2: aws_sdk_ec2::Client, + autoscaling: aws_sdk_autoscaling::Client, launch_template_arn: String, - auto_scaling_group_arn: String, + auto_scaling_group_name: String, } -pub struct AutoScalingScenarioDescription {} - -impl AutoScalingScenario { - pub fn launch_template_arn(&self) -> &str { - self.launch_template_arn.as_str() +impl Display for AutoScalingScenario { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "\t Launch Template: {}\n", + self.launch_template_arn + ))?; + f.write_fmt(format_args!( + "\tScaling Group Arn: {}\n", + self.auto_scaling_group_name + ))?; + + Ok(()) } +} + +pub struct AutoScalingScenarioDescription<'a> { + launch_template_id: &'a str, + auto_scaling_group_name: &'a str, +} - pub fn auto_scaling_group_arn(&self) -> &str { - self.auto_scaling_group_arn.as_str() +impl<'a> Display for AutoScalingScenarioDescription<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "\tLaunch Template: {}\n", + self.launch_template_id + ))?; + f.write_fmt(format_args!( + "\t Scaling Group: {}\n", + self.auto_scaling_group_name + ))?; + + Ok(()) } +} - pub async fn prepare_scenario(config: SdkConfig) -> Result { - let client = aws_sdk_ec2::Client::new(&config); +impl AutoScalingScenario { + pub async fn prepare_scenario( + sdk_config: &aws_types::sdk_config::SdkConfig, + ) -> Result { + let ec2 = aws_sdk_ec2::Client::new(sdk_config); + let autoscaling = aws_sdk_autoscaling::Client::new(sdk_config); + + // 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(anyhow!("Failed to find AZs: {err}")); + } + + let availability_zones: Vec = 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 = client + 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) @@ -37,33 +88,76 @@ impl AutoScalingScenario { let launch_template_arn = match create_launch_template.launch_template { Some(launch_template) => launch_template.launch_template_id.unwrap_or_default(), None => { - return Err(anyhow::anyhow!("Failed to load launch template"))-0p;hj87´ªº + return Err(anyhow::anyhow!("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' - let auto_scaling_group_arn = String::new(); + + let create_autoscaling_group = autoscaling + .create_auto_scaling_group() + .auto_scaling_group_name(AUTOSCALING_GROUP_NAME) + .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; + + if let Err(err) = create_autoscaling_group { + let delete_launch_template = ec2 + .delete_launch_template() + .launch_template_id(launch_template_arn.clone()) + .send() + .await; + if delete_launch_template.is_ok() { + return Err(anyhow!("Failed to create autoscaling group: {err:?}")); + } else { + return Err(anyhow!("Failed to create autoscaling group: {err:?}\nFailed to clean up launch template: {delete_launch_template:?}")); + } + } + + let auto_scaling_group_name = String::from(AUTOSCALING_GROUP_NAME); // 4. EnableMetricsCollection: enable all metrics or a subset. // If a subset, this list makes the most sense: 'GroupMinSize', 'GroupMaxSize', 'GroupDesiredCapacity', 'GroupInServiceInstances', 'GroupTotalInstances'. Ok(AutoScalingScenario { - client, - auto_scaling_group_arn, + ec2, + autoscaling, + auto_scaling_group_name, launch_template_arn, }) } pub async fn clean_scenario(self) -> Result<(), anyhow::Error> { - self.scale_group().await?; + let scale_group = self.scale_group().await; // 14. Delete LaunchTemplate. - todo!() + let delete_launch_template = self + .ec2 + .delete_launch_template() + .launch_template_id(self.launch_template_arn.clone()) + .send() + .await; + + if scale_group.is_ok() && delete_launch_template.is_ok() { + Ok(()) + } else { + Err(anyhow!("There was an error cleaning the scenario\nScale Group: {scale_group:?}\nDelete Launch Template: {delete_launch_template:?}")) + } } - pub async fn describe_scenario(&self) -> Result { + pub async fn describe_scenario<'a>( + &'a self, + ) -> Result, anyhow::Error> { // 3. DescribeAutoScalingInstances: show that one instance has launched. // 8. DescribeAutoScalingInstances: show that two instances are launched. // 6. DescribeAutoScalingGroups: show the current state of the group. @@ -72,22 +166,25 @@ impl AutoScalingScenario { // CW.ListMetrics with Namespace='AWS/AutoScaling' and Dimensions=[{'Name': 'AutoScalingGroupName', 'Value': }] // CW.GetMetricStatistics with Statistics='Sum'. Start and End times must be in UTC! - todo!() + Ok(AutoScalingScenarioDescription { + auto_scaling_group_name: &self.auto_scaling_group_name, + launch_template_id: &self.launch_template_arn, + }) } pub async fn list_instance_arns(&self) -> Result, anyhow::Error> { - todo!(); + Ok(vec![]) } pub async fn scale_max_size(&self, size: usize) -> Result<(), anyhow::Error> { // 5. UpdateAutoScalingGroup: update max size to 3. - todo!(); + Ok(()) } pub async fn scale_desired_capacity(&self, capacity: usize) -> Result<(), anyhow::Error> { // 7. SetDesiredCapacity: set desired capacity to 2. // Wait for a second instance to launch. - todo!() + Ok(()) } pub async fn scale_group(&self) -> Result<(), anyhow::Error> { @@ -97,16 +194,44 @@ impl AutoScalingScenario { // UpdateAutoScalingGroup with MinSize=0 // TerminateInstanceInAutoScalingGroup for each instance, specify ShouldDecrementDesiredCapacity=True. Wait for instances to stop. // Now you can delete the group. - todo!(); + + self.autoscaling + .delete_auto_scaling_group() + .auto_scaling_group_name(self.auto_scaling_group_name.clone()) + .send() + .await?; + + Ok(()) } - pub async fn terminate_instance( - &self, - instance_arn: String, - reduce_capacity: bool, - ) -> Result<(), anyhow::Error> { + pub async fn terminate_instance(&self, instance_arn: &str) -> Result<(), anyhow::Error> { + Ok(()) + } + + pub async fn terminate_instance_and_wait(&self) -> Result<(), anyhow::Error> { // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. - todo!() + let instances = self.list_instance_arns().await; + match instances { + Ok(instances) => { + let instance = instances.first(); + if let Some(instance) = instance { + let termination = self.terminate_instance(instance).await; + if let Err(err) = termination { + Err(anyhow!( + "There was a problem terminating an instance\n{err:?}" + )) + } else { + // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. + Ok(()) + } + } else { + Err(anyhow!("There was no instance to terminate")) + } + } + Err(err) => Err(anyhow!( + "Error getting Autoscaling Group Instances\n{err:?}" + )), + } } } diff --git a/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs b/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs index d9b1a84b152..d4d80d7847a 100644 --- a/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs +++ b/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs @@ -1,12 +1,12 @@ -use aws_config::meta::region::RegionProviderChain; -use aws_types::region::Region; +use anyhow::anyhow; use ec2_code_examples::autoscaling::AutoScalingScenario; use tracing::info; async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) { - match scenario.describe_scenario().await { - Ok(description) => info!(description, "DescribeAutoScalingInstances: {event}"), - Err(err) => info!(err, "Error in DescribeAutoScalingInstances: {event}"), + let auto_scaling_scenario_description = scenario.describe_scenario().await; + match auto_scaling_scenario_description { + Ok(description) => info!("DescribeAutoScalingInstances: {event}\n{description}"), + Err(err) => info!("Error in DescribeAutoScalingInstances: {event}\n{err:?}"), } } @@ -14,53 +14,62 @@ async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) async fn main() -> Result<(), anyhow::Error> { tracing_subscriber::fmt::init(); - let region_provider = RegionProviderChain::first_try(region.map(Region::new)) - .or_default_provider() - .or_else(Region::new("us-west-2")); - let shared_config = aws_config::from_env().region(region_provider).load().await; + let shared_config = aws_config::from_env().load().await; // 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 = AutoScalingScenario::prepare_scenario(shared_config).await?; + let scenario = match AutoScalingScenario::prepare_scenario(&shared_config).await { + Ok(scenario) => scenario, + Err(err) => return Err(anyhow!("Failed to initialize scenario: {err:?}")), + }; + + info!("Prepared autoscaling scenario:\n{scenario}"); // 3. DescribeAutoScalingInstances: show that one instance has launched. - show_scenario_description(&scenario, &"show that one instance has launched").await; + show_scenario_description( + &scenario, + "show that the group was created and one instance has launched", + ) + .await; // 5. UpdateAutoScalingGroup: update max size to 3. - if let Err(err) = scenario.scale_max_size(3).await { - info!(err, "there was a problem scaling max size"); + let scale_max_size = scenario.scale_max_size(3).await; + if let Err(err) = scale_max_size { + info!("There was a problem scaling max size\n{err:?}"); } // 6. DescribeAutoScalingGroups: the current state of the group - show_scenario_description(&scenario, &"show the current state of the group").await; + show_scenario_description( + &scenario, + "show the current state of the group after setting max size", + ) + .await; // 7. SetDesiredCapacity: set desired capacity to 2. - if let Err(err) = scenario.scale_desired_capacity(2).await { - info!(err, "there was a problem setting desired capacity"); + let scale_desired_capacity = scenario.scale_desired_capacity(2).await; + if let Err(err) = scale_desired_capacity { + info!("There was a problem setting desired capacity\n{err:?}"); } // Wait for a second instance to launch. // 8. DescribeAutoScalingInstances: show that two instances are launched. - show_scenario_description(&scenario, &"show that two instances are launched").await; + show_scenario_description( + &scenario, + "show that two instances are launched after setting desired capacity", + ) + .await; // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. - match scenario.list_instance_arns().await { - Ok(instances) => { - if let Some(instance) = instances.iter().next() { - scenario.terminate_instance(instance, false).await; - } - // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. - } - Err(err) => { - info!(err, "error getting Autoscaling Group Instances"); - } + let terminate_and_wait = scenario.terminate_instance_and_wait().await; + if let Err(err) = terminate_and_wait { + info!("There was a problem replacing an instance\n{err:?}"); } // 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", + "list the scaling activities that have occurred for the group so far", ) .await; @@ -70,5 +79,7 @@ async fn main() -> Result<(), anyhow::Error> { // 14. Delete LaunchTemplate. scenario.clean_scenario().await?; + info!("The scenario has been cleaned up!"); + Ok(()) } From 3b1eb5f3477b1fefb613a7bd2b81724994c4e0b5 Mon Sep 17 00:00:00 2001 From: David Souther Date: Wed, 6 Sep 2023 16:48:01 -0400 Subject: [PATCH 03/10] Scale and remove group --- rust_dev_preview/examples/ec2/Cargo.toml | 1 + .../examples/ec2/src/autoscaling.rs | 193 +++++++++++++++--- .../examples/ec2/src/bin/autoscaling.rs | 23 ++- 3 files changed, 179 insertions(+), 38 deletions(-) diff --git a/rust_dev_preview/examples/ec2/Cargo.toml b/rust_dev_preview/examples/ec2/Cargo.toml index b3a0121ecd6..91660e46a73 100644 --- a/rust_dev_preview/examples/ec2/Cargo.toml +++ b/rust_dev_preview/examples/ec2/Cargo.toml @@ -20,3 +20,4 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } anyhow = "1.0.75" tracing = "0.1.37" valuable = "0.1.0" +tokio-stream = "0.1.14" diff --git a/rust_dev_preview/examples/ec2/src/autoscaling.rs b/rust_dev_preview/examples/ec2/src/autoscaling.rs index 345dcad222a..f04044702c7 100644 --- a/rust_dev_preview/examples/ec2/src/autoscaling.rs +++ b/rust_dev_preview/examples/ec2/src/autoscaling.rs @@ -1,8 +1,11 @@ use std::fmt::Display; use anyhow::anyhow; -use aws_sdk_autoscaling::types::LaunchTemplateSpecification; -use aws_sdk_ec2::types::{AvailabilityZone, RequestLaunchTemplateData}; +use aws_sdk_autoscaling::types::{ + AutoScalingGroup, AutoScalingInstanceDetails, LaunchTemplateSpecification, +}; +use aws_sdk_ec2::types::RequestLaunchTemplateData; +use tokio_stream::StreamExt; 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"; @@ -29,21 +32,26 @@ impl Display for AutoScalingScenario { } } -pub struct AutoScalingScenarioDescription<'a> { - launch_template_id: &'a str, - auto_scaling_group_name: &'a str, +pub struct AutoScalingScenarioDescription { + instances: Result, anyhow::Error>, } -impl<'a> Display for AutoScalingScenarioDescription<'a> { +impl Display for AutoScalingScenarioDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!( - "\tLaunch Template: {}\n", - self.launch_template_id - ))?; - f.write_fmt(format_args!( - "\t Scaling Group: {}\n", - self.auto_scaling_group_name - ))?; + f.write_str("\t Instances:\n")?; + match &self.instances { + Ok(instances) => { + for instance in instances { + f.write_fmt(format_args!( + "\t\t- {}\n", + instance.instance_id().unwrap_or_default() + ))?; + } + } + Err(err) => { + f.write_fmt(format_args!("\t\t{err:?}\n"))?; + } + } Ok(()) } @@ -138,7 +146,12 @@ impl AutoScalingScenario { } pub async fn clean_scenario(self) -> Result<(), anyhow::Error> { - let scale_group = self.scale_group().await; + let scale_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 @@ -155,35 +168,136 @@ impl AutoScalingScenario { } } - pub async fn describe_scenario<'a>( - &'a self, - ) -> Result, anyhow::Error> { + pub async fn describe_scenario(&self) -> Result { // 3. DescribeAutoScalingInstances: show that one instance has launched. // 8. DescribeAutoScalingInstances: show that two instances are launched. + + let instances = self + .autoscaling + .describe_auto_scaling_instances() + .into_paginator() + .items() + .send() + .filter(|i| { + i.as_ref().is_ok_and(|i| { + i.auto_scaling_group_name == Some(String::from(AUTOSCALING_GROUP_NAME)) + }) + }) + .collect::, _>>() + .await + .map_err(|err| anyhow!("Could not find autoscaling instances: {err:?}")); + // 6. DescribeAutoScalingGroups: show the current state of the group. // 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! - Ok(AutoScalingScenarioDescription { - auto_scaling_group_name: &self.auto_scaling_group_name, - launch_template_id: &self.launch_template_arn, - }) + Ok(AutoScalingScenarioDescription { instances }) + } + + async fn get_group(&self) -> Result { + 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(anyhow!( + "Failed to get status of autoscaling group {}: {err}", + self.auto_scaling_group_name.clone() + )); + } + + let auto_scaling_groups = describe_auto_scaling_groups + .unwrap() + .auto_scaling_groups + .unwrap_or_default(); + let auto_scaling_group = auto_scaling_groups.first(); + + if auto_scaling_group.is_none() { + return Err(anyhow!( + "Could not find autoscaling group {}", + self.auto_scaling_group_name.clone() + )); + } + + Ok(auto_scaling_group.unwrap().clone()) + } + + pub async fn wait_for_stable(&self, size: usize) -> Result<(), anyhow::Error> { + let mut group = self.get_group().await?; + + while !{ + let group = &group; + group + .instances + .as_ref() + .map(|i| i.len()) + .unwrap_or_default() + == size + } { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + group = self.get_group().await?; + } + + Ok(()) } pub async fn list_instance_arns(&self) -> Result, anyhow::Error> { Ok(vec![]) } - pub async fn scale_max_size(&self, size: usize) -> Result<(), anyhow::Error> { + pub async fn scale_min_size(&self, size: i32) -> Result<(), anyhow::Error> { + 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(anyhow!( + "Failed to update group to min size ({size}): {err:?}" + )); + } + Ok(()) + } + + pub async fn scale_max_size(&self, size: i32) -> Result<(), anyhow::Error> { // 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(anyhow!( + "Failed to update group to max size ({size}): {err:?}" + )); + } Ok(()) } - pub async fn scale_desired_capacity(&self, capacity: usize) -> Result<(), anyhow::Error> { + pub async fn scale_desired_capacity(&self, capacity: i32) -> Result<(), anyhow::Error> { // 7. SetDesiredCapacity: set desired capacity to 2. // Wait for a second instance to launch. + let update_group = self + .autoscaling + .update_auto_scaling_group() + .auto_scaling_group_name(self.auto_scaling_group_name.clone()) + .desired_capacity(capacity) + .send() + .await; + if let Err(err) = update_group { + return Err(anyhow!( + "Failed to update group to desired capacity ({capacity}): {err:?}" + )); + } Ok(()) } @@ -192,19 +306,25 @@ impl AutoScalingScenario { // 12. DeleteAutoScalingGroup (to delete the group you must stop all instances): // UpdateAutoScalingGroup with MinSize=0 - // TerminateInstanceInAutoScalingGroup for each instance, specify ShouldDecrementDesiredCapacity=True. Wait for instances to stop. - // Now you can delete the group. - - self.autoscaling - .delete_auto_scaling_group() + 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?; + .await; + if let Err(err) = update_group { + return Err(anyhow!("Failed to update group for scaling down: {err:?}")); + } - Ok(()) - } + let stable = self.wait_for_stable(0).await; + if let Err(err) = stable { + return Err(anyhow!( + "Error while waiting for group to be stable on scale down: {err:?}" + )); + } - pub async fn terminate_instance(&self, instance_arn: &str) -> Result<(), anyhow::Error> { Ok(()) } @@ -216,7 +336,12 @@ impl AutoScalingScenario { Ok(instances) => { let instance = instances.first(); if let Some(instance) = instance { - let termination = self.terminate_instance(instance).await; + let termination = self + .ec2 + .terminate_instances() + .instance_ids(instance) + .send() + .await; if let Err(err) = termination { Err(anyhow!( "There was a problem terminating an instance\n{err:?}" diff --git a/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs b/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs index d4d80d7847a..b1a4ab6bbcc 100644 --- a/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs +++ b/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs @@ -1,6 +1,6 @@ use anyhow::anyhow; use ec2_code_examples::autoscaling::AutoScalingScenario; -use tracing::info; +use tracing::{info, warn}; async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) { let auto_scaling_scenario_description = scenario.describe_scenario().await; @@ -26,6 +26,11 @@ async fn main() -> Result<(), anyhow::Error> { info!("Prepared autoscaling scenario:\n{scenario}"); + let stable = scenario.wait_for_stable(1).await; + if let Err(err) = stable { + warn!("Error while waiting for group to be stable: {err:?}"); + } + // 3. DescribeAutoScalingInstances: show that one instance has launched. show_scenario_description( &scenario, @@ -49,9 +54,14 @@ async fn main() -> Result<(), anyhow::Error> { // 7. SetDesiredCapacity: set desired capacity to 2. let scale_desired_capacity = scenario.scale_desired_capacity(2).await; if let Err(err) = scale_desired_capacity { - info!("There was a problem setting desired capacity\n{err:?}"); + warn!("There was a problem setting desired capacity\n{err:?}"); } + // Wait for a second instance to launch. + let stable = scenario.wait_for_stable(2).await; + if let Err(err) = stable { + warn!("Error while waiting for group to be stable: {err:?}"); + } // 8. DescribeAutoScalingInstances: show that two instances are launched. show_scenario_description( @@ -63,7 +73,7 @@ async fn main() -> Result<(), anyhow::Error> { // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. let terminate_and_wait = scenario.terminate_instance_and_wait().await; if let Err(err) = terminate_and_wait { - info!("There was a problem replacing an instance\n{err:?}"); + warn!("There was a problem replacing an instance\n{err:?}"); } // 10. DescribeScalingActivities: list the scaling activities that have occurred for the group so far. @@ -74,8 +84,13 @@ async fn main() -> Result<(), anyhow::Error> { .await; // 11. DisableMetricsCollection + let scale_group = scenario.scale_group().await; + if let Err(err) = scale_group { + warn!("Error scaling 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. TerminateInstanceInAutoScalingGroup for each instance, specify ShouldDecrementDesiredCapacity=True. Wait for instances to stop. // 14. Delete LaunchTemplate. scenario.clean_scenario().await?; From f3e987b2dc024a261fea6793dce2f6d78b48bbf1 Mon Sep 17 00:00:00 2001 From: David Souther Date: Thu, 7 Sep 2023 15:44:49 -0400 Subject: [PATCH 04/10] Moved to autoscaling and finished debuggign --- .../examples/autoscaling/Cargo.toml | 5 + .../src/bin/scenario.rs} | 104 +++++++++++++++--- .../examples/autoscaling/src/lib.rs | 1 + .../src/scenario.rs} | 68 ++++-------- rust_dev_preview/examples/ec2/Cargo.toml | 4 - rust_dev_preview/examples/ec2/src/lib.rs | 1 - 6 files changed, 118 insertions(+), 65 deletions(-) rename rust_dev_preview/examples/{ec2/src/bin/autoscaling.rs => autoscaling/src/bin/scenario.rs} (52%) create mode 100644 rust_dev_preview/examples/autoscaling/src/lib.rs rename rust_dev_preview/examples/{ec2/src/autoscaling.rs => autoscaling/src/scenario.rs} (85%) delete mode 100644 rust_dev_preview/examples/ec2/src/lib.rs diff --git a/rust_dev_preview/examples/autoscaling/Cargo.toml b/rust_dev_preview/examples/autoscaling/Cargo.toml index 55a0b99b24b..0b811ae6a34 100644 --- a/rust_dev_preview/examples/autoscaling/Cargo.toml +++ b/rust_dev_preview/examples/autoscaling/Cargo.toml @@ -9,6 +9,11 @@ edition = "2021" [dependencies] aws-config = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } aws-sdk-autoscaling = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } +aws-sdk-ec2 = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } +aws-types = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } tokio = { version = "1.20.1", features = ["full"] } clap = { version = "~4.2", features = ["derive"] } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } +anyhow = "1.0.75" +tracing = "0.1.37" +tokio-stream = "0.1.14" diff --git a/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs b/rust_dev_preview/examples/autoscaling/src/bin/scenario.rs similarity index 52% rename from rust_dev_preview/examples/ec2/src/bin/autoscaling.rs rename to rust_dev_preview/examples/autoscaling/src/bin/scenario.rs index b1a4ab6bbcc..8a488dc2fc6 100644 --- a/rust_dev_preview/examples/ec2/src/bin/autoscaling.rs +++ b/rust_dev_preview/examples/autoscaling/src/bin/scenario.rs @@ -1,5 +1,7 @@ +use std::collections::BTreeSet; + use anyhow::anyhow; -use ec2_code_examples::autoscaling::AutoScalingScenario; +use autoscaling_code_examples::scenario::AutoScalingScenario; use tracing::{info, warn}; async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) { @@ -10,12 +12,29 @@ async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) } } +#[derive(Default, Debug)] +struct Warnings(Vec); + +impl Warnings { + pub fn push(&mut self, warning: &str, error: anyhow::Error) { + let formatted = format!("{warning}: {error:?}"); + warn!("{formatted}"); + self.0.push(formatted); + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + #[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. @@ -28,7 +47,10 @@ async fn main() -> Result<(), anyhow::Error> { let stable = scenario.wait_for_stable(1).await; if let Err(err) = stable { - warn!("Error while waiting for group to be stable: {err:?}"); + warnings.push( + "There was a problem while waiting for group to be stable", + err, + ); } // 3. DescribeAutoScalingInstances: show that one instance has launched. @@ -41,7 +63,7 @@ async fn main() -> Result<(), anyhow::Error> { // 5. UpdateAutoScalingGroup: update max size to 3. let scale_max_size = scenario.scale_max_size(3).await; if let Err(err) = scale_max_size { - info!("There was a problem scaling max size\n{err:?}"); + warnings.push("There was a problem scaling max size", err); } // 6. DescribeAutoScalingGroups: the current state of the group @@ -54,13 +76,16 @@ async fn main() -> Result<(), anyhow::Error> { // 7. SetDesiredCapacity: set desired capacity to 2. let scale_desired_capacity = scenario.scale_desired_capacity(2).await; if let Err(err) = scale_desired_capacity { - warn!("There was a problem setting desired capacity\n{err:?}"); + 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 { - warn!("Error while waiting for group to be stable: {err:?}"); + warnings.push( + "There was a problem while waiting for group to be stable", + err, + ); } // 8. DescribeAutoScalingInstances: show that two instances are launched. @@ -70,10 +95,54 @@ async fn main() -> Result<(), anyhow::Error> { ) .await; + let ids_before = scenario + .list_instances() + .await + .map(|v| { + v.iter() + .map(|i| i.instance_id.clone().unwrap_or_default()) + .collect::>() + }) + .unwrap_or_default(); + // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. - let terminate_and_wait = scenario.terminate_instance_and_wait().await; - if let Err(err) = terminate_and_wait { - warn!("There was a problem replacing an instance\n{err:?}"); + 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.iter() + .map(|i| i.instance_id.clone().unwrap_or_default()) + .collect::>() + }) + .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", + anyhow!("{difference}"), + ); } // 10. DescribeScalingActivities: list the scaling activities that have occurred for the group so far. @@ -86,15 +155,24 @@ async fn main() -> Result<(), anyhow::Error> { // 11. DisableMetricsCollection let scale_group = scenario.scale_group().await; if let Err(err) = scale_group { - warn!("Error scaling group to 0: {err:?}"); + 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): // 14. Delete LaunchTemplate. - scenario.clean_scenario().await?; - - info!("The scenario has been cleaned up!"); + let clean_scenario = scenario.clean_scenario().await; + if let Err(err) = clean_scenario { + warnings.push("There was a problem cleaning the scenario", err); + } else { + info!("The scenario has been cleaned up!"); + } - Ok(()) + if warnings.is_empty() { + Ok(()) + } else { + Err(anyhow!( + "There were warnings during scenario execution:\n{warnings:?}" + )) + } } diff --git a/rust_dev_preview/examples/autoscaling/src/lib.rs b/rust_dev_preview/examples/autoscaling/src/lib.rs new file mode 100644 index 00000000000..b632e6235b3 --- /dev/null +++ b/rust_dev_preview/examples/autoscaling/src/lib.rs @@ -0,0 +1 @@ +pub mod scenario; diff --git a/rust_dev_preview/examples/ec2/src/autoscaling.rs b/rust_dev_preview/examples/autoscaling/src/scenario.rs similarity index 85% rename from rust_dev_preview/examples/ec2/src/autoscaling.rs rename to rust_dev_preview/examples/autoscaling/src/scenario.rs index f04044702c7..ce6b7af1fbc 100644 --- a/rust_dev_preview/examples/ec2/src/autoscaling.rs +++ b/rust_dev_preview/examples/autoscaling/src/scenario.rs @@ -1,11 +1,9 @@ use std::fmt::Display; use anyhow::anyhow; -use aws_sdk_autoscaling::types::{ - AutoScalingGroup, AutoScalingInstanceDetails, LaunchTemplateSpecification, -}; +use aws_sdk_autoscaling::types::{AutoScalingGroup, Instance, LaunchTemplateSpecification}; use aws_sdk_ec2::types::RequestLaunchTemplateData; -use tokio_stream::StreamExt; +use tracing::info; 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"; @@ -20,11 +18,11 @@ pub struct AutoScalingScenario { impl Display for AutoScalingScenario { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( - "\t Launch Template: {}\n", + "\tLaunch Template ID: {}\n", self.launch_template_arn ))?; f.write_fmt(format_args!( - "\tScaling Group Arn: {}\n", + "\tScaling Group Name: {}\n", self.auto_scaling_group_name ))?; @@ -33,24 +31,17 @@ impl Display for AutoScalingScenario { } pub struct AutoScalingScenarioDescription { - instances: Result, anyhow::Error>, + instances: Vec, } impl Display for AutoScalingScenarioDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("\t Instances:\n")?; - match &self.instances { - Ok(instances) => { - for instance in instances { - f.write_fmt(format_args!( - "\t\t- {}\n", - instance.instance_id().unwrap_or_default() - ))?; - } - } - Err(err) => { - f.write_fmt(format_args!("\t\t{err:?}\n"))?; - } + for instance in &self.instances { + f.write_fmt(format_args!( + "\t\t- {}\n", + instance.instance_id().unwrap_or_default() + ))?; } Ok(()) @@ -146,7 +137,7 @@ impl AutoScalingScenario { } pub async fn clean_scenario(self) -> Result<(), anyhow::Error> { - let scale_group = self + let delete_group = self .autoscaling .delete_auto_scaling_group() .auto_scaling_group_name(self.auto_scaling_group_name.clone()) @@ -161,10 +152,10 @@ impl AutoScalingScenario { .send() .await; - if scale_group.is_ok() && delete_launch_template.is_ok() { + if delete_group.is_ok() && delete_launch_template.is_ok() { Ok(()) } else { - Err(anyhow!("There was an error cleaning the scenario\nScale Group: {scale_group:?}\nDelete Launch Template: {delete_launch_template:?}")) + Err(anyhow!("There was an error cleaning the scenario\nScale Group: {delete_group:?}\nDelete Launch Template: {delete_launch_template:?}")) } } @@ -172,20 +163,7 @@ impl AutoScalingScenario { // 3. DescribeAutoScalingInstances: show that one instance has launched. // 8. DescribeAutoScalingInstances: show that two instances are launched. - let instances = self - .autoscaling - .describe_auto_scaling_instances() - .into_paginator() - .items() - .send() - .filter(|i| { - i.as_ref().is_ok_and(|i| { - i.auto_scaling_group_name == Some(String::from(AUTOSCALING_GROUP_NAME)) - }) - }) - .collect::, _>>() - .await - .map_err(|err| anyhow!("Could not find autoscaling instances: {err:?}")); + let instances = self.list_instances().await?; // 6. DescribeAutoScalingGroups: show the current state of the group. // 10. DescribeScalingActivities: list the scaling activities that have occurred for the group so far. @@ -246,8 +224,8 @@ impl AutoScalingScenario { Ok(()) } - pub async fn list_instance_arns(&self) -> Result, anyhow::Error> { - Ok(vec![]) + pub async fn list_instances(&self) -> Result, anyhow::Error> { + Ok(self.get_group().await?.instances.unwrap_or_default()) } pub async fn scale_min_size(&self, size: i32) -> Result<(), anyhow::Error> { @@ -328,26 +306,22 @@ impl AutoScalingScenario { Ok(()) } - pub async fn terminate_instance_and_wait(&self) -> Result<(), anyhow::Error> { + pub async fn terminate_some_instance(&self) -> Result<(), anyhow::Error> { // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. - let instances = self.list_instance_arns().await; + let instances = self.list_instances().await; match instances { Ok(instances) => { let instance = instances.first(); if let Some(instance) = instance { - let termination = self - .ec2 - .terminate_instances() - .instance_ids(instance) - .send() - .await; + let id = instance.instance_id().unwrap(); + info!("Terminating {id}"); + let termination = self.ec2.terminate_instances().instance_ids(id).send().await; if let Err(err) = termination { Err(anyhow!( "There was a problem terminating an instance\n{err:?}" )) } else { - // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. Ok(()) } } else { diff --git a/rust_dev_preview/examples/ec2/Cargo.toml b/rust_dev_preview/examples/ec2/Cargo.toml index 91660e46a73..400eb63a003 100644 --- a/rust_dev_preview/examples/ec2/Cargo.toml +++ b/rust_dev_preview/examples/ec2/Cargo.toml @@ -17,7 +17,3 @@ aws-types = { git = "https://github.com/awslabs/aws-sdk-rust", branch = "next" } tokio = { version = "1.20.1", features = ["full"] } clap = { version = "~4.2", features = ["derive"] } tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } -anyhow = "1.0.75" -tracing = "0.1.37" -valuable = "0.1.0" -tokio-stream = "0.1.14" diff --git a/rust_dev_preview/examples/ec2/src/lib.rs b/rust_dev_preview/examples/ec2/src/lib.rs deleted file mode 100644 index 298750f01ea..00000000000 --- a/rust_dev_preview/examples/ec2/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod autoscaling; From 82ac06785845b851ac3dc0f79bf36b03b00385f2 Mon Sep 17 00:00:00 2001 From: David Souther Date: Fri, 8 Sep 2023 12:34:46 -0400 Subject: [PATCH 05/10] Finished auto scaling example formatting, added metadata, and moved to correct service name folder. Fix snippet tag Fix enable vs disable metrics snippet names --- .doc_gen/metadata/auto-scaling_metadata.yaml | 63 +++- rust_dev_preview/examples/Cargo.toml | 2 +- .../{autoscaling => auto-scaling}/Cargo.toml | 2 +- .../examples/auto-scaling/README.md | 118 +++++++ .../src/bin/create-autoscaling-group.rs | 0 .../src/bin/delete-autoscaling-group.rs | 0 .../src/bin/list-autoscaling-groups.rs | 0 .../src/bin/scenario.rs | 14 +- .../src/bin/update-autoscaling-group.rs | 0 .../{autoscaling => auto-scaling}/src/lib.rs | 0 .../src/scenario.rs | 293 ++++++++++++------ .../examples/autoscaling/README.md | 78 ----- 12 files changed, 387 insertions(+), 183 deletions(-) rename rust_dev_preview/examples/{autoscaling => auto-scaling}/Cargo.toml (88%) create mode 100644 rust_dev_preview/examples/auto-scaling/README.md rename rust_dev_preview/examples/{autoscaling => auto-scaling}/src/bin/create-autoscaling-group.rs (100%) rename rust_dev_preview/examples/{autoscaling => auto-scaling}/src/bin/delete-autoscaling-group.rs (100%) rename rust_dev_preview/examples/{autoscaling => auto-scaling}/src/bin/list-autoscaling-groups.rs (100%) rename rust_dev_preview/examples/{autoscaling => auto-scaling}/src/bin/scenario.rs (93%) rename rust_dev_preview/examples/{autoscaling => auto-scaling}/src/bin/update-autoscaling-group.rs (100%) rename rust_dev_preview/examples/{autoscaling => auto-scaling}/src/lib.rs (100%) rename rust_dev_preview/examples/{autoscaling => auto-scaling}/src/scenario.rs (50%) delete mode 100644 rust_dev_preview/examples/autoscaling/README.md diff --git a/.doc_gen/metadata/auto-scaling_metadata.yaml b/.doc_gen/metadata/auto-scaling_metadata.yaml index c2317ac717c..dc253f068e0 100644 --- a/.doc_gen/metadata/auto-scaling_metadata.yaml +++ b/.doc_gen/metadata/auto-scaling_metadata.yaml @@ -162,7 +162,8 @@ auto-scaling_DeleteAutoScalingGroup: - sdk_version: 3 github: python/example_code/auto-scaling excerpts: - - description: Update the minimum size of an Auto Scaling group to zero, terminate all + - description: + Update the minimum size of an Auto Scaling group to zero, terminate all instances in the group, and delete the group. snippet_tags: - python.cross_service.resilient_service.AutoScaler.decl @@ -382,6 +383,15 @@ auto-scaling_TerminateInstanceInAutoScalingGroup: - cpp.example_code.autoscaling.autoscaling_client - cpp.example_code.autoscaling.terminate_instance_autoscaling_group1 - cpp.example_code.autoscaling.terminate_instance_autoscaling_group2 + Rust: + versions: + - sdk_version: 1 + github: rust_dev_preview/examples/autoscaling + excerpts: + - description: + snippet_tags: + - rust.autoscaling.scenario.terminate_some_instance + - rust.autoscaling.scenario.get_group services: auto-scaling: {TerminateInstanceInAutoScalingGroup} auto-scaling_SetDesiredCapacity: @@ -444,6 +454,14 @@ auto-scaling_SetDesiredCapacity: - cpp.example_code.autoscaling.autoscaling_client - cpp.example_code.autoscaling.set_desired_capacity1 - cpp.example_code.autoscaling.set_desired_capacity2 + Rust: + versions: + - sdk_version: 1 + github: rust_dev_preview/examples/autoscaling + excerpts: + - description: + snippet_tags: + - rust.autoscaling.scenario.scale_desired_capacity services: auto-scaling: {SetDesiredCapacity} auto-scaling_DescribeAutoScalingInstances: @@ -506,6 +524,14 @@ auto-scaling_DescribeAutoScalingInstances: - cpp.example_code.autoscaling.autoscaling_client - cpp.example_code.autoscaling.describe_autoscaling_instances1 - cpp.example_code.autoscaling.describe_autoscaling_instances2 + Rust: + versions: + - sdk_version: 1 + github: rust_dev_preview/examples/autoscaling + excerpts: + - description: + snippet_tags: + - rust.autoscaling.scenario.list_instances services: auto-scaling: {DescribeAutoScalingInstances} auto-scaling_DescribeScalingActivities: @@ -568,6 +594,14 @@ auto-scaling_DescribeScalingActivities: - cpp.example_code.autoscaling.autoscaling_client - cpp.example_code.autoscaling.describe_scaling_activities1 - cpp.example_code.autoscaling.describe_scaling_activities2 + Rust: + versions: + - sdk_version: 1 + github: rust_dev_preview/examples/autoscaling + excerpts: + - description: + snippet_tags: + - rust.autoscaling.scenario.describe_scenario services: auto-scaling: {DescribeScalingActivities} auto-scaling_EnableMetricsCollection: @@ -630,6 +664,14 @@ auto-scaling_EnableMetricsCollection: - cpp.example_code.autoscaling.autoscaling_client - cpp.example_code.autoscaling.enable_metrics_collection1 - cpp.example_code.autoscaling.enable_metrics_collection2 + Rust: + versions: + - sdk_version: 1 + github: rust_dev_preview/examples/autoscaling + excerpts: + - description: + snippet_tags: + - rust.autoscaling.scenario.enable_metrics_collection services: auto-scaling: {EnableMetricsCollection} auto-scaling_DisableMetricsCollection: @@ -692,6 +734,14 @@ auto-scaling_DisableMetricsCollection: - cpp.example_code.autoscaling.autoscaling_client - cpp.example_code.autoscaling.disable_metrics_collection1 - cpp.example_code.autoscaling.disable_metrics_collection2 + Rust: + versions: + - sdk_version: 1 + github: rust_dev_preview/examples/autoscaling + excerpts: + - description: + snippet_tags: + - rust.autoscaling.scenario.disable_metrics_collection services: auto-scaling: {DisableMetricsCollection} auto-scaling_AttachLoadBalancerTargetGroups: @@ -792,6 +842,17 @@ auto-scaling_Scenario_GroupsAndInstances: - description: snippet_tags: - cpp.example_code.autoscaling.groups_and_instances_scenario + Rust: + versions: + - sdk_version: 1 + github: rust_dev_preview/examples/autoscaling + excerpts: + - description: + snippet_files: + - rust_dev_preview/examples/auto-scaling/Cargo.toml + - rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs + - rust_dev_preview/examples/auto-scaling/src/lib.rs + - rust_dev_preview/examples/auto-scaling/src/scenario.rs services: auto-scaling: { diff --git a/rust_dev_preview/examples/Cargo.toml b/rust_dev_preview/examples/Cargo.toml index 113cde5bb4f..ee214b256cd 100644 --- a/rust_dev_preview/examples/Cargo.toml +++ b/rust_dev_preview/examples/Cargo.toml @@ -6,7 +6,7 @@ members = [ "apigateway", "apigatewaymanagement", "applicationautoscaling", - "autoscaling", + "auto-scaling", "autoscalingplans", "batch", "cloudformation", diff --git a/rust_dev_preview/examples/autoscaling/Cargo.toml b/rust_dev_preview/examples/auto-scaling/Cargo.toml similarity index 88% rename from rust_dev_preview/examples/autoscaling/Cargo.toml rename to rust_dev_preview/examples/auto-scaling/Cargo.toml index 0b811ae6a34..63dbc3315a8 100644 --- a/rust_dev_preview/examples/autoscaling/Cargo.toml +++ b/rust_dev_preview/examples/auto-scaling/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "autoscaling-code-examples" version = "0.1.0" -authors = ["Doug Schwartz "] +authors = ["Doug Schwartz ", "David Souther "] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust_dev_preview/examples/auto-scaling/README.md b/rust_dev_preview/examples/auto-scaling/README.md new file mode 100644 index 00000000000..d9aaa92298e --- /dev/null +++ b/rust_dev_preview/examples/auto-scaling/README.md @@ -0,0 +1,118 @@ + +# Auto Scaling code examples for the SDK for Rust + +## Overview + +Shows how to use the AWS SDK for Rust to work with Amazon EC2 Auto Scaling. + + + + +*Auto Scaling automatically scales EC2 instances, either with scaling policies or with scheduled scaling.* + +## ⚠ Important + +* Running this code might result in charges to your AWS account. +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `rust_dev_preview` folder. + + + + + + +### Get started + +* [Hello Auto Scaling](None) (`DescribeAutoScalingGroups`) + +### Single actions + +Code excerpts that show you how to call individual service functions. + +* [Create a group](None) (`CreateAutoScalingGroup`) +* [Delete a group](None) (`DeleteAutoScalingGroup`) +* [Disable metrics collection for a group](None) (`DisableMetricsCollection`) +* [Enable metrics collection for a group](None) (`EnableMetricsCollection`) +* [Get information about groups](None) (`DescribeAutoScalingGroups`) +* [Get information about instances](None) (`DescribeAutoScalingInstances`) +* [Get information about scaling activities](None) (`DescribeScalingActivities`) +* [Set the desired capacity of a group](None) (`SetDesiredCapacity`) +* [Terminate an instance in a group](None) (`TerminateInstanceInAutoScalingGroup`) +* [Update a group](None) (`UpdateAutoScalingGroup`) + +### Scenarios + +Code examples that show you how to accomplish a specific task by calling multiple +functions within the same service. + +* [Manage groups and instances](rust_dev_preview/examples/auto-scaling/Cargo.toml) + +## Run the examples + +### Instructions + + + + + +#### Hello Auto Scaling + +This example shows you how to get started using Auto Scaling. + + + +#### Manage groups and instances + +This example shows you how to do the following: + +* Create an Amazon EC2 Auto Scaling group with a launch template and Availability Zones, and get information about running instances. +* Enable Amazon CloudWatch metrics collection. +* Update the group's desired capacity and wait for an instance to start. +* Terminate an instance in the group. +* List scaling activities that occur in response to user requests and capacity changes. +* Get statistics for CloudWatch metrics, then clean up resources. + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../../README.md#Tests) +in the `rust_dev_preview` folder. + + + + + + +## Additional resources + +* [Auto Scaling User Guide](https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html) +* [Auto Scaling API Reference](https://docs.aws.amazon.com/autoscaling/ec2/APIReference/Welcome.html) +* [SDK for Rust Auto Scaling reference](https://docs.rs/aws-sdk-auto-scaling/latest/aws_sdk_auto-scaling/) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/rust_dev_preview/examples/autoscaling/src/bin/create-autoscaling-group.rs b/rust_dev_preview/examples/auto-scaling/src/bin/create-autoscaling-group.rs similarity index 100% rename from rust_dev_preview/examples/autoscaling/src/bin/create-autoscaling-group.rs rename to rust_dev_preview/examples/auto-scaling/src/bin/create-autoscaling-group.rs diff --git a/rust_dev_preview/examples/autoscaling/src/bin/delete-autoscaling-group.rs b/rust_dev_preview/examples/auto-scaling/src/bin/delete-autoscaling-group.rs similarity index 100% rename from rust_dev_preview/examples/autoscaling/src/bin/delete-autoscaling-group.rs rename to rust_dev_preview/examples/auto-scaling/src/bin/delete-autoscaling-group.rs diff --git a/rust_dev_preview/examples/autoscaling/src/bin/list-autoscaling-groups.rs b/rust_dev_preview/examples/auto-scaling/src/bin/list-autoscaling-groups.rs similarity index 100% rename from rust_dev_preview/examples/autoscaling/src/bin/list-autoscaling-groups.rs rename to rust_dev_preview/examples/auto-scaling/src/bin/list-autoscaling-groups.rs diff --git a/rust_dev_preview/examples/autoscaling/src/bin/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs similarity index 93% rename from rust_dev_preview/examples/autoscaling/src/bin/scenario.rs rename to rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs index 8a488dc2fc6..cccf6179f8c 100644 --- a/rust_dev_preview/examples/autoscaling/src/bin/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs @@ -98,11 +98,7 @@ async fn main() -> Result<(), anyhow::Error> { let ids_before = scenario .list_instances() .await - .map(|v| { - v.iter() - .map(|i| i.instance_id.clone().unwrap_or_default()) - .collect::>() - }) + .map(|v| v.into_iter().collect::>()) .unwrap_or_default(); // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. @@ -130,11 +126,7 @@ async fn main() -> Result<(), anyhow::Error> { let ids_after = scenario .list_instances() .await - .map(|v| { - v.iter() - .map(|i| i.instance_id.clone().unwrap_or_default()) - .collect::>() - }) + .map(|v| v.into_iter().collect::>()) .unwrap_or_default(); let difference = ids_after.intersection(&ids_before).count(); @@ -153,7 +145,7 @@ async fn main() -> Result<(), anyhow::Error> { .await; // 11. DisableMetricsCollection - let scale_group = scenario.scale_group().await; + 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); } diff --git a/rust_dev_preview/examples/autoscaling/src/bin/update-autoscaling-group.rs b/rust_dev_preview/examples/auto-scaling/src/bin/update-autoscaling-group.rs similarity index 100% rename from rust_dev_preview/examples/autoscaling/src/bin/update-autoscaling-group.rs rename to rust_dev_preview/examples/auto-scaling/src/bin/update-autoscaling-group.rs diff --git a/rust_dev_preview/examples/autoscaling/src/lib.rs b/rust_dev_preview/examples/auto-scaling/src/lib.rs similarity index 100% rename from rust_dev_preview/examples/autoscaling/src/lib.rs rename to rust_dev_preview/examples/auto-scaling/src/lib.rs diff --git a/rust_dev_preview/examples/autoscaling/src/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/scenario.rs similarity index 50% rename from rust_dev_preview/examples/autoscaling/src/scenario.rs rename to rust_dev_preview/examples/auto-scaling/src/scenario.rs index ce6b7af1fbc..27529823b53 100644 --- a/rust_dev_preview/examples/autoscaling/src/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/scenario.rs @@ -1,12 +1,19 @@ -use std::fmt::Display; +use std::{ + fmt::Display, + time::{Duration, SystemTime}, +}; use anyhow::anyhow; -use aws_sdk_autoscaling::types::{AutoScalingGroup, Instance, LaunchTemplateSpecification}; +use aws_sdk_autoscaling::{ + error::DisplayErrorContext, + types::{Activity, AutoScalingGroup, LaunchTemplateSpecification}, +}; use aws_sdk_ec2::types::RequestLaunchTemplateData; -use tracing::info; +use tokio_stream::StreamExt; 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); pub struct AutoScalingScenario { ec2: aws_sdk_ec2::Client, @@ -31,23 +38,58 @@ impl Display for AutoScalingScenario { } pub struct AutoScalingScenarioDescription { - instances: Vec, + instances: Result, anyhow::Error>, + activities: Result, anyhow::Error>, } impl Display for AutoScalingScenarioDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("\t Instances:\n")?; - for instance in &self.instances { - f.write_fmt(format_args!( - "\t\t- {}\n", - instance.instance_id().unwrap_or_default() - ))?; + f.write_str("\t Instances:\n")?; + match &self.instances { + Ok(instances) => { + for instance in instances { + f.write_fmt(format_args!("\t\t- {instance}\n"))?; + } + } + Err(e) => f.write_fmt(format_args!("\t\t! {e}"))?, + } + + f.write_str("\t Activities:\n")?; + match &self.activities { + Ok(activities) => { + for activity in activities { + f.write_fmt(format_args!( + "\t\t- {} {}\n", + activity.cause().unwrap_or("(Cause unknown)"), + activity.status_message().unwrap_or_default() + ))?; + } + } + Err(e) => f.write_fmt(format_args!("\t\t! {e}"))?, } Ok(()) } } +macro_rules! sdk_err { + ($context:expr) => { + Err(anyhow!("{}", $context)) + }; + ($context:expr, $err:ident) => { + Err(anyhow!("{}: {}", $context, DisplayErrorContext(&$err))) + }; + ($c1:expr, $e1:ident, $c2:expr, $e2:ident) => { + Err(anyhow!( + "{}: {}\n{}: {}", + $c1, + DisplayErrorContext(&$e1), + $c2, + DisplayErrorContext(&$e2) + )) + }; +} + impl AutoScalingScenario { pub async fn prepare_scenario( sdk_config: &aws_types::sdk_config::SdkConfig, @@ -55,10 +97,12 @@ impl AutoScalingScenario { 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(anyhow!("Failed to find AZs: {err}")); + return sdk_err!("Failed to find AZs", err); } let availability_zones: Vec = availablity_zones @@ -87,17 +131,16 @@ impl AutoScalingScenario { let launch_template_arn = match create_launch_template.launch_template { Some(launch_template) => launch_template.launch_template_id.unwrap_or_default(), None => { - return Err(anyhow::anyhow!("Failed to load launch template")); + return sdk_err!("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' - let create_autoscaling_group = autoscaling .create_auto_scaling_group() - .auto_scaling_group_name(AUTOSCALING_GROUP_NAME) + .auto_scaling_group_name(auto_scaling_group_name.as_str()) .launch_template( LaunchTemplateSpecification::builder() .launch_template_id(launch_template_arn.clone()) @@ -116,24 +159,47 @@ impl AutoScalingScenario { .launch_template_id(launch_template_arn.clone()) .send() .await; - if delete_launch_template.is_ok() { - return Err(anyhow!("Failed to create autoscaling group: {err:?}")); - } else { - return Err(anyhow!("Failed to create autoscaling group: {err:?}\nFailed to clean up launch template: {delete_launch_template:?}")); - } + return match delete_launch_template { + Ok(_) => sdk_err!("Failed to create autoscaling group", err), + Err(dlt_err) => sdk_err!( + "Failed to create autoscaling group", + err, + "Failed to clean up launch template", + dlt_err + ), + }; } - let auto_scaling_group_name = String::from(AUTOSCALING_GROUP_NAME); - - // 4. EnableMetricsCollection: enable all metrics or a subset. - // If a subset, this list makes the most sense: 'GroupMinSize', 'GroupMaxSize', 'GroupDesiredCapacity', 'GroupInServiceInstances', 'GroupTotalInstances'. - - Ok(AutoScalingScenario { + let scenario = AutoScalingScenario { ec2, - autoscaling, - auto_scaling_group_name, + 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, - }) + }; + + // snippet-start:[rust.autoscaling.scenario.enable_metrics_collection] + 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; + // snippet-end:[rust.autoscaling.scenario.enable_metrics_collection] + + match enable_metrics_collection { + Ok(_) => Ok(scenario), + Err(err) => { + scenario.clean_scenario().await?; + sdk_err!("Failed to enable metrics collections for group", err) + } + } } pub async fn clean_scenario(self) -> Result<(), anyhow::Error> { @@ -152,28 +218,54 @@ impl AutoScalingScenario { .send() .await; - if delete_group.is_ok() && delete_launch_template.is_ok() { - Ok(()) - } else { - Err(anyhow!("There was an error cleaning the scenario\nScale Group: {delete_group:?}\nDelete Launch Template: {delete_launch_template:?}")) + match (delete_group, delete_launch_template) { + (Ok(_), Ok(_)) => Ok(()), + (Ok(_), Err(e)) => sdk_err!("There was an error cleaning the launch template", e), + (Err(e), Ok(_)) => sdk_err!("There was an error cleaning the scale group", e), + (Err(e1), Err(e2)) => sdk_err!( + "There was an error cleaning the scenario\nScale Group", + e1, + "Delete Launch Template", + e2 + ), } } + // snippet-start:[rust.autoscaling.scenario.describe_scenario] pub async fn describe_scenario(&self) -> Result { - // 3. DescribeAutoScalingInstances: show that one instance has launched. - // 8. DescribeAutoScalingInstances: show that two instances are launched. - - let instances = self.list_instances().await?; + let instances = self + .list_instances() + .await + .map_err(|e| anyhow!("There was an error listing instances: {e}",)); - // 6. DescribeAutoScalingGroups: show the current state of the group. // 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! - - Ok(AutoScalingScenarioDescription { instances }) + let activities = self + .autoscaling + .describe_scaling_activities() + .auto_scaling_group_name(self.auto_scaling_group_name.clone()) + .into_paginator() + .items() + .send() + .collect::, _>>() + .await + .map_err(|e| { + anyhow!( + "There was an error retrieving scaling activities: {}", + DisplayErrorContext(&e) + ) + }); + + Ok(AutoScalingScenarioDescription { + instances, + activities, + }) } + // snippet-end:[rust.autoscaling.scenario.describe_scenario] + // snippet-start:[rust.autoscaling.scenario.get_group] async fn get_group(&self) -> Result { let describe_auto_scaling_groups = self .autoscaling @@ -183,10 +275,13 @@ impl AutoScalingScenario { .await; if let Err(err) = describe_auto_scaling_groups { - return Err(anyhow!( - "Failed to get status of autoscaling group {}: {err}", - self.auto_scaling_group_name.clone() - )); + return sdk_err!( + format!( + "Failed to get status of autoscaling group {}", + self.auto_scaling_group_name.clone() + ), + err + ); } let auto_scaling_groups = describe_auto_scaling_groups @@ -204,19 +299,26 @@ impl AutoScalingScenario { Ok(auto_scaling_group.unwrap().clone()) } + // snippet-end:[rust.autoscaling.scenario.get_group] pub async fn wait_for_stable(&self, size: usize) -> Result<(), anyhow::Error> { let mut group = self.get_group().await?; + let start = SystemTime::now(); - while !{ - let group = &group; - group - .instances - .as_ref() - .map(|i| i.len()) - .unwrap_or_default() - == size - } { + while !group + .instances + .as_ref() + .map(|i| i.len()) + .unwrap_or_default() + == size + { + if SystemTime::now() + .duration_since(start) + .unwrap_or(Duration::MAX) + > MAX_WAIT + { + return Err(anyhow!("Exceeded maximum wait duration for stable group")); + } tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; group = self.get_group().await?; } @@ -224,9 +326,15 @@ impl AutoScalingScenario { Ok(()) } - pub async fn list_instances(&self) -> Result, anyhow::Error> { - Ok(self.get_group().await?.instances.unwrap_or_default()) + // snippet-start:[rust.autoscaling.scenario.list_instances] + pub async fn list_instances(&self) -> Result, anyhow::Error> { + // The direct way to list instances is by using DescribeAutoScalingGroup's instances property. However, this returns a Vec, as opposed to a Vec. + // 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. + Ok(vec![]) } + // snippet-end:[rust.autoscaling.scenario.list_instances] pub async fn scale_min_size(&self, size: i32) -> Result<(), anyhow::Error> { let update_group = self @@ -237,9 +345,7 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = update_group { - return Err(anyhow!( - "Failed to update group to min size ({size}): {err:?}" - )); + return sdk_err!(format!("Failed to update group to min size ({size}))"), err); } Ok(()) } @@ -254,33 +360,42 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = update_group { - return Err(anyhow!( - "Failed to update group to max size ({size}): {err:?}" - )); + return sdk_err!(format!("Failed to update group to max size ({size})"), err); } Ok(()) } + // snippet-start:[rust.autoscaling.scenario.scale_desired_capacity] pub async fn scale_desired_capacity(&self, capacity: i32) -> Result<(), anyhow::Error> { // 7. SetDesiredCapacity: set desired capacity to 2. // Wait for a second instance to launch. let update_group = self .autoscaling - .update_auto_scaling_group() + .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(anyhow!( - "Failed to update group to desired capacity ({capacity}): {err:?}" - )); + return sdk_err!( + format!("Failed to update group to desired capacity ({capacity}))"), + err + ); } Ok(()) } + // snippet-end:[rust.autoscaling.scenario.scale_desired_capacity] - pub async fn scale_group(&self) -> Result<(), anyhow::Error> { - // 11. DisableMetricsCollection + pub async fn scale_group_to_zero(&self) -> Result<(), anyhow::Error> { + // snippet-start:[rust.autoscaling.scenario.disable_metrics_collection] + // 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; + // snippet-end:[rust.autoscaling.scenario.disable_metrics_collection] // 12. DeleteAutoScalingGroup (to delete the group you must stop all instances): // UpdateAutoScalingGroup with MinSize=0 @@ -293,44 +408,40 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = update_group { - return Err(anyhow!("Failed to update group for scaling down: {err:?}")); + return sdk_err!("Failed to update group for scaling down", err); } let stable = self.wait_for_stable(0).await; if let Err(err) = stable { return Err(anyhow!( - "Error while waiting for group to be stable on scale down: {err:?}" + "Error while waiting for group to be stable on scale down: {err}" )); } Ok(()) } + // snippet-start:[rust.autoscaling.scenario.terminate_some_instance] pub async fn terminate_some_instance(&self) -> Result<(), anyhow::Error> { - // 9. TerminateInstanceInAutoScalingGroup: terminate one of the instances in the group. - // Wait for the old instance to stop and a new instance to launch to bring the capacity back to 2. - let instances = self.list_instances().await; - match instances { - Ok(instances) => { - let instance = instances.first(); - if let Some(instance) = instance { - let id = instance.instance_id().unwrap(); - info!("Terminating {id}"); - let termination = self.ec2.terminate_instances().instance_ids(id).send().await; - if let Err(err) = termination { - Err(anyhow!( - "There was a problem terminating an instance\n{err:?}" - )) - } else { - Ok(()) - } - } else { - Err(anyhow!("There was no instance to terminate")) - } + // Retrieve a list of instances in the auto scaling group. + let instances = self.get_group().await?.instances.unwrap_or_default(); + // Or use other logic to find an instance to terminate. + let instance = instances.first(); + if let Some(instance) = instance { + let termination = self + .ec2 + .terminate_instances() + .instance_ids(instance.instance_id().unwrap_or_default()) + .send() + .await; + if let Err(err) = termination { + sdk_err!("There was a problem terminating an instance", err) + } else { + Ok(()) } - Err(err) => Err(anyhow!( - "Error getting Autoscaling Group Instances\n{err:?}" - )), + } else { + Err(anyhow!("There was no instance to terminate")) } } + // snippet-end:[rust.autoscaling.scenario.terminate_some_instance] } diff --git a/rust_dev_preview/examples/autoscaling/README.md b/rust_dev_preview/examples/autoscaling/README.md deleted file mode 100644 index 8922a743cdc..00000000000 --- a/rust_dev_preview/examples/autoscaling/README.md +++ /dev/null @@ -1,78 +0,0 @@ - -# Auto Scaling code examples for the SDK for Rust - -## Overview - -Shows how to use the AWS SDK for Rust to work with Amazon EC2 Auto Scaling. - - - - -*Auto Scaling automatically scales EC2 instances, either with scaling policies or with scheduled scaling.* - -## ⚠ Important - -* Running this code might result in charges to your AWS account. -* Running the tests might result in charges to your AWS account. -* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). -* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). - - - - -## Code examples - -### Prerequisites - -For prerequisites, see the [README](../README.md#Prerequisites) in the `rust_dev_preview` folder. - - - - - -### Single actions - -Code excerpts that show you how to call individual service functions. - -* [Create a group](src/bin/create-autoscaling-group.rs#L32) (`CreateAutoScalingGroup`) -* [Delete a group](src/bin/delete-autoscaling-group.rs#L32) (`DeleteAutoScalingGroup`) -* [Get information about groups](src/bin/list-autoscaling-groups.rs#L24) (`DescribeAutoScalingGroups`) -* [Update a group](src/bin/update-autoscaling-group.rs#L32) (`UpdateAutoScalingGroup`) - -## Run the examples - -### Instructions - - - - - - - -### Tests - -⚠ Running tests might result in charges to your AWS account. - - -To find instructions for running these tests, see the [README](../README.md#Tests) -in the `rust_dev_preview` folder. - - - - - - -## Additional resources - -* [Auto Scaling User Guide](https://docs.aws.amazon.com/autoscaling/ec2/userguide/what-is-amazon-ec2-auto-scaling.html) -* [Auto Scaling API Reference](https://docs.aws.amazon.com/autoscaling/ec2/APIReference/Welcome.html) -* [SDK for Rust Auto Scaling reference](https://docs.rs/aws-sdk-auto-scaling/latest/aws_sdk_auto-scaling/) - - - - ---- - -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 \ No newline at end of file From 5bee4dfc94b1f8c2fdfdc336878d485ec53498ea Mon Sep 17 00:00:00 2001 From: David Souther Date: Wed, 20 Sep 2023 16:18:40 -0400 Subject: [PATCH 06/10] Reworked error handling to be explicit, and fixed waiters --- rust_dev_preview/.gitignore | 2 +- .../examples/auto-scaling/src/bin/scenario.rs | 44 +- .../examples/auto-scaling/src/scenario.rs | 430 ++++++++++++++---- 3 files changed, 360 insertions(+), 116 deletions(-) diff --git a/rust_dev_preview/.gitignore b/rust_dev_preview/.gitignore index 4470988469a..2c96eb1b651 100644 --- a/rust_dev_preview/.gitignore +++ b/rust_dev_preview/.gitignore @@ -1,2 +1,2 @@ target/ -Cargo.lock \ No newline at end of file +Cargo.lock diff --git a/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs index cccf6179f8c..a351f23ad39 100644 --- a/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs @@ -1,23 +1,20 @@ -use std::collections::BTreeSet; +use std::{collections::BTreeSet, fmt::Display}; use anyhow::anyhow; -use autoscaling_code_examples::scenario::AutoScalingScenario; +use autoscaling_code_examples::scenario::{AutoScalingScenario, ScenarioError}; use tracing::{info, warn}; async fn show_scenario_description(scenario: &AutoScalingScenario, event: &str) { - let auto_scaling_scenario_description = scenario.describe_scenario().await; - match auto_scaling_scenario_description { - Ok(description) => info!("DescribeAutoScalingInstances: {event}\n{description}"), - Err(err) => info!("Error in DescribeAutoScalingInstances: {event}\n{err:?}"), - } + let description = scenario.describe_scenario().await; + info!("DescribeAutoScalingInstances: {event}\n{description}"); } #[derive(Default, Debug)] struct Warnings(Vec); impl Warnings { - pub fn push(&mut self, warning: &str, error: anyhow::Error) { - let formatted = format!("{warning}: {error:?}"); + pub fn push(&mut self, warning: &str, error: ScenarioError) { + let formatted = format!("{warning}: {error}"); warn!("{formatted}"); self.0.push(formatted); } @@ -27,6 +24,16 @@ impl Warnings { } } +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(); @@ -40,7 +47,14 @@ async fn main() -> Result<(), anyhow::Error> { // 4. EnableMetricsCollection: enable all metrics or a subset. let scenario = match AutoScalingScenario::prepare_scenario(&shared_config).await { Ok(scenario) => scenario, - Err(err) => return Err(anyhow!("Failed to initialize scenario: {err:?}")), + Err(errs) => { + let err_str = errs + .into_iter() + .map(|e| e.to_string()) + .collect::>() + .join(", "); + return Err(anyhow!("Failed to initialize scenario: {err_str}")); + } }; info!("Prepared autoscaling scenario:\n{scenario}"); @@ -133,7 +147,7 @@ async fn main() -> Result<(), anyhow::Error> { if !(difference == 1 && ids_before.len() == 2 && ids_after.len() == 2) { warnings.push( "Before and after set not different", - anyhow!("{difference}"), + ScenarioError::with(format!("{difference}")), ); } @@ -154,8 +168,10 @@ async fn main() -> Result<(), anyhow::Error> { // 12. DeleteAutoScalingGroup (to delete the group you must stop all instances): // 14. Delete LaunchTemplate. let clean_scenario = scenario.clean_scenario().await; - if let Err(err) = clean_scenario { - warnings.push("There was a problem cleaning the scenario", err); + 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!"); } @@ -164,7 +180,7 @@ async fn main() -> Result<(), anyhow::Error> { Ok(()) } else { Err(anyhow!( - "There were warnings during scenario execution:\n{warnings:?}" + "There were warnings during scenario execution:\n{warnings}" )) } } diff --git a/rust_dev_preview/examples/auto-scaling/src/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/scenario.rs index 27529823b53..76d394595db 100644 --- a/rust_dev_preview/examples/auto-scaling/src/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/scenario.rs @@ -1,19 +1,51 @@ use std::{ - fmt::Display, + error::Error, + fmt::{Debug, Display}, time::{Duration, SystemTime}, }; use anyhow::anyhow; use aws_sdk_autoscaling::{ - error::DisplayErrorContext, + error::{DisplayErrorContext, ProvideErrorMetadata}, types::{Activity, AutoScalingGroup, LaunchTemplateSpecification}, }; use aws_sdk_ec2::types::RequestLaunchTemplateData; use tokio_stream::StreamExt; +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); +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, @@ -38,62 +70,117 @@ impl Display for AutoScalingScenario { } pub struct AutoScalingScenarioDescription { + group: Result, ScenarioError>, instances: Result, anyhow::Error>, activities: Result, anyhow::Error>, } impl Display for AutoScalingScenarioDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("\t Instances:\n")?; + 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 { - f.write_fmt(format_args!("\t\t- {instance}\n"))?; + writeln!(f, "\t\t- {instance}")?; } } - Err(e) => f.write_fmt(format_args!("\t\t! {e}"))?, + Err(e) => writeln!(f, "\t\t! {e}")?, } - f.write_str("\t Activities:\n")?; + writeln!(f, "\t Activities:")?; match &self.activities { Ok(activities) => { for activity in activities { - f.write_fmt(format_args!( - "\t\t- {} {}\n", + writeln!( + f, + "\t\t- {} Progress: {}% Status: {:?} End: {:?}", activity.cause().unwrap_or("(Cause unknown)"), - activity.status_message().unwrap_or_default() - ))?; + activity.progress(), + activity.status_code(), + // activity.status_message().unwrap_or_default() + activity.end_time(), + )?; } } - Err(e) => f.write_fmt(format_args!("\t\t! {e}"))?, + Err(e) => writeln!(f, "\t\t! {e}")?, } Ok(()) } } -macro_rules! sdk_err { - ($context:expr) => { - Err(anyhow!("{}", $context)) - }; - ($context:expr, $err:ident) => { - Err(anyhow!("{}: {}", $context, DisplayErrorContext(&$err))) - }; - ($c1:expr, $e1:ident, $c2:expr, $e2:ident) => { - Err(anyhow!( - "{}: {}\n{}: {}", - $c1, - DisplayErrorContext(&$e1), - $c2, - DisplayErrorContext(&$e2) - )) - }; +#[derive(Debug)] +struct MetadataError { + message: Option, + code: Option, +} + +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, +} + +impl ScenarioError { + pub fn with(message: impl Into) -> Self { + ScenarioError { + message: message.into(), + context: None, + } + } + + pub fn new(message: impl Into, err: &dyn ProvideErrorMetadata) -> Self { + ScenarioError { + message: message.into(), + context: Some(MetadataError::from(err)), + } + } +} + +impl Error for ScenarioError {} +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: &aws_types::sdk_config::SdkConfig, - ) -> Result { + ) -> Result> { let ec2 = aws_sdk_ec2::Client::new(sdk_config); let autoscaling = aws_sdk_autoscaling::Client::new(sdk_config); @@ -102,7 +189,7 @@ impl AutoScalingScenario { // 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 sdk_err!("Failed to find AZs", err); + return Err(vec![ScenarioError::new("Failed to find AZs", &err)]); } let availability_zones: Vec = availablity_zones @@ -126,19 +213,26 @@ impl AutoScalingScenario { .build(), ) .send() - .await?; + .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 => { - return sdk_err!("Failed to load launch template"); + // 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' - let create_autoscaling_group = autoscaling + if let Err(err) = autoscaling .create_auto_scaling_group() .auto_scaling_group_name(auto_scaling_group_name.as_str()) .launch_template( @@ -151,23 +245,37 @@ impl AutoScalingScenario { .min_size(1) .set_availability_zones(Some(availability_zones)) .send() - .await; + .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) = create_autoscaling_group { - let delete_launch_template = ec2 + if let Err(err) = ec2 .delete_launch_template() .launch_template_id(launch_template_arn.clone()) .send() - .await; - return match delete_launch_template { - Ok(_) => sdk_err!("Failed to create autoscaling group", err), - Err(dlt_err) => sdk_err!( - "Failed to create autoscaling group", - err, + .await + { + errs.push(ScenarioError::new( "Failed to clean up launch template", - dlt_err - ), - }; + &err, + )); + } + return Err(errs); } let scenario = AutoScalingScenario { @@ -197,12 +305,16 @@ impl AutoScalingScenario { Ok(_) => Ok(scenario), Err(err) => { scenario.clean_scenario().await?; - sdk_err!("Failed to enable metrics collections for group", err) + Err(vec![ScenarioError::new( + "Failed to enable metrics collections for group", + &err, + )]) } } } - pub async fn clean_scenario(self) -> Result<(), anyhow::Error> { + pub async fn clean_scenario(self) -> Result<(), Vec> { + let _ = self.wait_for_no_scaling().await; let delete_group = self .autoscaling .delete_auto_scaling_group() @@ -218,21 +330,91 @@ impl AutoScalingScenario { .send() .await; - match (delete_group, delete_launch_template) { + let early_exit = match (delete_group, delete_launch_template) { (Ok(_), Ok(_)) => Ok(()), - (Ok(_), Err(e)) => sdk_err!("There was an error cleaning the launch template", e), - (Err(e), Ok(_)) => sdk_err!("There was an error cleaning the scale group", e), - (Err(e1), Err(e2)) => sdk_err!( - "There was an error cleaning the scenario\nScale Group", - e1, - "Delete Launch Template", - e2 - ), + (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::::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.unwrap_or_default().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", + )]) } } // snippet-start:[rust.autoscaling.scenario.describe_scenario] - pub async fn describe_scenario(&self) -> Result { + 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() + .unwrap_or_default() + .iter() + .map(|s| { + format!( + "{}: {}", + s.auto_scaling_group_name().unwrap_or_default().clone(), + s.status().unwrap_or_default().clone() + ) + }) + .collect::>() + }) + .map_err(|e| { + ScenarioError::new("Failed to describe auto scaling groups for scenario", &e) + }); + let instances = self .list_instances() .await @@ -258,15 +440,16 @@ impl AutoScalingScenario { ) }); - Ok(AutoScalingScenarioDescription { + AutoScalingScenarioDescription { + group, instances, activities, - }) + } } // snippet-end:[rust.autoscaling.scenario.describe_scenario] // snippet-start:[rust.autoscaling.scenario.get_group] - async fn get_group(&self) -> Result { + async fn get_group(&self) -> Result { let describe_auto_scaling_groups = self .autoscaling .describe_auto_scaling_groups() @@ -275,13 +458,14 @@ impl AutoScalingScenario { .await; if let Err(err) = describe_auto_scaling_groups { - return sdk_err!( + return Err(ScenarioError::new( format!( "Failed to get status of autoscaling group {}", self.auto_scaling_group_name.clone() - ), - err - ); + ) + .as_str(), + &err, + )); } let auto_scaling_groups = describe_auto_scaling_groups @@ -291,52 +475,80 @@ impl AutoScalingScenario { let auto_scaling_group = auto_scaling_groups.first(); if auto_scaling_group.is_none() { - return Err(anyhow!( + return Err(ScenarioError::with(format!( "Could not find autoscaling group {}", self.auto_scaling_group_name.clone() - )); + ))); } Ok(auto_scaling_group.unwrap().clone()) } // snippet-end:[rust.autoscaling.scenario.get_group] - pub async fn wait_for_stable(&self, size: usize) -> Result<(), anyhow::Error> { + 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.unwrap_or_default(); + trace!( + "Waiting for no scaling found {} activities", + activities.len() + ); + scaling = activities.iter().any(|a| a.progress < 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 start = SystemTime::now(); + let mut count = count_group_instances(&group); - while !group - .instances - .as_ref() - .map(|i| i.len()) - .unwrap_or_default() - == size - { - if SystemTime::now() - .duration_since(start) - .unwrap_or(Duration::MAX) - > MAX_WAIT - { - return Err(anyhow!("Exceeded maximum wait duration for stable group")); - } - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + 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(()) } // snippet-start:[rust.autoscaling.scenario.list_instances] - pub async fn list_instances(&self) -> Result, anyhow::Error> { + pub async fn list_instances(&self) -> Result, ScenarioError> { // The direct way to list instances is by using DescribeAutoScalingGroup's instances property. However, this returns a Vec, as opposed to a Vec. // 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. - Ok(vec![]) + Ok(self + .autoscaling + .describe_auto_scaling_instances() + .into_paginator() + .items() + .send() + .filter(|i| i.is_ok()) + .map(|i| i.unwrap()) + .filter(|i| i.auto_scaling_group_name() == Some(self.auto_scaling_group_name.as_str())) + .map(|i| i.instance_id.clone().unwrap_or_default()) + .filter(|id| !id.is_empty()) + .collect::>() + .await) } // snippet-end:[rust.autoscaling.scenario.list_instances] - pub async fn scale_min_size(&self, size: i32) -> Result<(), anyhow::Error> { + pub async fn scale_min_size(&self, size: i32) -> Result<(), ScenarioError> { let update_group = self .autoscaling .update_auto_scaling_group() @@ -345,12 +557,15 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = update_group { - return sdk_err!(format!("Failed to update group to min size ({size}))"), err); + 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<(), anyhow::Error> { + pub async fn scale_max_size(&self, size: i32) -> Result<(), ScenarioError> { // 5. UpdateAutoScalingGroup: update max size to 3. let update_group = self .autoscaling @@ -360,13 +575,16 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = update_group { - return sdk_err!(format!("Failed to update group to max size ({size})"), err); + return Err(ScenarioError::new( + format!("Failed to update group to max size ({size})").as_str(), + &err, + )); } Ok(()) } // snippet-start:[rust.autoscaling.scenario.scale_desired_capacity] - pub async fn scale_desired_capacity(&self, capacity: i32) -> Result<(), anyhow::Error> { + 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 @@ -377,16 +595,16 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = update_group { - return sdk_err!( - format!("Failed to update group to desired capacity ({capacity}))"), - err - ); + return Err(ScenarioError::new( + format!("Failed to update group to desired capacity ({capacity}))").as_str(), + &err, + )); } Ok(()) } // snippet-end:[rust.autoscaling.scenario.scale_desired_capacity] - pub async fn scale_group_to_zero(&self) -> Result<(), anyhow::Error> { + pub async fn scale_group_to_zero(&self) -> Result<(), ScenarioError> { // snippet-start:[rust.autoscaling.scenario.disable_metrics_collection] // If this fails it's fine, just means there are extra cloudwatch metrics events for the scale-down. let _ = self @@ -408,21 +626,24 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = update_group { - return sdk_err!("Failed to update group for scaling down", err); + 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(anyhow!( + return Err(ScenarioError::with(format!( "Error while waiting for group to be stable on scale down: {err}" - )); + ))); } Ok(()) } // snippet-start:[rust.autoscaling.scenario.terminate_some_instance] - pub async fn terminate_some_instance(&self) -> Result<(), anyhow::Error> { + pub async fn terminate_some_instance(&self) -> Result<(), ScenarioError> { // Retrieve a list of instances in the auto scaling group. let instances = self.get_group().await?.instances.unwrap_or_default(); // Or use other logic to find an instance to terminate. @@ -435,13 +656,20 @@ impl AutoScalingScenario { .send() .await; if let Err(err) = termination { - sdk_err!("There was a problem terminating an instance", err) + Err(ScenarioError::new( + "There was a problem terminating an instance", + &err, + )) } else { Ok(()) } } else { - Err(anyhow!("There was no instance to terminate")) + Err(ScenarioError::with("There was no instance to terminate")) } } // snippet-end:[rust.autoscaling.scenario.terminate_some_instance] } + +fn count_group_instances(group: &AutoScalingGroup) -> usize { + group.instances.as_ref().map(|i| i.len()).unwrap_or(0) +} From b2381aed3476b7ba36b96d2185ae50acbe663d5e Mon Sep 17 00:00:00 2001 From: David Souther Date: Wed, 20 Sep 2023 16:40:24 -0400 Subject: [PATCH 07/10] Writeme not finding line numbers Fixed github metadata field --- .doc_gen/metadata/auto-scaling_metadata.yaml | 38 +++++++++---------- .../examples/auto-scaling/README.md | 28 +++++++------- .../examples/auto-scaling/src/scenario.rs | 28 +++++++------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/.doc_gen/metadata/auto-scaling_metadata.yaml b/.doc_gen/metadata/auto-scaling_metadata.yaml index dc253f068e0..4836972a29f 100644 --- a/.doc_gen/metadata/auto-scaling_metadata.yaml +++ b/.doc_gen/metadata/auto-scaling_metadata.yaml @@ -33,7 +33,7 @@ auto-scaling_Hello: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: @@ -101,7 +101,7 @@ auto-scaling_CreateAutoScalingGroup: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: @@ -171,7 +171,7 @@ auto-scaling_DeleteAutoScalingGroup: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: @@ -238,7 +238,7 @@ auto-scaling_DescribeAutoScalingGroups: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: @@ -305,7 +305,7 @@ auto-scaling_UpdateAutoScalingGroup: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: @@ -386,12 +386,12 @@ auto-scaling_TerminateInstanceInAutoScalingGroup: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: - - rust.autoscaling.scenario.terminate_some_instance - - rust.autoscaling.scenario.get_group + - rust.auto-scaling.scenario.terminate_some_instance + - rust.auto-scaling.scenario.get_group services: auto-scaling: {TerminateInstanceInAutoScalingGroup} auto-scaling_SetDesiredCapacity: @@ -457,11 +457,11 @@ auto-scaling_SetDesiredCapacity: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: - - rust.autoscaling.scenario.scale_desired_capacity + - rust.auto-scaling.scenario.scale_desired_capacity services: auto-scaling: {SetDesiredCapacity} auto-scaling_DescribeAutoScalingInstances: @@ -527,11 +527,11 @@ auto-scaling_DescribeAutoScalingInstances: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: - - rust.autoscaling.scenario.list_instances + - rust.auto-scaling.scenario.list_instances services: auto-scaling: {DescribeAutoScalingInstances} auto-scaling_DescribeScalingActivities: @@ -597,11 +597,11 @@ auto-scaling_DescribeScalingActivities: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: - - rust.autoscaling.scenario.describe_scenario + - rust.auto-scaling.scenario.describe_scenario services: auto-scaling: {DescribeScalingActivities} auto-scaling_EnableMetricsCollection: @@ -667,11 +667,11 @@ auto-scaling_EnableMetricsCollection: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: - - rust.autoscaling.scenario.enable_metrics_collection + - rust.auto-scaling.scenario.enable_metrics_collection services: auto-scaling: {EnableMetricsCollection} auto-scaling_DisableMetricsCollection: @@ -737,11 +737,11 @@ auto-scaling_DisableMetricsCollection: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_tags: - - rust.autoscaling.scenario.disable_metrics_collection + - rust.auto-scaling.scenario.disable_metrics_collection services: auto-scaling: {DisableMetricsCollection} auto-scaling_AttachLoadBalancerTargetGroups: @@ -845,7 +845,7 @@ auto-scaling_Scenario_GroupsAndInstances: Rust: versions: - sdk_version: 1 - github: rust_dev_preview/examples/autoscaling + github: rust_dev_preview/examples/auto-scaling excerpts: - description: snippet_files: diff --git a/rust_dev_preview/examples/auto-scaling/README.md b/rust_dev_preview/examples/auto-scaling/README.md index d9aaa92298e..62c2cecd015 100644 --- a/rust_dev_preview/examples/auto-scaling/README.md +++ b/rust_dev_preview/examples/auto-scaling/README.md @@ -1,4 +1,4 @@ - + # Auto Scaling code examples for the SDK for Rust ## Overview @@ -12,7 +12,7 @@ Shows how to use the AWS SDK for Rust to work with Amazon EC2 Auto Scaling. ## ⚠ Important -* Running this code might result in charges to your AWS account. +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/?aws-products-pricing.sort-by=item.additionalFields.productNameLowercase&aws-products-pricing.sort-order=asc&awsf.Free%20Tier%20Type=*all&awsf.tech-category=*all) and [Free Tier](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all). * Running the tests might result in charges to your AWS account. * We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). * This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). @@ -33,29 +33,29 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `rust_ ### Get started -* [Hello Auto Scaling](None) (`DescribeAutoScalingGroups`) +* [Hello Auto Scaling](src/bin/list-autoscaling-groups.rs#L24) (`DescribeAutoScalingGroups`) ### Single actions Code excerpts that show you how to call individual service functions. -* [Create a group](None) (`CreateAutoScalingGroup`) -* [Delete a group](None) (`DeleteAutoScalingGroup`) -* [Disable metrics collection for a group](None) (`DisableMetricsCollection`) -* [Enable metrics collection for a group](None) (`EnableMetricsCollection`) -* [Get information about groups](None) (`DescribeAutoScalingGroups`) -* [Get information about instances](None) (`DescribeAutoScalingInstances`) -* [Get information about scaling activities](None) (`DescribeScalingActivities`) -* [Set the desired capacity of a group](None) (`SetDesiredCapacity`) -* [Terminate an instance in a group](None) (`TerminateInstanceInAutoScalingGroup`) -* [Update a group](None) (`UpdateAutoScalingGroup`) +* [Create a group](src/bin/create-autoscaling-group.rs#L32) (`CreateAutoScalingGroup`) +* [Delete a group](src/bin/delete-autoscaling-group.rs#L32) (`DeleteAutoScalingGroup`) +* [Disable metrics collection for a group](src/scenario.rs#L608) (`DisableMetricsCollection`) +* [Enable metrics collection for a group](src/scenario.rs#L288) (`EnableMetricsCollection`) +* [Get information about groups](src/bin/list-autoscaling-groups.rs#L24) (`DescribeAutoScalingGroups`) +* [Get information about instances](src/scenario.rs#L529) (`DescribeAutoScalingInstances`) +* [Get information about scaling activities](src/scenario.rs#L393) (`DescribeScalingActivities`) +* [Set the desired capacity of a group](src/scenario.rs#L586) (`SetDesiredCapacity`) +* [Terminate an instance in a group](src/scenario.rs#L645) (`TerminateInstanceInAutoScalingGroup`) +* [Update a group](src/bin/update-autoscaling-group.rs#L32) (`UpdateAutoScalingGroup`) ### Scenarios Code examples that show you how to accomplish a specific task by calling multiple functions within the same service. -* [Manage groups and instances](rust_dev_preview/examples/auto-scaling/Cargo.toml) +* [Manage groups and instances](README.md) ## Run the examples diff --git a/rust_dev_preview/examples/auto-scaling/src/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/scenario.rs index 76d394595db..5146dd96365 100644 --- a/rust_dev_preview/examples/auto-scaling/src/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/scenario.rs @@ -285,7 +285,7 @@ impl AutoScalingScenario { launch_template_arn, }; - // snippet-start:[rust.autoscaling.scenario.enable_metrics_collection] + // snippet-start:[rust.auto-scaling.scenario.enable_metrics_collection] let enable_metrics_collection = autoscaling .enable_metrics_collection() .auto_scaling_group_name(auto_scaling_group_name.as_str()) @@ -299,7 +299,7 @@ impl AutoScalingScenario { ])) .send() .await; - // snippet-end:[rust.autoscaling.scenario.enable_metrics_collection] + // snippet-end:[rust.auto-scaling.scenario.enable_metrics_collection] match enable_metrics_collection { Ok(_) => Ok(scenario), @@ -390,7 +390,7 @@ impl AutoScalingScenario { } } - // snippet-start:[rust.autoscaling.scenario.describe_scenario] + // snippet-start:[rust.auto-scaling.scenario.describe_scenario] pub async fn describe_scenario(&self) -> AutoScalingScenarioDescription { let group = self .autoscaling @@ -446,9 +446,9 @@ impl AutoScalingScenario { activities, } } - // snippet-end:[rust.autoscaling.scenario.describe_scenario] + // snippet-end:[rust.auto-scaling.scenario.describe_scenario] - // snippet-start:[rust.autoscaling.scenario.get_group] + // snippet-start:[rust.auto-scaling.scenario.get_group] async fn get_group(&self) -> Result { let describe_auto_scaling_groups = self .autoscaling @@ -483,7 +483,7 @@ impl AutoScalingScenario { Ok(auto_scaling_group.unwrap().clone()) } - // snippet-end:[rust.autoscaling.scenario.get_group] + // snippet-end:[rust.auto-scaling.scenario.get_group] pub async fn wait_for_no_scaling(&self) -> Result<(), ScenarioError> { let waiter = Waiter::new(); @@ -526,7 +526,7 @@ impl AutoScalingScenario { Ok(()) } - // snippet-start:[rust.autoscaling.scenario.list_instances] + // snippet-start:[rust.auto-scaling.scenario.list_instances] pub async fn list_instances(&self) -> Result, ScenarioError> { // The direct way to list instances is by using DescribeAutoScalingGroup's instances property. However, this returns a Vec, as opposed to a Vec. // Ok(self.get_group().await?.instances.unwrap_or_default().map(|i| i.instance_id.clone().unwrap_or_default()).filter(|id| !id.is_empty()).collect()) @@ -546,7 +546,7 @@ impl AutoScalingScenario { .collect::>() .await) } - // snippet-end:[rust.autoscaling.scenario.list_instances] + // snippet-end:[rust.auto-scaling.scenario.list_instances] pub async fn scale_min_size(&self, size: i32) -> Result<(), ScenarioError> { let update_group = self @@ -583,7 +583,7 @@ impl AutoScalingScenario { Ok(()) } - // snippet-start:[rust.autoscaling.scenario.scale_desired_capacity] + // snippet-start:[rust.auto-scaling.scenario.scale_desired_capacity] 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. @@ -602,10 +602,10 @@ impl AutoScalingScenario { } Ok(()) } - // snippet-end:[rust.autoscaling.scenario.scale_desired_capacity] + // snippet-end:[rust.auto-scaling.scenario.scale_desired_capacity] pub async fn scale_group_to_zero(&self) -> Result<(), ScenarioError> { - // snippet-start:[rust.autoscaling.scenario.disable_metrics_collection] + // snippet-start:[rust.auto-scaling.scenario.disable_metrics_collection] // If this fails it's fine, just means there are extra cloudwatch metrics events for the scale-down. let _ = self .autoscaling @@ -613,7 +613,7 @@ impl AutoScalingScenario { .auto_scaling_group_name(self.auto_scaling_group_name.clone()) .send() .await; - // snippet-end:[rust.autoscaling.scenario.disable_metrics_collection] + // snippet-end:[rust.auto-scaling.scenario.disable_metrics_collection] // 12. DeleteAutoScalingGroup (to delete the group you must stop all instances): // UpdateAutoScalingGroup with MinSize=0 @@ -642,7 +642,7 @@ impl AutoScalingScenario { Ok(()) } - // snippet-start:[rust.autoscaling.scenario.terminate_some_instance] + // snippet-start:[rust.auto-scaling.scenario.terminate_some_instance] pub async fn terminate_some_instance(&self) -> Result<(), ScenarioError> { // Retrieve a list of instances in the auto scaling group. let instances = self.get_group().await?.instances.unwrap_or_default(); @@ -667,7 +667,7 @@ impl AutoScalingScenario { Err(ScenarioError::with("There was no instance to terminate")) } } - // snippet-end:[rust.autoscaling.scenario.terminate_some_instance] + // snippet-end:[rust.auto-scaling.scenario.terminate_some_instance] } fn count_group_instances(group: &AutoScalingGroup) -> usize { From 481b76bc640f23032bd03451c577c2150fe6ab0c Mon Sep 17 00:00:00 2001 From: David Souther Date: Tue, 26 Sep 2023 15:49:12 -0400 Subject: [PATCH 08/10] Review updates --- .doc_gen/metadata/auto-scaling_metadata.yaml | 4 +--- rust_dev_preview/examples/auto-scaling/Cargo.toml | 2 +- rust_dev_preview/examples/auto-scaling/src/scenario.rs | 7 ++++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.doc_gen/metadata/auto-scaling_metadata.yaml b/.doc_gen/metadata/auto-scaling_metadata.yaml index 4836972a29f..621e9dd3be0 100644 --- a/.doc_gen/metadata/auto-scaling_metadata.yaml +++ b/.doc_gen/metadata/auto-scaling_metadata.yaml @@ -162,9 +162,7 @@ auto-scaling_DeleteAutoScalingGroup: - sdk_version: 3 github: python/example_code/auto-scaling excerpts: - - description: - Update the minimum size of an Auto Scaling group to zero, terminate all - instances in the group, and delete the group. + - description: Update the minimum size of an Auto Scaling group to zero, terminate all instances in the group, and delete the group. snippet_tags: - python.cross_service.resilient_service.AutoScaler.decl - python.cross_service.resilient_service.auto-scaling.DeleteAutoScalingGroup diff --git a/rust_dev_preview/examples/auto-scaling/Cargo.toml b/rust_dev_preview/examples/auto-scaling/Cargo.toml index 63dbc3315a8..60514e18223 100644 --- a/rust_dev_preview/examples/auto-scaling/Cargo.toml +++ b/rust_dev_preview/examples/auto-scaling/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "autoscaling-code-examples" version = "0.1.0" -authors = ["Doug Schwartz ", "David Souther "] +authors = ["Doug Schwartz ", "David Souther "] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust_dev_preview/examples/auto-scaling/src/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/scenario.rs index 5146dd96365..3d3676317d3 100644 --- a/rust_dev_preview/examples/auto-scaling/src/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/scenario.rs @@ -167,7 +167,12 @@ impl ScenarioError { } } -impl Error for ScenarioError {} +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 { From 9d6684a56a6edfc443374ed8bc1f7557abd1e2a9 Mon Sep 17 00:00:00 2001 From: David Souther Date: Tue, 3 Oct 2023 12:58:50 -0400 Subject: [PATCH 09/10] OnCall Review --- rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs | 7 ++++++- rust_dev_preview/examples/auto-scaling/src/scenario.rs | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs index a351f23ad39..a0ea12383e0 100644 --- a/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/bin/scenario.rs @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + use std::{collections::BTreeSet, fmt::Display}; use anyhow::anyhow; @@ -166,7 +171,7 @@ async fn main() -> Result<(), anyhow::Error> { show_scenario_description(&scenario, "Scenario scaled to 0").await; // 12. DeleteAutoScalingGroup (to delete the group you must stop all instances): - // 14. Delete LaunchTemplate. + // 13. Delete LaunchTemplate. let clean_scenario = scenario.clean_scenario().await; if let Err(errs) = clean_scenario { for err in errs { diff --git a/rust_dev_preview/examples/auto-scaling/src/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/scenario.rs index 3d3676317d3..f5479171a83 100644 --- a/rust_dev_preview/examples/auto-scaling/src/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/scenario.rs @@ -1,3 +1,8 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + use std::{ error::Error, fmt::{Debug, Display}, From 4a690cd28bd933c6d8d50500cc54f4835358747c Mon Sep 17 00:00:00 2001 From: David Souther Date: Tue, 3 Oct 2023 14:27:54 -0400 Subject: [PATCH 10/10] Update scenario for new paginators --- .../examples/auto-scaling/src/scenario.rs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/rust_dev_preview/examples/auto-scaling/src/scenario.rs b/rust_dev_preview/examples/auto-scaling/src/scenario.rs index f5479171a83..87b1135be3c 100644 --- a/rust_dev_preview/examples/auto-scaling/src/scenario.rs +++ b/rust_dev_preview/examples/auto-scaling/src/scenario.rs @@ -15,7 +15,6 @@ use aws_sdk_autoscaling::{ types::{Activity, AutoScalingGroup, LaunchTemplateSpecification}, }; use aws_sdk_ec2::types::RequestLaunchTemplateData; -use tokio_stream::StreamExt; use tracing::trace; const LAUNCH_TEMPLATE_NAME: &str = "SDK_Code_Examples_EC2_Autoscaling_template_from_Rust_SDK"; @@ -108,7 +107,7 @@ impl Display for AutoScalingScenarioDescription { writeln!( f, "\t\t- {} Progress: {}% Status: {:?} End: {:?}", - activity.cause().unwrap_or("(Cause unknown)"), + activity.cause(), activity.progress(), activity.status_code(), // activity.status_message().unwrap_or_default() @@ -374,7 +373,7 @@ impl AutoScalingScenario { .send() .await; match describe_group { - Ok(group) => match group.auto_scaling_groups.unwrap_or_default().first() { + Ok(group) => match group.auto_scaling_groups().first() { Some(group) => { if group.status() != Some("Delete in progress") { errors.push(ScenarioError::with(format!( @@ -410,13 +409,12 @@ impl AutoScalingScenario { .await .map(|s| { s.auto_scaling_groups() - .unwrap_or_default() .iter() .map(|s| { format!( "{}: {}", - s.auto_scaling_group_name().unwrap_or_default().clone(), - s.status().unwrap_or_default().clone() + s.auto_scaling_group_name(), + s.status().unwrap_or_default() ) }) .collect::>() @@ -478,10 +476,8 @@ impl AutoScalingScenario { )); } - let auto_scaling_groups = describe_auto_scaling_groups - .unwrap() - .auto_scaling_groups - .unwrap_or_default(); + 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() { @@ -509,7 +505,7 @@ impl AutoScalingScenario { .map_err(|e| { ScenarioError::new("Failed to get autoscaling activities for group", &e) })?; - let activities = describe_activities.activities.unwrap_or_default(); + let activities = describe_activities.activities(); trace!( "Waiting for no scaling found {} activities", activities.len() @@ -542,19 +538,22 @@ impl AutoScalingScenario { // 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. - Ok(self - .autoscaling + self.autoscaling .describe_auto_scaling_instances() .into_paginator() .items() .send() - .filter(|i| i.is_ok()) - .map(|i| i.unwrap()) - .filter(|i| i.auto_scaling_group_name() == Some(self.auto_scaling_group_name.as_str())) - .map(|i| i.instance_id.clone().unwrap_or_default()) - .filter(|id| !id.is_empty()) - .collect::>() - .await) + .try_collect() + .await + .map(|items| { + items + .into_iter() + .filter(|i| i.auto_scaling_group_name == self.auto_scaling_group_name) + .map(|i| i.instance_id) + .filter(|id| !id.is_empty()) + .collect::>() + }) + .map_err(|err| ScenarioError::new("Failed to get list of auto scaling instances", &err)) } // snippet-end:[rust.auto-scaling.scenario.list_instances] @@ -655,14 +654,15 @@ impl AutoScalingScenario { // snippet-start:[rust.auto-scaling.scenario.terminate_some_instance] pub async fn terminate_some_instance(&self) -> Result<(), ScenarioError> { // Retrieve a list of instances in the auto scaling group. - let instances = self.get_group().await?.instances.unwrap_or_default(); + 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 termination = self .ec2 .terminate_instances() - .instance_ids(instance.instance_id().unwrap_or_default()) + .instance_ids(instance.instance_id()) .send() .await; if let Err(err) = termination {