Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Commit

Permalink
Merge pull request #137 from STCLab-Inc/132-scaling-component-azure-f…
Browse files Browse the repository at this point in the history
…unctions

feat: scaling component azure functions
  • Loading branch information
pueding authored Aug 24, 2023
2 parents 87cdac0 + 29e46e2 commit 6ddf956
Show file tree
Hide file tree
Showing 6 changed files with 601 additions and 0 deletions.
297 changes: 297 additions & 0 deletions core/wave-autoscale/src/scaling_component/azure_functions_app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
use super::super::util::azure::{
azure_funtions_app_helper::{call_patch_azure_functions_app, AzureFunctionsPatchAppSetting},
AzureCredential,
};
use super::ScalingComponent;
use anyhow::{Ok, Result};
use async_trait::async_trait;
use data_layer::ScalingComponentDefinition;

use std::collections::HashMap;

pub struct AzureFunctionsAppScalingComponent {
definition: ScalingComponentDefinition,
}

impl AzureFunctionsAppScalingComponent {
// Static variables
pub const SCALING_KIND: &'static str = "azure-functions";

// Functions
pub fn new(definition: ScalingComponentDefinition) -> Self {
AzureFunctionsAppScalingComponent { definition }
}
}

#[async_trait]
impl ScalingComponent for AzureFunctionsAppScalingComponent {
fn get_scaling_component_kind(&self) -> &str {
&self.definition.component_kind
}

fn get_id(&self) -> &str {
&self.definition.id
}

async fn apply(&self, params: HashMap<String, serde_json::Value>) -> Result<()> {
let metadata: HashMap<String, serde_json::Value> = self.definition.metadata.clone();

if let (
Some(serde_json::Value::String(client_id)),
Some(serde_json::Value::String(client_secret)),
Some(serde_json::Value::String(tenant_id)),
Some(serde_json::Value::String(subscription_id)),
Some(serde_json::Value::String(resource_group_name)),
Some(serde_json::Value::String(app_name)),
min_instance_count,
max_instance_count,
) = (
metadata.get("client_id"),
metadata.get("client_secret"),
metadata.get("tenant_id"),
metadata.get("subscription_id"),
metadata.get("resource_group_name"),
metadata.get("app_name"),
params
.get("min_instance_count")
.and_then(serde_json::Value::as_u64)
.map(|v| v as u32),
params
.get("max_instance_count")
.and_then(serde_json::Value::as_u64)
.map(|v| v as u32),
) {
// Call patch azure functions app api
let azure_credential = AzureCredential {
client_id: Some(client_id.to_string()),
client_secret: Some(client_secret.to_string()),
tenant_id: Some(tenant_id.to_string()),
};
let azure_functions_app_setting = AzureFunctionsPatchAppSetting {
azure_credential: azure_credential.clone(),
subscription_id: subscription_id.to_string(),
resource_group_name: resource_group_name.to_string(),
app_name: app_name.to_string(),
payload: Some(create_payload(
min_instance_count
.map(|value| value.to_string())
.as_deref()
.or(None),
max_instance_count
.map(|value| value.to_string())
.as_deref()
.or(None),
)),
};
let result = call_patch_azure_functions_app(azure_functions_app_setting).await;
if result.is_err() {
return Err(anyhow::anyhow!(serde_json::json!({
"message": "API call error",
"code": "500",
"extras": result.unwrap_err().is_body().to_string()
})));
}
let result = result.unwrap();
let result_status_code = result.status();
let result_body: String = match result.text().await {
core::result::Result::Ok(result_body) => result_body,
Err(_error) => {
return Err(anyhow::anyhow!(serde_json::json!({
"message": "API call error",
"code": "500",
"extras": "Not found response text",
})));
}
};
if !result_status_code.is_success() {
log::error!("API call error: {:?}", &result_body);
let json = serde_json::json!({
"message": "API call error",
"code": result_status_code.as_str(),
"extras": result_body
});
return Err(anyhow::anyhow!(json));
}

Ok(())
} else {
Err(anyhow::anyhow!("Invalid metadata"))
}
}
}

// Create payload to patch azure functions app api
fn create_payload(
min_instance_count: Option<&str>,
max_instance_count: Option<&str>,
) -> serde_json::Value {
match (min_instance_count, max_instance_count) {
(Some(min_instance_count), Some(max_instance_count)) => {
serde_json::json!({
"properties": {
"siteConfig": {
"functionAppScaleLimit": max_instance_count,
"minimumElasticInstanceCount": min_instance_count
}
}
})
}
(_, Some(max_instance_count)) => {
serde_json::json!({
"properties": {
"siteConfig": {
"functionAppScaleLimit": max_instance_count
}
}
})
}
_ => serde_json::Value::Null,
}
}

#[cfg(test)]
mod test {
use super::AzureFunctionsAppScalingComponent;
use crate::scaling_component::ScalingComponent;
use data_layer::ScalingComponentDefinition;
use std::collections::HashMap;

#[ignore]
#[tokio::test]
async fn apply_call_patch_azure_functions_app_for_consumption_plan() {
let metadata: HashMap<String, serde_json::Value> = vec![
(String::from("client_id"), serde_json::json!("CLIENT_ID")),
(
String::from("client_secret"),
serde_json::json!("CLIENT_SECRET"),
),
(String::from("tenant_id"), serde_json::json!("TENANT_ID")),
(
String::from("subscription_id"),
serde_json::json!("SUBSCRIPTION_ID"),
),
(
String::from("resource_group_name"),
serde_json::json!("RESOURCE_GROUP_NAME"),
),
(String::from("app_name"), serde_json::json!("APP_NAME")),
]
.into_iter()
.collect();
let params: HashMap<String, serde_json::Value> =
vec![(String::from("max_instance_count"), serde_json::json!(5))]
.into_iter()
.collect();
let scaling_definition = ScalingComponentDefinition {
kind: data_layer::types::object_kind::ObjectKind::ScalingComponent,
db_id: String::from("db-id"),
id: String::from("scaling-id"),
component_kind: String::from("azure-functions"),
metadata,
};
let azure_functions_app_scaling_component: Result<(), anyhow::Error> =
AzureFunctionsAppScalingComponent::new(scaling_definition)
.apply(params)
.await;

println!(
"azure_functions_app_scaling_component: {:?}",
azure_functions_app_scaling_component
);
assert!(azure_functions_app_scaling_component.is_ok());
}

#[ignore]
#[tokio::test]
async fn apply_call_patch_azure_functions_app_for_premium_plan() {
let metadata: HashMap<String, serde_json::Value> = vec![
(String::from("client_id"), serde_json::json!("CLIENT_ID")),
(
String::from("client_secret"),
serde_json::json!("CLIENT_SECRET"),
),
(String::from("tenant_id"), serde_json::json!("TENANT_ID")),
(
String::from("subscription_id"),
serde_json::json!("SUBSCRIPTION_ID"),
),
(
String::from("resource_group_name"),
serde_json::json!("RESOURCE_GROUP_NAME"),
),
(String::from("app_name"), serde_json::json!("APP_NAME")),
]
.into_iter()
.collect();
let params: HashMap<String, serde_json::Value> = vec![
(String::from("min_instance_count"), serde_json::json!(2)),
(String::from("max_instance_count"), serde_json::json!(5)),
]
.into_iter()
.collect();
let scaling_definition = ScalingComponentDefinition {
kind: data_layer::types::object_kind::ObjectKind::ScalingComponent,
db_id: String::from("db-id"),
id: String::from("scaling-id"),
component_kind: String::from("azure-functions"),
metadata,
};
let azure_functions_app_scaling_component: Result<(), anyhow::Error> =
AzureFunctionsAppScalingComponent::new(scaling_definition)
.apply(params)
.await;

println!(
"azure_functions_app_scaling_component: {:?}",
azure_functions_app_scaling_component
);
assert!(azure_functions_app_scaling_component.is_ok());
}

#[ignore]
#[tokio::test]
async fn apply_error_call_patch_azure_functions_app_for_consumption_plan() {
let metadata: HashMap<String, serde_json::Value> = vec![
(String::from("client_id"), serde_json::json!("CLIENT_ID")),
(
String::from("client_secret"),
serde_json::json!("CLIENT_SECRET"),
),
(String::from("tenant_id"), serde_json::json!("TENANT_ID")),
(
String::from("subscription_id"),
serde_json::json!("SUBSCRIPTION_ID"),
),
(
String::from("resource_group_name"),
serde_json::json!("RESOURCE_GROUP_NAME"),
),
(String::from("app_name"), serde_json::json!("APP_NAME")),
]
.into_iter()
.collect();
let params: HashMap<String, serde_json::Value> = vec![
(String::from("min_instance_count"), serde_json::json!(2)),
(String::from("max_instance_count"), serde_json::json!(5)),
]
.into_iter()
.collect();
let scaling_definition = ScalingComponentDefinition {
kind: data_layer::types::object_kind::ObjectKind::ScalingComponent,
db_id: String::from("db-id"),
id: String::from("scaling-id"),
component_kind: String::from("azure-functions"),
metadata,
};
let azure_functions_app_scaling_component: Result<(), anyhow::Error> =
AzureFunctionsAppScalingComponent::new(scaling_definition)
.apply(params)
.await;

println!(
"azure_functions_app_scaling_component: {:?}",
azure_functions_app_scaling_component
);
assert!(azure_functions_app_scaling_component.is_ok());
}
}
5 changes: 5 additions & 0 deletions core/wave-autoscale/src/scaling_component/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod amazon_emr_ec2;
pub mod aws_ec2_autoscaling;
pub mod aws_ecs_service_scaling;
pub mod aws_lambda_function;
pub mod azure_functions_app;
pub mod azure_vmss_autoscaling;
pub mod gcp_mig_autoscaling;
pub mod google_cloud_functions_instance;
Expand All @@ -17,6 +18,7 @@ use self::{
amazon_emr_ec2::EMREC2AutoScalingComponent, aws_ec2_autoscaling::EC2AutoScalingComponent,
aws_ecs_service_scaling::ECSServiceScalingComponent,
aws_lambda_function::LambdaFunctionScalingComponent,
azure_functions_app::AzureFunctionsAppScalingComponent,
azure_vmss_autoscaling::VMSSAutoScalingComponent, gcp_mig_autoscaling::MIGAutoScalingComponent,
google_cloud_functions_instance::CloudFunctionsInstanceScalingComponent,
google_cloud_run_service::CloudRunServiceScalingComponent,
Expand Down Expand Up @@ -90,6 +92,9 @@ impl ScalingComponentManager {
CloudRunServiceScalingComponent::SCALING_KIND => Ok(Box::new(
CloudRunServiceScalingComponent::new(cloned_defintion),
)),
AzureFunctionsAppScalingComponent::SCALING_KIND => Ok(Box::new(
AzureFunctionsAppScalingComponent::new(cloned_defintion),
)),
_ => Err(anyhow::anyhow!("Unknown trigger kind")),
}
}
Expand Down
Loading

0 comments on commit 6ddf956

Please sign in to comment.