From f64ae054576301acadcf8899be6a2a72e2092fd5 Mon Sep 17 00:00:00 2001 From: Milind Srivastava Date: Mon, 29 Jun 2026 09:06:54 -0400 Subject: [PATCH] refactor(tools): nest cloudlab/local config under a providers section in config.yaml --- .../CONFIG_PARAMETERS_REFERENCE.md | 18 ++--- asap-tools/experiments/HYDRA_CONFIG_USAGE.md | 72 +++++++++---------- asap-tools/experiments/config/config.yaml | 19 +++-- .../experiment_only_ingest_path.py | 6 +- .../experiments/experiment_run_clickhouse.py | 15 ++-- asap-tools/experiments/experiment_run_e2e.py | 13 +--- ...experiment_run_exporters_and_prometheus.py | 5 -- .../experiment_run_grafana_demo.py | 6 +- .../experiment_run_sketchdboffline.py | 5 -- .../experiment_teardown_everything.py | 5 -- .../experiments/experiment_utils/config.py | 41 +++++++---- .../experiment_utils/providers/factory.py | 55 ++++---------- asap-tools/experiments/grafana_config.py | 4 +- 13 files changed, 111 insertions(+), 153 deletions(-) diff --git a/asap-tools/experiments/CONFIG_PARAMETERS_REFERENCE.md b/asap-tools/experiments/CONFIG_PARAMETERS_REFERENCE.md index 6f177586..fba9c6ac 100644 --- a/asap-tools/experiments/CONFIG_PARAMETERS_REFERENCE.md +++ b/asap-tools/experiments/CONFIG_PARAMETERS_REFERENCE.md @@ -30,20 +30,20 @@ These parameters must be provided for all experiment scripts: - **Validation**: Cannot be `???` or empty - **Used by**: All experiment scripts -#### `cloudlab.num_nodes` (int, required) +#### `providers.cloudlab.num_nodes` (int, required) - **Description**: Number of CloudLab nodes to allocate for the experiment - **Range**: 1-50 (recommended) - **Example**: `4` - **Validation**: Must be positive integer - **Used by**: All experiment scripts -#### `cloudlab.username` (string, required) +#### `providers.cloudlab.username` (string, required) - **Description**: Your CloudLab username for SSH access - **Example**: `"myuser"` - **Validation**: Must be valid CloudLab username - **Used by**: All experiment scripts -#### `cloudlab.hostname_suffix` (string, required) +#### `providers.cloudlab.hostname_suffix` (string, required) - **Description**: CloudLab experiment hostname suffix - **Example**: `"myexp.cloudlab.us"` - **Validation**: Must be valid hostname format @@ -612,7 +612,7 @@ These parameters come from the `experiment_type` config group and are prefixed w ```python # Recommended validation rules VALIDATION_RULES = { - "cloudlab.num_nodes": {"type": int, "min": 1, "max": 50}, + "providers.cloudlab.num_nodes": {"type": int, "min": 1, "max": 50}, "prometheus.scrape_interval": {"type": str, "pattern": r"^\d+[smh]$"}, "experiment_params.exporters.exporter_list.fake_exporter.start_port": { "type": int, "min": 1024, "max": 65535 @@ -685,9 +685,9 @@ VALIDATION_RULES = { python experiment_run_e2e.py \ experiment.name=my_test \ experiment_type=cloud_demo \ - cloudlab.num_nodes=4 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us \ + providers.cloudlab.num_nodes=4 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \ prometheus.local_config_dir=/path/to/prometheus/config ``` @@ -719,7 +719,7 @@ python experiment_run_e2e.py \ ```bash python experiment_run_e2e.py \ experiment_type=cloud_demo \ - cloudlab.num_nodes=2 \ + providers.cloudlab.num_nodes=2 \ logging.level=DEBUG \ flow.steady_state_wait=60 \ streaming.do_local_flink=true \ @@ -742,7 +742,7 @@ for nodes in 4 8 16; do python experiment_run_e2e.py \ experiment.name=nodes_${nodes} \ experiment_type=cloud_demo \ - cloudlab.num_nodes=${nodes} \ + providers.cloudlab.num_nodes=${nodes} \ [required params...] done ``` diff --git a/asap-tools/experiments/HYDRA_CONFIG_USAGE.md b/asap-tools/experiments/HYDRA_CONFIG_USAGE.md index c966076d..14a20041 100644 --- a/asap-tools/experiments/HYDRA_CONFIG_USAGE.md +++ b/asap-tools/experiments/HYDRA_CONFIG_USAGE.md @@ -9,9 +9,9 @@ This guide explains how to use the Hydra framework integration for experiment sc python experiment_run_e2e.py \ experiment.name=my_test \ experiment_type=cloud_demo \ - cloudlab.num_nodes=4 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us \ + providers.cloudlab.num_nodes=4 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \ prometheus.local_config_dir=/path/to/prometheus/config ``` @@ -20,9 +20,9 @@ python experiment_run_e2e.py \ ### Required Infrastructure Parameters All experiment scripts require these core parameters: - `experiment.name`: Human-readable experiment name -- `cloudlab.num_nodes`: Number of CloudLab nodes -- `cloudlab.username`: Your CloudLab username -- `cloudlab.hostname_suffix`: CloudLab experiment hostname suffix +- `providers.cloudlab.num_nodes`: Number of CloudLab nodes +- `providers.cloudlab.username`: Your CloudLab username +- `providers.cloudlab.hostname_suffix`: CloudLab experiment hostname suffix ### Script-Specific Required Parameters - **experiment_run_clickhouse.py**: `experiment_type=clickhouse`, `experiment_params.dataset.name`, `experiment_params.dataset.local_data_file`, `experiment_params.query_groups[0].sql_file` @@ -107,9 +107,9 @@ python experiment_run_e2e.py \ python experiment_run_e2e.py \ experiment.name=dev_test \ experiment_type=cloud_demo \ - cloudlab.num_nodes=2 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=dev.cloudlab.us \ + providers.cloudlab.num_nodes=2 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=dev.cloudlab.us \ prometheus.local_config_dir=/path/to/prometheus/config \ logging.level=DEBUG \ flow.steady_state_wait=60 @@ -121,7 +121,7 @@ python experiment_run_e2e.py \ python experiment_run_e2e.py \ experiment.name=arroyo_perf \ experiment_type=my_exp_config \ - cloudlab.num_nodes=8 \ + providers.cloudlab.num_nodes=8 \ streaming.engine=arroyo \ streaming.enable_object_reuse=true \ profiling.arroyo=true \ @@ -136,9 +136,9 @@ python experiment_run_e2e.py experiment.name=delay_60 experiment_type=cloud_demo python experiment_run_e2e.py experiment.name=delay_120 experiment_type=cloud_demo experiment_params.query_groups.0.repetition_delay=120 [required params...] # Test different node counts -python experiment_run_e2e.py experiment.name=nodes_4 experiment_type=cloud_demo cloudlab.num_nodes=4 [required params...] -python experiment_run_e2e.py experiment.name=nodes_8 experiment_type=cloud_demo cloudlab.num_nodes=8 [required params...] -python experiment_run_e2e.py experiment.name=nodes_16 experiment_type=cloud_demo cloudlab.num_nodes=16 [required params...] +python experiment_run_e2e.py experiment.name=nodes_4 experiment_type=cloud_demo providers.cloudlab.num_nodes=4 [required params...] +python experiment_run_e2e.py experiment.name=nodes_8 experiment_type=cloud_demo providers.cloudlab.num_nodes=8 [required params...] +python experiment_run_e2e.py experiment.name=nodes_16 experiment_type=cloud_demo providers.cloudlab.num_nodes=16 [required params...] # Test different streaming engines python experiment_run_e2e.py experiment.name=flink_test experiment_type=cloud_demo streaming.engine=flink [required params...] @@ -165,9 +165,9 @@ cat > config/experiment_type/clickhouse.yaml << 'EOF' # python experiment_run_clickhouse.py \ # experiment_type=clickhouse \ # experiment.name=my_test \ -# cloudlab.num_nodes=1 \ -# cloudlab.username=myuser \ -# cloudlab.hostname_suffix=myexp.cloudlab.us \ +# providers.cloudlab.num_nodes=1 \ +# providers.cloudlab.username=myuser \ +# providers.cloudlab.hostname_suffix=myexp.cloudlab.us \ # experiment_params.dataset.name=clickbench \ # experiment_params.dataset.local_data_file=/path/to/hits.json \ # 'experiment_params.query_groups[0].sql_file=/path/to/queries.sql' @@ -203,9 +203,9 @@ EOF python experiment_run_clickhouse.py \ experiment_type=clickhouse \ experiment.name=my_clickbench_run \ - cloudlab.num_nodes=1 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us \ + providers.cloudlab.num_nodes=1 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \ experiment_params.dataset.name=clickbench \ experiment_params.dataset.local_data_file=/path/to/hits.json \ 'experiment_params.query_groups[0].sql_file=/path/to/queries.sql' @@ -231,9 +231,9 @@ experiment_params.dataset.max_rows=10000 python experiment_run_e2e.py \ experiment.name=full_test \ experiment_type=cloud_demo \ - cloudlab.num_nodes=4 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us \ + providers.cloudlab.num_nodes=4 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \ prometheus.local_config_dir=/path/to/prometheus/config ``` @@ -242,9 +242,9 @@ python experiment_run_e2e.py \ # Only exporters and Prometheus - no streaming python experiment_run_exporters_and_prometheus.py \ experiment.name=monitoring_test \ - cloudlab.num_nodes=4 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us + providers.cloudlab.num_nodes=4 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us ``` ### experiment_run_empty_flink.py (Simplified Streaming) @@ -253,9 +253,9 @@ python experiment_run_exporters_and_prometheus.py \ python experiment_run_empty_flink.py \ experiment.name=empty_flink_test \ experiment.config_file=/path/to/config.yml \ - cloudlab.num_nodes=4 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us + providers.cloudlab.num_nodes=4 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us ``` ### experiment_run_e2e_no_queryengine.py (E2E without Query Engine) @@ -264,9 +264,9 @@ python experiment_run_empty_flink.py \ python experiment_run_e2e_no_queryengine.py \ experiment.name=e2e_no_qe \ experiment.config_file=/path/to/config.yml \ - cloudlab.num_nodes=4 \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us \ + providers.cloudlab.num_nodes=4 \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \ prometheus.local_config_dir=/path/to/prometheus/config ``` @@ -284,8 +284,8 @@ python experiment_run_sketchdboffline.py \ # Flink performance testing with aggregation scaling python experiment_run_flink_with_different_num_aggregations.py \ experiment.name=flink_perf \ - cloudlab.username=myuser \ - cloudlab.hostname_suffix=myexp.cloudlab.us \ + providers.cloudlab.username=myuser \ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \ experiment_variants.flink_aggregations.config=/path/to/config.yaml \ experiment_variants.flink_aggregations.aggregation_id=0 \ experiment_variants.flink_aggregations.min_aggregations=1 \ @@ -334,9 +334,9 @@ python experiment_run_e2e.py \ python experiment_run_e2e.py \ experiment.name=test \ experiment_type=cloud_demo \ - cloudlab.num_nodes=4 \ - cloudlab.username=user \ - cloudlab.hostname_suffix=exp.cloudlab.us \ + providers.cloudlab.num_nodes=4 \ + providers.cloudlab.username=user \ + providers.cloudlab.hostname_suffix=exp.cloudlab.us \ prometheus.local_config_dir=/path/to/config ``` diff --git a/asap-tools/experiments/config/config.yaml b/asap-tools/experiments/config/config.yaml index a62495a6..7b697450 100644 --- a/asap-tools/experiments/config/config.yaml +++ b/asap-tools/experiments/config/config.yaml @@ -5,12 +5,17 @@ experiment: name: ??? # REQUIRED: Human-readable experiment name -# CloudLab configuration -cloudlab: - num_nodes: ??? # REQUIRED: Number of CloudLab nodes to use - node_offset: 0 # Starting node index (default 0, allows running multiple experiments in parallel) - username: ??? # REQUIRED: Your CloudLab username - hostname_suffix: ??? # REQUIRED: CloudLab experiment hostname suffix +# Infrastructure provider configuration — activate exactly one provider: +# CloudLab (default): fill in the cloudlab fields below via CLI overrides. +# Local machine: uncomment the local section below (leave cloudlab as-is). +providers: + cloudlab: + num_nodes: ??? # REQUIRED for CloudLab: Number of CloudLab nodes to use + node_offset: 0 # Starting node index (default 0, allows running multiple experiments in parallel) + username: ??? # REQUIRED for CloudLab: Your CloudLab username + hostname_suffix: ??? # REQUIRED for CloudLab: CloudLab experiment hostname suffix + # local: # Uncomment this section to run on the local machine instead + # home_dir: ??? # REQUIRED: absolute path to home directory on this machine # Logging and debugging logging: @@ -57,7 +62,7 @@ streaming: use_kafka_ingest: false # Use Prometheus remote write by default, set to true for legacy Kafka ingest # Prometheus remote write configuration remote_write: - ip: "${remote_write_ip:${cloudlab.node_offset}}" + ip: null # set at runtime via provider.get_node_ip(node_offset) base_port: 8080 path: "/api/v1/write" enable_optimized_source: true # Use optimized Prometheus remote_write source (10-20x faster) diff --git a/asap-tools/experiments/experiment_only_ingest_path.py b/asap-tools/experiments/experiment_only_ingest_path.py index 497c6bbb..942e72fc 100644 --- a/asap-tools/experiments/experiment_only_ingest_path.py +++ b/asap-tools/experiments/experiment_only_ingest_path.py @@ -30,11 +30,6 @@ "local_experiment_dir", lambda: constants.LOCAL_EXPERIMENT_DIR ) -# Register custom resolver for remote write IP based on node_offset -OmegaConf.register_new_resolver( - "remote_write_ip", lambda node_offset: f"10.10.1.{node_offset + 1}" -) - KAFKA_NUM_TRIES = 5 CONTROLLER_LOCAL_OUTPUT_DIR = None CONTROLLER_REMOTE_OUTPUT_DIR = None @@ -83,6 +78,7 @@ def main(cfg: DictConfig): # Create infrastructure provider provider = create_provider(cfg) + args.remote_write_ip = provider.get_node_ip(args.node_offset) local_experiment_root_dir = os.path.join( constants.LOCAL_EXPERIMENT_DIR, args.experiment_name diff --git a/asap-tools/experiments/experiment_run_clickhouse.py b/asap-tools/experiments/experiment_run_clickhouse.py index 3ca71375..2a4855ed 100644 --- a/asap-tools/experiments/experiment_run_clickhouse.py +++ b/asap-tools/experiments/experiment_run_clickhouse.py @@ -69,9 +69,9 @@ python experiment_run_clickhouse.py \\ experiment_type=clickhouse \\ experiment.name=my_bench \\ - cloudlab.num_nodes=1 \\ - cloudlab.username=myuser \\ - cloudlab.hostname_suffix=myexp.cloudlab.us \\ + providers.cloudlab.num_nodes=1 \\ + providers.cloudlab.username=myuser \\ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \\ experiment_params.dataset.local_data_file=/path/to/hits.json \\ 'experiment_params.query_groups[0].sql_file=/path/to/clickbench.sql' @@ -79,9 +79,9 @@ python experiment_run_clickhouse.py \\ experiment_type=clickhouse \\ experiment.name=my_bench \\ - cloudlab.num_nodes=1 \\ - cloudlab.username=myuser \\ - cloudlab.hostname_suffix=myexp.cloudlab.us \\ + providers.cloudlab.num_nodes=1 \\ + providers.cloudlab.username=myuser \\ + providers.cloudlab.hostname_suffix=myexp.cloudlab.us \\ experiment_params.dataset.local_data_file=/path/to/hits.json \\ 'experiment_params.query_groups[0].sql_file=/path/to/clickbench.sql' @@ -148,9 +148,6 @@ def _expand_groups(groups): OmegaConf.register_new_resolver( "local_experiment_dir", lambda: constants.LOCAL_EXPERIMENT_DIR ) -OmegaConf.register_new_resolver( - "remote_write_ip", lambda node_offset: f"10.10.1.{node_offset + 1}" -) def _run_query_client( diff --git a/asap-tools/experiments/experiment_run_e2e.py b/asap-tools/experiments/experiment_run_e2e.py index 74b22577..3779c2cb 100644 --- a/asap-tools/experiments/experiment_run_e2e.py +++ b/asap-tools/experiments/experiment_run_e2e.py @@ -47,11 +47,6 @@ "local_experiment_dir", lambda: constants.LOCAL_EXPERIMENT_DIR ) -# Register custom resolver for remote write IP based on node_offset -OmegaConf.register_new_resolver( - "remote_write_ip", lambda node_offset: f"10.10.1.{node_offset + 1}" -) - @hydra.main(version_base=None, config_path="config", config_name="config") def main(cfg: DictConfig): @@ -63,15 +58,9 @@ def main(cfg: DictConfig): # Create infrastructure provider provider = create_provider(cfg) - # Route the remote-write IP through the provider (127.0.0.1 for local mode, - # CloudLab's 10.10.1.x scheme otherwise) before anything resolves - # cfg.streaming.remote_write.ip (Args does, below). - OmegaConf.register_new_resolver( - "remote_write_ip", provider.get_node_ip, replace=True - ) - # Convert config to args-like object for backward compatibility args = config.Args(cfg) + args.remote_write_ip = provider.get_node_ip(args.node_offset) if provider.is_remote(): local_experiment_root_dir = os.path.join( diff --git a/asap-tools/experiments/experiment_run_exporters_and_prometheus.py b/asap-tools/experiments/experiment_run_exporters_and_prometheus.py index ebc8bdd3..ba11bab3 100644 --- a/asap-tools/experiments/experiment_run_exporters_and_prometheus.py +++ b/asap-tools/experiments/experiment_run_exporters_and_prometheus.py @@ -22,11 +22,6 @@ "local_experiment_dir", lambda: constants.LOCAL_EXPERIMENT_DIR ) -# Register custom resolver for remote write IP based on node_offset -OmegaConf.register_new_resolver( - "remote_write_ip", lambda node_offset: f"10.10.1.{node_offset + 1}" -) - @hydra.main(version_base=None, config_path="config", config_name="config") def main(cfg: DictConfig): diff --git a/asap-tools/experiments/experiment_run_grafana_demo.py b/asap-tools/experiments/experiment_run_grafana_demo.py index 52377c88..906fa3bd 100644 --- a/asap-tools/experiments/experiment_run_grafana_demo.py +++ b/asap-tools/experiments/experiment_run_grafana_demo.py @@ -43,11 +43,6 @@ "local_experiment_dir", lambda: constants.LOCAL_EXPERIMENT_DIR ) -# Register custom resolver for remote write IP based on node_offset -OmegaConf.register_new_resolver( - "remote_write_ip", lambda node_offset: f"10.10.1.{node_offset + 1}" -) - @hydra.main(version_base=None, config_path="config", config_name="config") def main(cfg: DictConfig): @@ -60,6 +55,7 @@ def main(cfg: DictConfig): # Create infrastructure provider provider = create_provider(cfg) + args.remote_write_ip = provider.get_node_ip(args.node_offset) args.forward_unsupported_queries = True print("Forcing forward_unsupported_queries to True for Grafana demo") diff --git a/asap-tools/experiments/experiment_run_sketchdboffline.py b/asap-tools/experiments/experiment_run_sketchdboffline.py index 1b040479..74348928 100644 --- a/asap-tools/experiments/experiment_run_sketchdboffline.py +++ b/asap-tools/experiments/experiment_run_sketchdboffline.py @@ -17,11 +17,6 @@ "local_experiment_dir", lambda: constants.LOCAL_EXPERIMENT_DIR ) -# Register custom resolver for remote write IP based on node_offset -OmegaConf.register_new_resolver( - "remote_write_ip", lambda node_offset: f"10.10.1.{node_offset + 1}" -) - def run_sketchdboffline(args, output_dir) -> subprocess.Popen: cmd = ( diff --git a/asap-tools/experiments/experiment_teardown_everything.py b/asap-tools/experiments/experiment_teardown_everything.py index f14d277c..534fbd39 100644 --- a/asap-tools/experiments/experiment_teardown_everything.py +++ b/asap-tools/experiments/experiment_teardown_everything.py @@ -40,11 +40,6 @@ "local_experiment_dir", lambda: constants.LOCAL_EXPERIMENT_DIR ) -# Register custom resolver for remote write IP based on node_offset -OmegaConf.register_new_resolver( - "remote_write_ip", lambda node_offset: f"10.10.1.{node_offset + 1}" -) - @hydra.main(version_base=None, config_path="config", config_name="config") def main(cfg: DictConfig): diff --git a/asap-tools/experiments/experiment_utils/config.py b/asap-tools/experiments/experiment_utils/config.py index c4655e8d..af0d59d6 100644 --- a/asap-tools/experiments/experiment_utils/config.py +++ b/asap-tools/experiments/experiment_utils/config.py @@ -50,28 +50,28 @@ def validate_basic_config( elif "cloudlab.username" in param_path: error_msg += f" {param_path}=myuser \\\n" elif "cloudlab.hostname_suffix" in param_path: - error_msg += f" {param_path}=myexp.cloudlab.us\n" + error_msg += f" {param_path}=myexp.cloudlab.us \\\n" raise ValueError(error_msg) def get_node_params(cfg: DictConfig) -> Tuple[int, int]: """Return (num_nodes, node_offset). Local mode is a single node and never - touches cfg.cloudlab (whose username/hostname_suffix are mandatory-missing + touches cfg.providers.cloudlab (whose username/hostname_suffix are mandatory-missing markers that raise on mere attribute access if not provided).""" - if hasattr(cfg, "local"): + if hasattr(cfg.providers, "local"): return 0, 0 - return cfg.cloudlab.num_nodes, cfg.cloudlab.node_offset + return cfg.providers.cloudlab.num_nodes, cfg.providers.cloudlab.node_offset def required_cloudlab_params(cfg: DictConfig) -> List[Tuple[str, str]]: - """Return cloudlab.* required-param tuples, or [] when running in local mode.""" - if hasattr(cfg, "local"): + """Return providers.cloudlab.* required-param tuples, or [] when running in local mode.""" + if hasattr(cfg.providers, "local"): return [] return [ - ("cloudlab.num_nodes", "Number of CloudLab nodes to use"), - ("cloudlab.username", "Your CloudLab username"), - ("cloudlab.hostname_suffix", "CloudLab experiment hostname suffix"), + ("providers.cloudlab.num_nodes", "Number of CloudLab nodes to use"), + ("providers.cloudlab.username", "Your CloudLab username"), + ("providers.cloudlab.hostname_suffix", "CloudLab experiment hostname suffix"), ] @@ -489,12 +489,12 @@ def __init__(self, cfg: DictConfig): # CloudLab configuration (or local-mode equivalents) self.num_nodes, self.node_offset = get_node_params(cfg) - if hasattr(cfg, "local"): + if hasattr(cfg.providers, "local"): self.cloudlab_username = None self.hostname_suffix = None else: - self.cloudlab_username = cfg.cloudlab.username - self.hostname_suffix = cfg.cloudlab.hostname_suffix + self.cloudlab_username = cfg.providers.cloudlab.username + self.hostname_suffix = cfg.providers.cloudlab.hostname_suffix # Logging and debugging self.log_level = cfg.logging.level @@ -530,7 +530,9 @@ def __init__(self, cfg: DictConfig): self.forward_unsupported_queries = cfg.streaming.forward_unsupported_queries self.use_kafka_ingest = cfg.streaming.use_kafka_ingest # Remote write configuration - self.remote_write_ip = cfg.streaming.remote_write.ip + self.remote_write_ip = ( + None # set by caller: provider.get_node_ip(self.node_offset) + ) self.remote_write_base_port = cfg.streaming.remote_write.base_port self.remote_write_path = cfg.streaming.remote_write.path @@ -595,6 +597,19 @@ def validate_config(cfg: DictConfig, script_name: str = "experiment_run_e2e"): cfg: The Hydra configuration object script_name: Name of the script for error messages """ + # Enforce exactly one provider (generic: works for any future provider) + active_providers = list(cfg.providers.keys()) + if len(active_providers) == 0: + raise ValueError( + "No provider configured. Uncomment exactly one provider under " + "'providers:' in config/config.yaml." + ) + if len(active_providers) > 1: + raise ValueError( + f"Multiple providers configured: {active_providers}. " + "Comment out all but one under 'providers:' in config/config.yaml." + ) + # Check for required parameters that must be provided via command line required_params = [ ("experiment.name", "Human-readable experiment name"), diff --git a/asap-tools/experiments/experiment_utils/providers/factory.py b/asap-tools/experiments/experiment_utils/providers/factory.py index 34773445..01d03c8a 100644 --- a/asap-tools/experiments/experiment_utils/providers/factory.py +++ b/asap-tools/experiments/experiment_utils/providers/factory.py @@ -29,29 +29,22 @@ def create_provider(cfg: DictConfig) -> InfrastructureProvider: ValueError: If the configuration doesn't contain required parameters or specifies an unsupported provider type """ - # Local mode (e.g. `+local.home_dir=...`) takes priority and must be checked - # before any `cfg.cloudlab.*` access — those fields are Hydra `???` mandatory- - # missing markers, so merely reading them raises if not provided via CLI. - if hasattr(cfg, "local"): - if not hasattr(cfg.local, "home_dir") or not cfg.local.home_dir: - raise ValueError("Missing 'local.home_dir' configuration parameter") - return LocalProvider(home_dir=cfg.local.home_dir) - - # Validate that we have the required CloudLab parameters - if not hasattr(cfg, "cloudlab"): + if hasattr(cfg.providers, "local"): + return LocalProvider(home_dir=cfg.providers.local.home_dir) + + if not cfg.providers.cloudlab.username: raise ValueError( - "Missing 'cloudlab' configuration section. " - "CloudLab provider requires 'cloudlab.username' and 'cloudlab.hostname_suffix'" + "Missing 'providers.cloudlab.username' configuration parameter" ) - if not hasattr(cfg.cloudlab, "username") or not cfg.cloudlab.username: - raise ValueError("Missing 'cloudlab.username' configuration parameter") - - if not hasattr(cfg.cloudlab, "hostname_suffix") or not cfg.cloudlab.hostname_suffix: - raise ValueError("Missing 'cloudlab.hostname_suffix' configuration parameter") + if not cfg.providers.cloudlab.hostname_suffix: + raise ValueError( + "Missing 'providers.cloudlab.hostname_suffix' configuration parameter" + ) return CloudLabProvider( - username=cfg.cloudlab.username, hostname_suffix=cfg.cloudlab.hostname_suffix + username=cfg.providers.cloudlab.username, + hostname_suffix=cfg.providers.cloudlab.hostname_suffix, ) @@ -68,32 +61,14 @@ def detect_provider_type(cfg: DictConfig) -> str: Returns: String identifier for the provider type ('cloudlab', 'aws', 'local', etc.) """ - if hasattr(cfg, "local"): + if hasattr(cfg.providers, "local"): return "local" - # Future phases will add logic to detect other provider types - # based on configuration parameters like: - # - cfg.infrastructure.provider - # - presence of aws/kubernetes configuration sections - # - environment variables - - if ( - hasattr(cfg, "cloudlab") - and cfg.cloudlab.username - and cfg.cloudlab.hostname_suffix - ): + if cfg.providers.cloudlab.username and cfg.providers.cloudlab.hostname_suffix: return "cloudlab" - # For future phases, add detection logic like: - # if hasattr(cfg, "infrastructure") and cfg.infrastructure.provider: - # return cfg.infrastructure.provider - # if hasattr(cfg, "aws"): - # return "aws" - # if hasattr(cfg, "kubernetes"): - # return "kubernetes" - raise ValueError( "Unable to detect infrastructure provider type from configuration. " - "Currently supported: CloudLab (requires cloudlab.username and cloudlab.hostname_suffix) " - "or Local (requires local.home_dir)" + "Currently supported: CloudLab (requires providers.cloudlab.username and providers.cloudlab.hostname_suffix) " + "or Local (requires providers.local section in config/config.yaml)" ) diff --git a/asap-tools/experiments/grafana_config.py b/asap-tools/experiments/grafana_config.py index 7d5d6950..7de02594 100644 --- a/asap-tools/experiments/grafana_config.py +++ b/asap-tools/experiments/grafana_config.py @@ -803,7 +803,7 @@ def main(cfg: DictConfig): python grafana_config.py experiment_type=cloud_demo experiment.name=test_dash --configure # With additional overrides - python grafana_config.py experiment_type=collapsable cloudlab.num_nodes=2 experiment.name=pc_test_3 --configure + python grafana_config.py experiment_type=collapsable providers.cloudlab.num_nodes=2 experiment.name=pc_test_3 --configure """ # Check if we're in configure mode (determined before Hydra processed args) configure_mode = hasattr(main, "_configure_mode") and main._configure_mode @@ -852,7 +852,7 @@ def main(cfg: DictConfig): print("") print(" # With additional overrides") print( - " python grafana_config.py experiment_type=collapsable cloudlab.num_nodes=2 experiment.name=pc_test_3 --configure" + " python grafana_config.py experiment_type=collapsable providers.cloudlab.num_nodes=2 experiment.name=pc_test_3 --configure" ) print("") print("Environment variables for Grafana configuration:")