From b11d1555600696eb6db70bc741938e0bd949a1ef Mon Sep 17 00:00:00 2001 From: Claudio Cicconetti Date: Wed, 16 Oct 2024 18:20:04 +0200 Subject: [PATCH 1/4] edgeless_node: Add --available-resources --- README.md | 4 ++ edgeless_node/src/bin/edgeless_node_d.rs | 42 +++++++++++++++++++ edgeless_node/src/lib.rs | 29 ++++++------- edgeless_node/src/resources/dda/mod.rs | 30 +++++++++++++ edgeless_node/src/resources/file_log.rs | 26 ++++++++++++ edgeless_node/src/resources/http_egress.rs | 20 +++++++++ edgeless_node/src/resources/http_ingress.rs | 26 ++++++++++++ edgeless_node/src/resources/kafka_egress.rs | 26 ++++++++++++ edgeless_node/src/resources/mod.rs | 1 + edgeless_node/src/resources/ollama.rs | 20 +++++++++ edgeless_node/src/resources/redis.rs | 23 ++++++++++ .../src/resources/resource_provider_specs.rs | 9 ++++ 12 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 edgeless_node/src/resources/resource_provider_specs.rs diff --git a/README.md b/README.md index 638370ba..d9ff0920 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ below. | `ollama` | Interact via an LLM ChatBot deployed on an external [ollama](https://ollama.com/) server | host, port, messages_number_limit, provider (separate section) | model | [click](examples/ollama/README.md) | | `redis` | Update a value on an external [Redis](https://redis.io/) server | redis_provider | url, key | [click](examples/redis/README.md) | +With `edgeless_node_d --available-resources` you can find the list of resource +providers that a node supports, along with the version, output channels, and +configuration parameters for each resource provider. + ### Functions Functions are _stateful_: a given function instance is assigned to exactly one diff --git a/edgeless_node/src/bin/edgeless_node_d.rs b/edgeless_node/src/bin/edgeless_node_d.rs index 7e72a794..331ddfa8 100644 --- a/edgeless_node/src/bin/edgeless_node_d.rs +++ b/edgeless_node/src/bin/edgeless_node_d.rs @@ -2,6 +2,15 @@ // SPDX-FileCopyrightText: © 2023 Claudio Cicconetti // SPDX-License-Identifier: MIT use clap::Parser; +use edgeless_node::resources::dda::DdaResourceSpec; +use edgeless_node::resources::file_log::FileLogResourceSpec; +use edgeless_node::resources::http_egress::HttpEgressResourceSpec; +use edgeless_node::resources::http_ingress::HttpIngressResourceSpec; +#[cfg(feature = "rdkafka")] +use edgeless_node::resources::kafka_egress::KafkaEgressResourceSpec; +use edgeless_node::resources::ollama::OllamasResourceSpec; +use edgeless_node::resources::redis::RedisResourceSpec; +use edgeless_node::resources::resource_provider_specs::ResourceProviderSpecs; #[derive(Debug, clap::Parser)] #[command(long_about = None)] @@ -10,6 +19,8 @@ struct Args { config_file: String, #[arg(short, long, default_value_t = String::from(""))] template: String, + #[arg(long, default_value_t = false)] + available_resources: bool, } fn main() -> anyhow::Result<()> { @@ -20,6 +31,37 @@ fn main() -> anyhow::Result<()> { // console_subscriber::init(); let args = Args::parse(); + if args.available_resources { + #[allow(unused_mut)] + let mut specs: Vec> = vec![ + Box::new(DdaResourceSpec {}), + Box::new(FileLogResourceSpec {}), + Box::new(HttpEgressResourceSpec {}), + Box::new(HttpIngressResourceSpec {}), + Box::new(OllamasResourceSpec {}), + Box::new(RedisResourceSpec {}), + ]; + #[cfg(feature = "rdkafka")] + specs.push(Box::new(KafkaEgressResourceSpec {})); + for spec in specs { + println!("class_type: {}", spec.class_type()); + println!("version: {}", spec.version()); + println!("outputs: [{}]", spec.outputs().join(",")); + if !spec.configurations().is_empty() { + println!("configurations:"); + println!( + "{}", + spec.configurations() + .iter() + .map(|(field, desc)| format!("- {}: {}", field, desc)) + .collect::>() + .join("\n") + ) + } + println!(); + } + return Ok(()); + } if !args.template.is_empty() { edgeless_api::util::create_template(&args.template, edgeless_node::edgeless_node_default_conf().as_str())?; return Ok(()); diff --git a/edgeless_node/src/lib.rs b/edgeless_node/src/lib.rs index 946fd1c9..9f19c5e8 100644 --- a/edgeless_node/src/lib.rs +++ b/edgeless_node/src/lib.rs @@ -5,6 +5,7 @@ use edgeless_api::orc::OrchestratorAPI; use futures::Future; +use resources::resource_provider_specs::ResourceProviderSpecs; pub mod agent; pub mod base_runtime; @@ -297,7 +298,7 @@ async fn fill_resources( if let Some(settings) = settings { if let (Some(http_ingress_url), Some(provider_id)) = (&settings.http_ingress_url, &settings.http_ingress_provider) { if !http_ingress_url.is_empty() && !provider_id.is_empty() { - let class_type = "http-ingress".to_string(); + let class_type = resources::http_ingress::HttpIngressResourceSpec {}.class_type(); log::info!("Creating resource '{}' at {}", provider_id, http_ingress_url); ret.insert( provider_id.clone(), @@ -314,7 +315,7 @@ async fn fill_resources( provider_specifications.push(edgeless_api::node_registration::ResourceProviderSpecification { provider_id: provider_id.clone(), class_type, - outputs: vec!["new_request".to_string()], + outputs: resources::http_ingress::HttpIngressResourceSpec {}.outputs(), }); } } @@ -322,7 +323,7 @@ async fn fill_resources( if let Some(provider_id) = &settings.http_egress_provider { if !provider_id.is_empty() { log::info!("Creating resource '{}'", provider_id); - let class_type = "http-egress".to_string(); + let class_type = resources::http_egress::HttpEgressResourceSpec {}.class_type(); ret.insert( provider_id.clone(), agent::ResourceDesc { @@ -339,7 +340,7 @@ async fn fill_resources( provider_specifications.push(edgeless_api::node_registration::ResourceProviderSpecification { provider_id: provider_id.clone(), class_type, - outputs: vec![], + outputs: resources::http_egress::HttpEgressResourceSpec {}.outputs(), }); } } @@ -347,7 +348,7 @@ async fn fill_resources( if let Some(provider_id) = &settings.file_log_provider { if !provider_id.is_empty() { log::info!("Creating resource '{}'", provider_id); - let class_type = "file-log".to_string(); + let class_type = resources::file_log::FileLogResourceSpec {}.class_type(); ret.insert( provider_id.clone(), agent::ResourceDesc { @@ -364,7 +365,7 @@ async fn fill_resources( provider_specifications.push(edgeless_api::node_registration::ResourceProviderSpecification { provider_id: provider_id.clone(), class_type, - outputs: vec![], + outputs: resources::file_log::FileLogResourceSpec {}.outputs(), }); } } @@ -372,7 +373,7 @@ async fn fill_resources( if let Some(provider_id) = &settings.redis_provider { if !provider_id.is_empty() { log::info!("Creating resource '{}'", provider_id); - let class_type = "redis".to_string(); + let class_type = resources::redis::RedisResourceSpec {}.class_type(); ret.insert( provider_id.clone(), agent::ResourceDesc { @@ -389,7 +390,7 @@ async fn fill_resources( provider_specifications.push(edgeless_api::node_registration::ResourceProviderSpecification { provider_id: provider_id.clone(), class_type, - outputs: vec![], + outputs: resources::redis::RedisResourceSpec {}.outputs(), }); } } @@ -397,7 +398,7 @@ async fn fill_resources( if let Some(provider_id) = &settings.dda_provider { if !provider_id.is_empty() { log::info!("Creating dda resource provider '{}'", provider_id); - let class_type = "dda".to_string(); + let class_type = resources::dda::DdaResourceSpec {}.class_type(); ret.insert( provider_id.clone(), agent::ResourceDesc { @@ -412,7 +413,7 @@ async fn fill_resources( provider_specifications.push(edgeless_api::node_registration::ResourceProviderSpecification { provider_id: provider_id.clone(), class_type, - outputs: vec!["new_request".to_string()], + outputs: resources::dda::DdaResourceSpec {}.outputs(), }); } } @@ -426,7 +427,7 @@ async fn fill_resources( settings.port, settings.messages_number_limit ); - let class_type = "ollama".to_string(); + let class_type = resources::ollama::OllamasResourceSpec {}.class_type(); ret.insert( settings.provider.clone(), agent::ResourceDesc { @@ -447,7 +448,7 @@ async fn fill_resources( provider_specifications.push(edgeless_api::node_registration::ResourceProviderSpecification { provider_id: settings.provider.clone(), class_type, - outputs: vec!["out".to_string()], + outputs: resources::ollama::OllamasResourceSpec {}.outputs(), }); } } @@ -457,7 +458,7 @@ async fn fill_resources( #[cfg(feature = "rdkafka")] { log::info!("Creating resource '{}'", provider_id); - let class_type = "kafka-egress".to_string(); + let class_type = resources::kafka_egress::KafkaEgressResourceSpec {}.class_type(); ret.insert( provider_id.clone(), agent::ResourceDesc { @@ -474,7 +475,7 @@ async fn fill_resources( provider_specifications.push(edgeless_api::node_registration::ResourceProviderSpecification { provider_id: provider_id.clone(), class_type, - outputs: vec![], + outputs: resources::kafka_egress::KafkaEgressResourceSpec {}.outputs(), }); } #[cfg(not(feature = "rdkafka"))] diff --git a/edgeless_node/src/resources/dda/mod.rs b/edgeless_node/src/resources/dda/mod.rs index 4ad06e3e..97027f3d 100644 --- a/edgeless_node/src/resources/dda/mod.rs +++ b/edgeless_node/src/resources/dda/mod.rs @@ -11,6 +11,36 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::Mutex; use uuid::Uuid; +pub struct DdaResourceSpec {} + +impl super::resource_provider_specs::ResourceProviderSpecs for DdaResourceSpec { + fn class_type(&self) -> String { + String::from("dda") + } + + fn outputs(&self) -> Vec { + vec![String::from("out")] + } + + fn configurations(&self) -> std::collections::HashMap { + std::collections::HashMap::from([ + (String::from("dda_url"), String::from("URL of the DDA")), + ( + String::from("dda_com_subscription_mapping"), + String::from("JSON encoding the DDA subscription mapping"), + ), + ( + String::from("dda_com_publication_mapping"), + String::from("JSON encoding the DDA publication mapping"), + ), + ]) + } + + fn version(&self) -> String { + String::from("1.0") + } +} + // imports the generated proto file for dda pub mod dda_com { tonic::include_proto!("dda.com.v1"); diff --git a/edgeless_node/src/resources/file_log.rs b/edgeless_node/src/resources/file_log.rs index 44b69613..69e99c9e 100644 --- a/edgeless_node/src/resources/file_log.rs +++ b/edgeless_node/src/resources/file_log.rs @@ -4,6 +4,32 @@ use edgeless_dataplane::core::Message; use std::io::prelude::*; +pub struct FileLogResourceSpec {} + +impl super::resource_provider_specs::ResourceProviderSpecs for FileLogResourceSpec { + fn class_type(&self) -> String { + String::from("file-log") + } + + fn outputs(&self) -> Vec { + vec![] + } + + fn configurations(&self) -> std::collections::HashMap { + std::collections::HashMap::from([ + ( + String::from("add-source-id"), + String::from("If specified adds the InstanceId of the source component"), + ), + (String::from("add-timestamp"), String::from("If specified adds a timestamp")), + ]) + } + + fn version(&self) -> String { + String::from("1.0") + } +} + #[derive(Clone)] pub struct FileLogResourceProvider { inner: std::sync::Arc>, diff --git a/edgeless_node/src/resources/http_egress.rs b/edgeless_node/src/resources/http_egress.rs index d92d558e..d7cb0ccc 100644 --- a/edgeless_node/src/resources/http_egress.rs +++ b/edgeless_node/src/resources/http_egress.rs @@ -3,6 +3,26 @@ // SPDX-License-Identifier: MIT use edgeless_dataplane::core::Message; +pub struct HttpEgressResourceSpec {} + +impl super::resource_provider_specs::ResourceProviderSpecs for HttpEgressResourceSpec { + fn class_type(&self) -> String { + String::from("http-egress") + } + + fn outputs(&self) -> Vec { + vec![] + } + + fn configurations(&self) -> std::collections::HashMap { + std::collections::HashMap::new() + } + + fn version(&self) -> String { + String::from("1.0") + } +} + #[derive(Clone)] pub struct EgressResourceProvider { inner: std::sync::Arc>, diff --git a/edgeless_node/src/resources/http_ingress.rs b/edgeless_node/src/resources/http_ingress.rs index 41083fc4..65362634 100644 --- a/edgeless_node/src/resources/http_ingress.rs +++ b/edgeless_node/src/resources/http_ingress.rs @@ -5,6 +5,32 @@ use edgeless_api::function_instance::{ComponentId, InstanceId}; use http_body_util::BodyExt; use std::str::FromStr; +pub struct HttpIngressResourceSpec {} + +impl super::resource_provider_specs::ResourceProviderSpecs for HttpIngressResourceSpec { + fn class_type(&self) -> String { + String::from("http-ingress") + } + + fn outputs(&self) -> Vec { + vec!["new_request".to_string()] + } + + fn configurations(&self) -> std::collections::HashMap { + std::collections::HashMap::from([ + ( + String::from("host"), + String::from("Hostname that is used to match incoming HTTP commands"), + ), + (String::from("method"), String::from("Comma-separated list of HTTP methods allowed")), + ]) + } + + fn version(&self) -> String { + String::from("1.0") + } +} + struct ResourceDesc { host: String, allow: std::collections::HashSet, diff --git a/edgeless_node/src/resources/kafka_egress.rs b/edgeless_node/src/resources/kafka_egress.rs index b5648324..475ba9e2 100644 --- a/edgeless_node/src/resources/kafka_egress.rs +++ b/edgeless_node/src/resources/kafka_egress.rs @@ -1,6 +1,32 @@ // SPDX-FileCopyrightText: © 2024 Yuan Yuan Luo // SPDX-License-Identifier: MIT +pub struct KafkaEgressResourceSpec {} + +impl super::resource_provider_specs::ResourceProviderSpecs for KafkaEgressResourceSpec { + fn class_type(&self) -> String { + String::from("kafka-egress") + } + + fn outputs(&self) -> Vec { + vec![] + } + + fn configurations(&self) -> std::collections::HashMap { + std::collections::HashMap::from([ + ( + String::from("brokers"), + String::from("Comma-separated list of initial brokers to access the cluster"), + ), + (String::from("topic"), String::from("Topic to which messages are posted")), + ]) + } + + fn version(&self) -> String { + String::from("1.0") + } +} + #[derive(Clone)] pub struct KafkaEgressResourceProvider { inner: std::sync::Arc>, diff --git a/edgeless_node/src/resources/mod.rs b/edgeless_node/src/resources/mod.rs index 43c6dc35..de9aebbc 100644 --- a/edgeless_node/src/resources/mod.rs +++ b/edgeless_node/src/resources/mod.rs @@ -9,3 +9,4 @@ pub mod kafka_egress; pub mod metrics_collector; pub mod ollama; pub mod redis; +pub mod resource_provider_specs; diff --git a/edgeless_node/src/resources/ollama.rs b/edgeless_node/src/resources/ollama.rs index b09a31bb..3a0aabd9 100644 --- a/edgeless_node/src/resources/ollama.rs +++ b/edgeless_node/src/resources/ollama.rs @@ -3,6 +3,26 @@ use futures::{SinkExt, StreamExt}; +pub struct OllamasResourceSpec {} + +impl super::resource_provider_specs::ResourceProviderSpecs for OllamasResourceSpec { + fn class_type(&self) -> String { + String::from("ollama") + } + + fn outputs(&self) -> Vec { + vec![String::from("new_request")] + } + + fn configurations(&self) -> std::collections::HashMap { + std::collections::HashMap::from([(String::from("model"), String::from("Model to be used for chatting"))]) + } + + fn version(&self) -> String { + String::from("1.0") + } +} + #[derive(Clone)] pub struct OllamaResourceProvider { inner: std::sync::Arc>, diff --git a/edgeless_node/src/resources/redis.rs b/edgeless_node/src/resources/redis.rs index c1859fd8..3e196896 100644 --- a/edgeless_node/src/resources/redis.rs +++ b/edgeless_node/src/resources/redis.rs @@ -5,6 +5,29 @@ use edgeless_dataplane::core::Message; extern crate redis; use redis::Commands; +pub struct RedisResourceSpec {} + +impl super::resource_provider_specs::ResourceProviderSpecs for RedisResourceSpec { + fn class_type(&self) -> String { + String::from("redis") + } + + fn outputs(&self) -> Vec { + vec![] + } + + fn configurations(&self) -> std::collections::HashMap { + std::collections::HashMap::from([ + (String::from("url"), String::from("URL of the Redis server to use")), + (String::from("key"), String::from("Key to set")), + ]) + } + + fn version(&self) -> String { + String::from("1.0") + } +} + #[derive(Clone)] pub struct RedisResourceProvider { inner: std::sync::Arc>, diff --git a/edgeless_node/src/resources/resource_provider_specs.rs b/edgeless_node/src/resources/resource_provider_specs.rs new file mode 100644 index 00000000..ab880a8f --- /dev/null +++ b/edgeless_node/src/resources/resource_provider_specs.rs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: © 2024 Claudio Cicconetti +// SPDX-License-Identifier: MIT + +pub trait ResourceProviderSpecs { + fn class_type(&self) -> String; + fn outputs(&self) -> Vec; + fn configurations(&self) -> std::collections::HashMap; + fn version(&self) -> String; +} From 6815ae119e8ce470904ee97d6d40ffd1030da168 Mon Sep 17 00:00:00 2001 From: Claudio Cicconetti Date: Wed, 16 Oct 2024 18:31:23 +0200 Subject: [PATCH 2/4] Update benchmark doc --- documentation/benchmark.md | 43 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/documentation/benchmark.md b/documentation/benchmark.md index 45472eac..9d9ca4b9 100644 --- a/documentation/benchmark.md +++ b/documentation/benchmark.md @@ -10,12 +10,13 @@ The tool supports different arrival models and workflow types. Arrival models (option `--arrival-model`): -| Arrival model | Description | -| ------------- | --------------------------------------------------------------------------------------------------------- | -| poisson | Inter-arrival between consecutive workflows and durations are exponentially distributed. | -| incremental | One new workflow arrive every new inter-arrival time. | -| incr-and-keep | Add workflows incrementally until the warm up period finishes, then keep until the end of the experiment. | -| single | Add a single workflow. | +| Arrival model | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| poisson | Inter-arrival between consecutive workflows and lifetimes are exponentially distributed. | +| incremental | One new workflow arrive every new inter-arrival time, with constant lifetime. | +| incr-and-keep | Add workflows, with constant lifetimes, incrementally until the warm up period finishes, then keep until the end of the experiment. | +| single | Add a single workflow that lasts for the entire experiment. | +| trace | Read the arrival and end times of workflows from a file specified with `--workload-trace`, one workflow per line in the format `arrival,end_time` | Workflow types (option `--wf_type`): @@ -26,6 +27,19 @@ Workflow types (option `--wf_type`): | vector-mul-chain | A chain of functions, each performing the multiplication of an internal random matrix of 32-bit floating point numbers by the input vector received from the caller. | workflow,function | | map-reduce | A workflow consisting of a random number of stages, where each stage is composed of a random number of processing blocks. Before going to the next stage, the output from all the processing blocks in the stage before must be received. | workflow | +For the workflow types, except `single` a template can be generated by specifying `--wf-type "NAME;template"`. +For example, by running: + +```shell +target/debug/edgeless_benchmark --wf-type "map-reduce;template" > map_reduce.json +``` + +A template will be generated in `map_reduce.json`, which can then be loaded with: + +```shell +target/debug/edgeless_benchmark --wf-type "map-reduce;map_reduce.json" +``` + The duration of the experiment is configurable via a command-line option, like the seed used to generate pseudo-random numbers to enable repeatable experiments and the duration of a warm-up period. @@ -119,11 +133,26 @@ In one shell start the EDGELESS in-a-box: target/debug/edgeless_inabox ``` +Then create the JSON file specifying the characteristics of the vector-mul-chain +workflow: + +```shell +cat << EOF > vector_mul_chain.json +{ + "min_chain_length": 5, + "max_chain_length": 5, + "min_input_size": 1000, + "max_input_size": 2000, + "function_wasm_path": "functions/vector_mul/vector_mul.wasm" +} +EOF +``` + In another run the following benchmark, which lasts 30 seconds: ```shell target/debug/edgeless_benchmark \ - -w "vector-mul-chain;5;5;1000;2000;functions/vector_mul/vector_mul.wasm" \ + -w "vector-mul-chain;vector_mul_chain.json" \ --dataset-path "dataset/myexp-" \ --additional-fields "a,b" \ --additional-header "h_a,h_b" \ From 15a5027732323924f28652641e7b9665ca2a999a Mon Sep 17 00:00:00 2001 From: Francisco Vicente Parra Date: Fri, 18 Oct 2024 14:29:44 +0200 Subject: [PATCH 3/4] Propose adding attribute to enable JSON output format --- edgeless_node/src/bin/edgeless_node_d.rs | 51 ++++++++++++++++++------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/edgeless_node/src/bin/edgeless_node_d.rs b/edgeless_node/src/bin/edgeless_node_d.rs index 331ddfa8..381505f0 100644 --- a/edgeless_node/src/bin/edgeless_node_d.rs +++ b/edgeless_node/src/bin/edgeless_node_d.rs @@ -21,6 +21,8 @@ struct Args { template: String, #[arg(long, default_value_t = false)] available_resources: bool, + #[arg(long, default_value_t = false)] + output_json: bool, } fn main() -> anyhow::Result<()> { @@ -43,22 +45,47 @@ fn main() -> anyhow::Result<()> { ]; #[cfg(feature = "rdkafka")] specs.push(Box::new(KafkaEgressResourceSpec {})); - for spec in specs { - println!("class_type: {}", spec.class_type()); - println!("version: {}", spec.version()); - println!("outputs: [{}]", spec.outputs().join(",")); - if !spec.configurations().is_empty() { - println!("configurations:"); - println!( - "{}", + + if args.output_json { + let mut json_output = String::from("["); + for (i, spec) in specs.iter().enumerate() { + let resource_json = format!( + r#"{{"class_type": "{}", "version": "{}", "outputs": [{}], "configurations": [{}]}}"#, + spec.class_type(), + spec.version(), + spec.outputs().iter().map(|o| format!(r#""{}""#, o)).collect::>().join(", "), spec.configurations() .iter() - .map(|(field, desc)| format!("- {}: {}", field, desc)) + .map(|(field, desc)| format!(r#"{{"field": "{}", "desc": "{}"}}"#, field, desc)) .collect::>() - .join("\n") - ) + .join(", ") + ); + + json_output.push_str(&resource_json); + if i < specs.len() - 1 { + json_output.push_str(", "); + } + } + json_output.push_str("]"); + println!("{}", json_output); + } else { + for spec in specs { + println!("class_type: {}", spec.class_type()); + println!("version: {}", spec.version()); + println!("outputs: [{}]", spec.outputs().join(",")); + if !spec.configurations().is_empty() { + println!("configurations:"); + println!( + "{}", + spec.configurations() + .iter() + .map(|(field, desc)| format!(" - {}: {}", field, desc)) + .collect::>() + .join("\n") + ) + } + println!(); } - println!(); } return Ok(()); } From cef4e2ff04167ac0d215129e998e403eec99b03a Mon Sep 17 00:00:00 2001 From: Claudio Cicconetti Date: Fri, 18 Oct 2024 15:17:52 +0200 Subject: [PATCH 4/4] Minor refactor the JSON output of a node's available resources --- edgeless_node/src/bin/edgeless_node_d.rs | 27 +++++-------------- .../src/resources/resource_provider_specs.rs | 19 +++++++++++++ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/edgeless_node/src/bin/edgeless_node_d.rs b/edgeless_node/src/bin/edgeless_node_d.rs index 381505f0..c1b7ebcb 100644 --- a/edgeless_node/src/bin/edgeless_node_d.rs +++ b/edgeless_node/src/bin/edgeless_node_d.rs @@ -10,6 +10,7 @@ use edgeless_node::resources::http_ingress::HttpIngressResourceSpec; use edgeless_node::resources::kafka_egress::KafkaEgressResourceSpec; use edgeless_node::resources::ollama::OllamasResourceSpec; use edgeless_node::resources::redis::RedisResourceSpec; +use edgeless_node::resources::resource_provider_specs::ResourceProviderSpecOutput; use edgeless_node::resources::resource_provider_specs::ResourceProviderSpecs; #[derive(Debug, clap::Parser)] @@ -47,27 +48,11 @@ fn main() -> anyhow::Result<()> { specs.push(Box::new(KafkaEgressResourceSpec {})); if args.output_json { - let mut json_output = String::from("["); - for (i, spec) in specs.iter().enumerate() { - let resource_json = format!( - r#"{{"class_type": "{}", "version": "{}", "outputs": [{}], "configurations": [{}]}}"#, - spec.class_type(), - spec.version(), - spec.outputs().iter().map(|o| format!(r#""{}""#, o)).collect::>().join(", "), - spec.configurations() - .iter() - .map(|(field, desc)| format!(r#"{{"field": "{}", "desc": "{}"}}"#, field, desc)) - .collect::>() - .join(", ") - ); - - json_output.push_str(&resource_json); - if i < specs.len() - 1 { - json_output.push_str(", "); - } - } - json_output.push_str("]"); - println!("{}", json_output); + println!( + "{}", + serde_json::to_string(&specs.iter().map(|x| x.to_output()).collect::>()) + .expect("could not serialize available resources to JSON") + ); } else { for spec in specs { println!("class_type: {}", spec.class_type()); diff --git a/edgeless_node/src/resources/resource_provider_specs.rs b/edgeless_node/src/resources/resource_provider_specs.rs index ab880a8f..d6cd8d1c 100644 --- a/edgeless_node/src/resources/resource_provider_specs.rs +++ b/edgeless_node/src/resources/resource_provider_specs.rs @@ -7,3 +7,22 @@ pub trait ResourceProviderSpecs { fn configurations(&self) -> std::collections::HashMap; fn version(&self) -> String; } + +#[derive(serde::Serialize)] +pub struct ResourceProviderSpecOutput { + class_type: String, + version: String, + outputs: Vec, + configurations: std::collections::HashMap, +} + +impl dyn ResourceProviderSpecs { + pub fn to_output(&self) -> ResourceProviderSpecOutput { + ResourceProviderSpecOutput { + class_type: self.class_type(), + version: self.version(), + outputs: self.outputs(), + configurations: self.configurations(), + } + } +}