vantage6 4.10.1rc1__py3-none-any.whl → 4.11.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vantage6 might be problematic. Click here for more details.

@@ -0,0 +1,23 @@
1
+ from click import UsageError
2
+ from vantage6.cli.test.client_script import cli_test_client_script
3
+
4
+ import click
5
+ import unittest
6
+
7
+
8
+ class TestScriptTest(unittest.TestCase):
9
+ def test_script_incorrect_usage(self):
10
+ ctx = click.Context(cli_test_client_script)
11
+
12
+ with self.assertRaises(UsageError):
13
+ ctx.invoke(
14
+ cli_test_client_script,
15
+ script="path/to/script.py",
16
+ task_arguments="{'my_arg': 1}",
17
+ )
18
+
19
+ with self.assertRaises(UsageError):
20
+ ctx.invoke(
21
+ cli_test_client_script,
22
+ task_arguments="not_a_json",
23
+ )
vantage6/cli/__build__ CHANGED
@@ -1 +1 @@
1
- 1
1
+ 2
vantage6/cli/_version.py CHANGED
@@ -7,7 +7,7 @@ with open(os.path.join(here, "__build__")) as fp:
7
7
  __build__ = json.load(fp)
8
8
 
9
9
  # Module version
10
- version_info = (4, 10, 1, "candidate", __build__, 0)
10
+ version_info = (4, 11, 0, "candidate", __build__, 0)
11
11
 
12
12
  # Module version stage suffix map
13
13
  _specifier_ = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""}
vantage6/cli/cli.py CHANGED
@@ -28,6 +28,7 @@ from vantage6.cli.dev.start import start_demo_network
28
28
  from vantage6.cli.dev.stop import stop_demo_network
29
29
  from vantage6.cli.algorithm.create import cli_algorithm_create
30
30
  from vantage6.cli.algorithm.update import cli_algorithm_update
31
+ from vantage6.cli.test.client_script import cli_test_client_script
31
32
  from vantage6.cli.test.feature_tester import cli_test_features
32
33
  from vantage6.cli.test.integration_test import cli_test_integration
33
34
  from vantage6.cli.algostore.attach import cli_algo_store_attach
@@ -124,6 +125,7 @@ def cli_test() -> None:
124
125
  # Define the commands for the test group
125
126
  cli_test.add_command(cli_test_features, name="feature-test")
126
127
  cli_test.add_command(cli_test_integration, name="integration-test")
128
+ cli_test.add_command(cli_test_client_script, name="client-script")
127
129
 
128
130
 
129
131
  # Define the algorithm-store group
@@ -52,6 +52,7 @@ class NodeConfiguration(Configuration):
52
52
  Optional("node_extra_env"): dict,
53
53
  Optional("node_extra_mounts"): [str],
54
54
  Optional("node_extra_hosts"): dict,
55
+ Optional("share_algorithm_logs"): Use(bool),
55
56
  }
56
57
 
57
58
 
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
+ from pathlib import Path
2
3
 
3
4
  from vantage6.common.globals import APPNAME, InstanceType
4
5
  from vantage6.cli.configuration_manager import ServerConfigurationManager
5
6
  from vantage6.cli.globals import (
6
7
  DEFAULT_SERVER_SYSTEM_FOLDERS as S_FOL,
8
+ PROMETHEUS_DIR,
7
9
  ServerType,
8
10
  ServerGlobals,
9
11
  )
@@ -57,6 +59,30 @@ class ServerContext(BaseServerContext):
57
59
  """
58
60
  return f"{APPNAME}-{self.name}-{self.scope}-{ServerType.V6SERVER}"
59
61
 
62
+ @property
63
+ def prometheus_container_name(self) -> str:
64
+ """
65
+ Get the name of the Prometheus Docker container for this server.
66
+
67
+ Returns
68
+ -------
69
+ str
70
+ Prometheus container name, unique to this server instance.
71
+ """
72
+ return f"{APPNAME}-prometheus"
73
+
74
+ @property
75
+ def prometheus_dir(self) -> Path:
76
+ """
77
+ Get the Prometheus directory path.
78
+
79
+ Returns
80
+ -------
81
+ Path
82
+ Path to the Prometheus directory
83
+ """
84
+ return self.data_dir / PROMETHEUS_DIR
85
+
60
86
  @classmethod
61
87
  def from_external_config_file(
62
88
  cls, path: str, system_folders: bool = S_FOL
@@ -12,7 +12,7 @@ from vantage6.common import ensure_config_dir_writable, info, error, generate_ap
12
12
 
13
13
  import vantage6.cli.dev.data as data_dir
14
14
  from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
15
- from vantage6.cli.globals import PACKAGE_FOLDER
15
+ from vantage6.cli.globals import PACKAGE_FOLDER, DefaultDatasets
16
16
  from vantage6.cli.context.server import ServerContext
17
17
  from vantage6.cli.context.node import NodeContext
18
18
  from vantage6.cli.server.common import get_server_context
@@ -20,7 +20,9 @@ from vantage6.cli.server.import_ import cli_server_import
20
20
  from vantage6.cli.utils import prompt_config_name
21
21
 
22
22
 
23
- def create_node_data_files(num_nodes: int, server_name: str) -> list[Path]:
23
+ def create_node_data_files(
24
+ num_nodes: int, server_name: str, dataset: tuple[str, Path]
25
+ ) -> list[tuple[str, Path]]:
24
26
  """Create data files for nodes.
25
27
 
26
28
  Parameters
@@ -29,15 +31,16 @@ def create_node_data_files(num_nodes: int, server_name: str) -> list[Path]:
29
31
  Number of nodes to create data files for.
30
32
  server_name : str
31
33
  Name of the server.
32
-
34
+ dataset : tuple[str, Path]
35
+ Tuple containing the name and the path to the dataset.
33
36
  Returns
34
37
  -------
35
- list[Path]
36
- List of paths to the created data files.
38
+ list[tuple[str, Path]]
39
+ List of the label and paths to the created data files.
37
40
  """
38
41
  info(f"Creating data files for {num_nodes} nodes.")
39
42
  data_files = []
40
- full_df = pd.read_csv(impresources.files(data_dir) / "olympic_athletes_2016.csv")
43
+ full_df = pd.read_csv(dataset[1])
41
44
  length_df = len(full_df)
42
45
  for i in range(num_nodes):
43
46
  node_name = f"{server_name}_node_{i + 1}"
@@ -49,16 +52,20 @@ def create_node_data_files(num_nodes: int, server_name: str) -> list[Path]:
49
52
  start = i * length_df // num_nodes
50
53
  end = (i + 1) * length_df // num_nodes
51
54
  data = full_df[start:end]
52
- data_file = data_folder / f"df_{node_name}.csv"
55
+ data_file = data_folder / f"df_{dataset[0]}_{node_name}.csv"
53
56
 
54
57
  # write data to file
55
58
  data.to_csv(data_file, index=False)
56
- data_files.append(data_file)
59
+ data_files.append((dataset[0], data_file))
57
60
  return data_files
58
61
 
59
62
 
60
63
  def create_node_config_file(
61
- server_url: str, port: int, config: dict, server_name: str, datafile: Path
64
+ server_url: str,
65
+ port: int,
66
+ config: dict,
67
+ server_name: str,
68
+ datasets: list[tuple[str, Path]] = (),
62
69
  ) -> None:
63
70
  """Create a node configuration file (YAML).
64
71
 
@@ -77,8 +84,8 @@ def create_node_config_file(
77
84
  additional user_defined_config.
78
85
  server_name : str
79
86
  Configuration name of the dummy server.
80
- datafile : Path
81
- Path to the data file for the node to use.
87
+ datasets : list[tuple[str, Path]]
88
+ List of tuples containing the labels and the paths to the datasets
82
89
  """
83
90
  environment = Environment(
84
91
  loader=FileSystemLoader(PACKAGE_FOLDER / APPNAME / "cli" / "template"),
@@ -102,10 +109,12 @@ def create_node_config_file(
102
109
  error(f"Node configuration file already exists: {full_path}")
103
110
  exit(1)
104
111
 
112
+ databases = [{dataset[0]: dataset[1]} for dataset in datasets]
113
+
105
114
  node_config = template.render(
106
115
  {
107
116
  "api_key": config["api_key"],
108
- "databases": {"default": datafile},
117
+ "databases": databases,
109
118
  "logging": {"file": f"{node_name}.log"},
110
119
  "port": port,
111
120
  "server_url": server_url,
@@ -124,8 +133,7 @@ def create_node_config_file(
124
133
  f.write(node_config)
125
134
 
126
135
  info(
127
- f"Spawned node for organization {Fore.GREEN}{config['org_id']}"
128
- f"{Style.RESET_ALL}"
136
+ f"Spawned node for organization {Fore.GREEN}{config['org_id']}{Style.RESET_ALL}"
129
137
  )
130
138
 
131
139
 
@@ -156,6 +164,7 @@ def generate_node_configs(
156
164
  port: int,
157
165
  server_name: str,
158
166
  extra_node_config: Path | None,
167
+ extra_datasets: list[tuple[str, Path]],
159
168
  ) -> list[dict]:
160
169
  """Generates ``num_nodes`` node configuration files.
161
170
 
@@ -171,6 +180,8 @@ def generate_node_configs(
171
180
  Configuration name of the dummy server.
172
181
  extra_node_config : Path | None
173
182
  Path to file with additional node configuration.
183
+ extra_datasets : list[tuple[str, Path]]
184
+ List of tuples containing the labels and the paths to extra datasets
174
185
 
175
186
  Returns
176
187
  -------
@@ -178,8 +189,36 @@ def generate_node_configs(
178
189
  List of dictionaries containing node configurations.
179
190
  """
180
191
  configs = []
192
+ node_data_files = []
181
193
  extra_config = _read_extra_config_file(extra_node_config)
182
- node_data_files = create_node_data_files(num_nodes, server_name)
194
+
195
+ data_directory = impresources.files(data_dir)
196
+
197
+ # Add default datasets to the list of dataset provided
198
+ for default_dataset in DefaultDatasets:
199
+ extra_datasets.append(
200
+ (default_dataset.name.lower(), data_directory / default_dataset.value)
201
+ )
202
+
203
+ # Check for duplicate dataset labels
204
+ seen_labels = set()
205
+ duplicates = [
206
+ label
207
+ for label in [dataset[0] for dataset in extra_datasets]
208
+ if (label in seen_labels or seen_labels.add(label))
209
+ ]
210
+
211
+ if len(duplicates) > 0:
212
+ error(
213
+ f"Duplicate dataset labels found: {duplicates}. "
214
+ f"Please make sure all dataset labels are unique."
215
+ )
216
+ exit(1)
217
+
218
+ # create the data files for the nodes and get the path and label for each dataset
219
+ for dataset in extra_datasets:
220
+ node_data_files.append(create_node_data_files(num_nodes, server_name, dataset))
221
+
183
222
  for i in range(num_nodes):
184
223
  config = {
185
224
  "org_id": i + 1,
@@ -188,7 +227,11 @@ def generate_node_configs(
188
227
  "user_defined_config": extra_config,
189
228
  }
190
229
  create_node_config_file(
191
- server_url, port, config, server_name, node_data_files[i]
230
+ server_url,
231
+ port,
232
+ config,
233
+ server_name,
234
+ [files[i] for files in node_data_files],
192
235
  )
193
236
  configs.append(config)
194
237
 
@@ -422,6 +465,7 @@ def demo_network(
422
465
  ui_image: str,
423
466
  ui_port: int,
424
467
  algorithm_store_port: int,
468
+ extra_datasets: list[tuple[str, Path]],
425
469
  ) -> tuple[list[dict], Path, Path]:
426
470
  """Generates the demo network.
427
471
 
@@ -448,6 +492,8 @@ def demo_network(
448
492
  Port to run the UI on.
449
493
  algorithm_store_port : int
450
494
  Port to run the algorithm store on.
495
+ extra_datasets : list[tuple[str, Path]]
496
+ List of tuples containing the labels and the paths to extra datasets
451
497
 
452
498
  Returns
453
499
  -------
@@ -455,7 +501,12 @@ def demo_network(
455
501
  Tuple containing node, server import and server configurations.
456
502
  """
457
503
  node_configs = generate_node_configs(
458
- num_nodes, server_url, server_port, server_name, extra_node_config
504
+ num_nodes,
505
+ server_url,
506
+ server_port,
507
+ server_name,
508
+ extra_node_config,
509
+ extra_datasets,
459
510
  )
460
511
  server_import_config = create_vserver_import_config(node_configs, server_name)
461
512
  server_config = create_vserver_config(
@@ -549,6 +600,14 @@ def demo_network(
549
600
  help="YAML File with additional algorithm store configuration. This will be"
550
601
  " appended to the algorithm store configuration file",
551
602
  )
603
+ @click.option(
604
+ "--add-dataset",
605
+ type=(str, click.Path()),
606
+ default=(),
607
+ multiple=True,
608
+ help="Add a dataset to the nodes. The first argument is the label of the database, "
609
+ "the second is the path to the dataset file.",
610
+ )
552
611
  @click.pass_context
553
612
  def create_demo_network(
554
613
  click_ctx: click.Context,
@@ -563,6 +622,7 @@ def create_demo_network(
563
622
  extra_server_config: Path = None,
564
623
  extra_node_config: Path = None,
565
624
  extra_store_config: Path = None,
625
+ add_dataset: list[tuple[str, Path]] = (),
566
626
  ) -> dict:
567
627
  """Creates a demo network.
568
628
 
@@ -584,6 +644,7 @@ def create_demo_network(
584
644
  ui_image,
585
645
  ui_port,
586
646
  algorithm_store_port,
647
+ list(add_dataset),
587
648
  )
588
649
  info(
589
650
  f"Created {Fore.GREEN}{len(demo[0])}{Style.RESET_ALL} node "
vantage6/cli/globals.py CHANGED
@@ -42,6 +42,18 @@ DIAGNOSTICS_IMAGE = "harbor2.vantage6.ai/algorithms/diagnostic"
42
42
  # Address of community algorithm store
43
43
  COMMUNITY_STORE = "https://store.cotopaxi.vantage6.ai/api"
44
44
 
45
+ DEFAULT_PROMETHEUS_IMAGE = "prom/prometheus"
46
+ PROMETHEUS_CONFIG = "prometheus.yml"
47
+ PROMETHEUS_DIR = "prometheus"
48
+
49
+
50
+ # datasets included in the nodes of the dev network
51
+ class DefaultDatasets(str, Enum):
52
+ """Enum containing default datasets"""
53
+
54
+ OLYMPIC_ATHLETES = "olympic_athletes_2016.csv"
55
+ KAPLAN_MEIER_TEST = "km_dataset.csv"
56
+
45
57
 
46
58
  class ServerType(str, Enum):
47
59
  """Enum containing server types"""
@@ -0,0 +1,145 @@
1
+ import yaml
2
+ import docker
3
+ from pathlib import Path
4
+
5
+ from vantage6.common import info, error
6
+ from vantage6.common.docker.network_manager import NetworkManager
7
+ from vantage6.common.globals import DEFAULT_PROMETHEUS_EXPORTER_PORT
8
+ from vantage6.cli.context.server import ServerContext
9
+ from vantage6.cli.globals import (
10
+ DEFAULT_PROMETHEUS_IMAGE,
11
+ PROMETHEUS_CONFIG,
12
+ )
13
+
14
+
15
+ class PrometheusServer:
16
+ """
17
+ Manages the Prometheus Docker container
18
+ """
19
+
20
+ def __init__(
21
+ self, ctx: ServerContext, network_mgr: NetworkManager, image: str = None
22
+ ):
23
+ """
24
+ Initialize the PrometheusServer instance.
25
+
26
+ Parameters
27
+ ----------
28
+ ctx : ServerContext
29
+ The server context containing configuration and paths.
30
+ network_mgr : NetworkManager
31
+ The network manager responsible for managing Docker networks.
32
+ image : str, optional
33
+ The Docker image to use for the Prometheus container. If not provided,
34
+ the default Prometheus image will be used.
35
+ """
36
+ self.ctx = ctx
37
+ self.network_mgr = network_mgr
38
+ self.docker = docker.from_env()
39
+ self.image = image if image else DEFAULT_PROMETHEUS_IMAGE
40
+ self.config_file = Path(self.ctx.data_dir / PROMETHEUS_CONFIG)
41
+ self.data_dir = self.ctx.prometheus_dir
42
+
43
+ def start(self):
44
+ """
45
+ Start a Docker container running Prometheus
46
+ """
47
+ self._prepare_config()
48
+
49
+ volumes = {
50
+ str(self.config_file): {
51
+ "bind": "/etc/prometheus/prometheus.yml",
52
+ "mode": "ro",
53
+ },
54
+ str(self.data_dir): {"bind": "/prometheus", "mode": "rw"},
55
+ }
56
+ ports = {"9090/tcp": 9090}
57
+
58
+ if self._is_container_running():
59
+ info("Prometheus is already running!")
60
+ return
61
+
62
+ self.docker.containers.run(
63
+ name=self.ctx.prometheus_container_name,
64
+ image=self.image,
65
+ volumes=volumes,
66
+ ports=ports,
67
+ detach=True,
68
+ restart_policy={"Name": "unless-stopped"},
69
+ network=self.network_mgr.network_name,
70
+ )
71
+ info("Prometheus container started successfully!")
72
+
73
+ def _prepare_config(self):
74
+ """
75
+ Prepare the Prometheus configuration and data directories
76
+ """
77
+ if not self.config_file.exists():
78
+ error(f"Prometheus configuration file {self.config_file} not found!")
79
+ raise FileNotFoundError(f"{self.config_file} not found!")
80
+
81
+ if not self.data_dir.exists():
82
+ self.data_dir.mkdir(parents=True, exist_ok=True)
83
+
84
+ self._update_prometheus_config()
85
+
86
+ def _update_prometheus_config(self):
87
+ """
88
+ Update the Prometheus configuration file with the server address.
89
+ """
90
+
91
+ try:
92
+ prometheus_exporter_port = self.ctx.config.get("prometheus", {}).get(
93
+ "exporter_port", DEFAULT_PROMETHEUS_EXPORTER_PORT
94
+ )
95
+ server_hostname = self.ctx.prometheus_container_name
96
+ server_address = f"{server_hostname}:{prometheus_exporter_port}"
97
+
98
+ info(
99
+ f"Using Docker container hostname '{server_hostname}' for Prometheus target. "
100
+ "Ensure Prometheus is in the same Docker network to resolve this address."
101
+ )
102
+
103
+ with open(self.config_file, "r") as f:
104
+ config = yaml.safe_load(f)
105
+
106
+ job_name = "vantage6_server_metrics"
107
+ job_exists = any(
108
+ job.get("job_name") == job_name
109
+ for job in config.get("scrape_configs", [])
110
+ )
111
+
112
+ if not job_exists:
113
+ new_job = {
114
+ "job_name": job_name,
115
+ "static_configs": [{"targets": [server_address]}],
116
+ }
117
+ config.setdefault("scrape_configs", []).append(new_job)
118
+ else:
119
+ for job in config["scrape_configs"]:
120
+ if job.get("job_name") == job_name:
121
+ job["static_configs"] = [{"targets": [server_address]}]
122
+
123
+ with open(self.config_file, "w") as f:
124
+ yaml.dump(config, f)
125
+
126
+ info(f"Prometheus configuration updated with target: {server_address}")
127
+
128
+ except Exception as e:
129
+ error(f"Failed to update Prometheus configuration: {e}")
130
+ raise
131
+
132
+ def _is_container_running(self) -> bool:
133
+ """
134
+ Check if a Prometheus container is already running.
135
+
136
+ Returns
137
+ -------
138
+ bool
139
+ True if the Prometheus container is running, False otherwise.
140
+ """
141
+ try:
142
+ container = self.docker.containers.get(self.ctx.prometheus_container_name)
143
+ return container.status == "running"
144
+ except docker.errors.NotFound:
145
+ return False
@@ -11,11 +11,12 @@ from vantage6.common.globals import (
11
11
  InstanceType,
12
12
  )
13
13
 
14
- from vantage6.common.globals import Ports
14
+ from vantage6.common.globals import Ports, DEFAULT_PROMETHEUS_EXPORTER_PORT
15
15
  from vantage6.cli.context.server import ServerContext
16
16
  from vantage6.cli.rabbitmq.queue_manager import RabbitMQManager
17
17
  from vantage6.cli.server.common import stop_ui
18
18
  from vantage6.cli.common.decorator import click_insert_context
19
+ from vantage6.cli.prometheus.monitoring_manager import PrometheusServer
19
20
  from vantage6.cli.common.start import (
20
21
  attach_logs,
21
22
  check_for_start,
@@ -49,6 +50,14 @@ from vantage6.cli.common.start import (
49
50
  "container - use in development only",
50
51
  )
51
52
  @click.option("--rabbitmq-image", default=None, help="RabbitMQ docker image to use")
53
+ @click.option(
54
+ "--with-prometheus",
55
+ "start_prometheus",
56
+ flag_value=True,
57
+ default=False,
58
+ help="Start Prometheus monitoring as a local container",
59
+ )
60
+ @click.option("--prometheus-image", default=None, help="Prometheus docker image to use")
52
61
  @click.option(
53
62
  "--keep/--auto-remove",
54
63
  default=False,
@@ -75,6 +84,8 @@ def cli_server_start(
75
84
  ui_port: int,
76
85
  start_rabbitmq: bool,
77
86
  rabbitmq_image: str,
87
+ start_prometheus: bool,
88
+ prometheus_image: str,
78
89
  keep: bool,
79
90
  mount_src: str,
80
91
  attach: bool,
@@ -135,6 +146,24 @@ def cli_server_start(
135
146
  "cannot be scaled horizontally!"
136
147
  )
137
148
 
149
+ if (
150
+ start_prometheus
151
+ or ctx.config.get("prometheus")
152
+ and ctx.config["prometheus"].get("start_with_server", False)
153
+ ):
154
+ info("Starting Prometheus container")
155
+ _start_prometheus(ctx, prometheus_image, server_network_mgr)
156
+ elif ctx.config.get("prometheus"):
157
+ info(
158
+ "Prometheus is provided in the config file as external service."
159
+ "Assuming this service is up and running."
160
+ )
161
+ else:
162
+ warning(
163
+ "Monitoring is not set up! This means that the vantage6 server "
164
+ "cannot be monitored with Prometheus!"
165
+ )
166
+
138
167
  # start the UI if requested
139
168
  if start_ui or ctx.config.get("ui") and ctx.config["ui"].get("enabled"):
140
169
  _start_ui(docker_client, ctx, ui_port)
@@ -152,6 +181,9 @@ def cli_server_start(
152
181
 
153
182
  info("Run Docker container")
154
183
  port_ = str(port or ctx.config["port"] or Ports.DEV_SERVER.value)
184
+ prometheus_exporter_port = ctx.config.get("prometheus", {}).get(
185
+ "exporter_port", DEFAULT_PROMETHEUS_EXPORTER_PORT
186
+ )
155
187
  container = docker_client.containers.run(
156
188
  image,
157
189
  command=cmd,
@@ -162,7 +194,10 @@ def cli_server_start(
162
194
  "name": ctx.config_file_name,
163
195
  },
164
196
  environment=environment_vars,
165
- ports={f"{internal_port}/tcp": (ip, port_)},
197
+ ports={
198
+ f"{internal_port}/tcp": (ip, port_), # API port
199
+ f"{prometheus_exporter_port}/tcp": prometheus_exporter_port,
200
+ },
166
201
  name=ctx.docker_container_name,
167
202
  auto_remove=not keep,
168
203
  tty=True,
@@ -202,6 +237,27 @@ def _start_rabbitmq(
202
237
  rabbit_mgr.start()
203
238
 
204
239
 
240
+ def _start_prometheus(
241
+ ctx: ServerContext, prometheus_image: str, network_mgr: NetworkManager
242
+ ) -> None:
243
+ """
244
+ Start the Prometheus container if it is not already running.
245
+
246
+ Parameters
247
+ ----------
248
+ ctx : ServerContext
249
+ Server context object
250
+ prometheus_image : str
251
+ Prometheus image to use
252
+ network_mgr : NetworkManager
253
+ Network manager object
254
+ """
255
+ prometheus_server = PrometheusServer(
256
+ ctx=ctx, network_mgr=network_mgr, image=prometheus_image
257
+ )
258
+ prometheus_server.start()
259
+
260
+
205
261
  def _start_ui(client: DockerClient, ctx: ServerContext, ui_port: int) -> None:
206
262
  """
207
263
  Start the UI container.
@@ -117,3 +117,10 @@ def _stop_server_containers(
117
117
  f"Stopped the {Fore.GREEN}{rabbit_container_name}"
118
118
  f"{Style.RESET_ALL} container."
119
119
  )
120
+
121
+ if ctx.config.get("prometheus", {}).get("enabled"):
122
+ remove_container_if_exists(client, name=ctx.prometheus_container_name)
123
+ info(
124
+ f"Stopped the {Fore.GREEN}{ctx.prometheus_container_name}"
125
+ f"{Style.RESET_ALL} container."
126
+ )
@@ -1,11 +1,13 @@
1
1
  api_key: {{ api_key }}
2
2
  api_path: /api
3
3
  databases:
4
- {% for label, path in databases.items() %}
4
+ {% for db in databases %}
5
+ {% for label, path in db.items() %}
5
6
  - label: {{ label }}
6
7
  uri: {{ path }}
7
8
  type: csv
8
9
  {% endfor %}
10
+ {% endfor %}
9
11
  encryption:
10
12
  enabled: false
11
13
  private_key: null
@@ -31,4 +33,4 @@ logging:
31
33
  port: {{ port }}
32
34
  server_url: {{ server_url }}
33
35
  task_dir: {{ task_dir}}
34
- {{ user_provided_config }}
36
+ {{- user_provided_config -}}
@@ -0,0 +1,33 @@
1
+ average = {
2
+ "collaboration": 1,
3
+ "organizations": [1],
4
+ "name": "test_average_task",
5
+ "image": "harbor2.vantage6.ai/demo/average",
6
+ "description": "",
7
+ "input_": {
8
+ "method": "central_average",
9
+ "args": [],
10
+ "kwargs": {"column_name": "Age"},
11
+ },
12
+ "databases": [{"label": "olympic_athletes"}],
13
+ }
14
+
15
+ kaplan_meier = {
16
+ "collaboration": 1,
17
+ "organizations": [1],
18
+ "name": "test_average_task",
19
+ "image": "harbor2.vantage6.ai/algorithms/kaplan-meier",
20
+ "description": "",
21
+ "input_": {
22
+ "method": "kaplan_meier_central",
23
+ "args": [],
24
+ "kwargs": {
25
+ "time_column_name": "days",
26
+ "censor_column_name": "censor",
27
+ "organizations_to_include": [1, 2, 3],
28
+ },
29
+ },
30
+ "databases": [{"label": "kaplan_meier_test"}],
31
+ }
32
+
33
+ args = {"average": average, "kaplan_meier": kaplan_meier}
@@ -0,0 +1,89 @@
1
+ import logging
2
+ import algo_test_arguments as arguments
3
+ import json
4
+ import sys
5
+
6
+ import vantage6.common.task_status as task_status
7
+
8
+ from vantage6.client import Client
9
+ from vantage6.common import error
10
+ from vantage6.common.globals import Ports
11
+
12
+
13
+ def create_and_run_task(client: Client, task_args: dict, algo_name: str = "algorithm"):
14
+ """
15
+ Create and run a task using the provided client and task arguments.
16
+
17
+ Parameters
18
+ ----------
19
+ client: Client
20
+ The client instance to use for creating and running the task.
21
+ task_args: dict
22
+ The arguments to pass to the task creation method.
23
+ algo_name: str, optional
24
+ The name of the algorithm for logging purposes. Default is "algorithm".
25
+
26
+ Raises
27
+ ------
28
+ AssertionError: If the task fails.
29
+ """
30
+ task = client.task.create(**task_args)
31
+ task_id = task["id"]
32
+ client.wait_for_results(task_id)
33
+
34
+ try:
35
+ # check if the task has failed
36
+ assert not task_status.has_task_failed(client.task.get(task_id)["status"])
37
+
38
+ logging.info(f"Task for {algo_name} completed successfully.")
39
+
40
+ except AssertionError:
41
+ error(
42
+ f"Task for {algo_name} failed. Check the log file of the task "
43
+ f"{task_id} for more information."
44
+ )
45
+ exit(1)
46
+
47
+
48
+ def run_test(custom_args: dict | None = None):
49
+ """
50
+ Run a test by creating and running tasks using the provided arguments.
51
+
52
+ Parameters
53
+ ----------
54
+ custom_args: dict, optional
55
+ The arguments to pass to the task creation method. If not provided,
56
+ the arguments from the `arguments` module will be used.
57
+ """
58
+ # Create a client and authenticate
59
+ client = Client("http://localhost", Ports.DEV_SERVER.value, "/api")
60
+ try:
61
+ client.authenticate("dev_admin", "password")
62
+ except ConnectionError:
63
+ error(
64
+ "Could not connect to the server. Please check if a dev network is running."
65
+ )
66
+ exit(1)
67
+
68
+ # if custom arguments are provided, use them for running the task
69
+ if custom_args:
70
+ create_and_run_task(client, custom_args)
71
+
72
+ else:
73
+ # Run the task for each algorithm in the arguments file
74
+ for algo in arguments.args:
75
+ logging.info(f"Running task for {algo}")
76
+
77
+ task_args = arguments.args[algo]
78
+ create_and_run_task(client, task_args, algo)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ # check if arguments are provided
83
+ if len(sys.argv) > 1:
84
+ input_string = sys.argv[1].replace("'", '"')
85
+ json_input = json.loads(input_string)
86
+ else:
87
+ json_input = None
88
+
89
+ run_test(json_input)
@@ -0,0 +1,143 @@
1
+ import click
2
+ import json
3
+ import subprocess
4
+ import sys
5
+
6
+ from pathlib import Path
7
+
8
+ from rich.console import Console
9
+
10
+ from vantage6.cli.dev.create import create_demo_network
11
+ from vantage6.cli.dev.remove import remove_demo_network
12
+ from vantage6.cli.dev.start import start_demo_network
13
+ from vantage6.cli.dev.stop import stop_demo_network
14
+ from vantage6.common.globals import Ports
15
+
16
+ TEST_FILE_PATH = Path(__file__).parent / "algo_test_scripts" / "algo_test_script.py"
17
+
18
+
19
+ @click.command()
20
+ @click.option(
21
+ "--script",
22
+ type=click.Path(),
23
+ default=TEST_FILE_PATH,
24
+ help="Path of the script to test the algorithm. If a script is not provided, the default script is used.",
25
+ )
26
+ @click.option(
27
+ "--task-arguments",
28
+ type=str,
29
+ default=None,
30
+ help="Arguments to be provided to Task.create function. If --script is provided, this should not be set.",
31
+ )
32
+ @click.option(
33
+ "--create-dev-network",
34
+ is_flag=True,
35
+ help="Create a new dev network to run the test",
36
+ )
37
+ @click.option(
38
+ "--start-dev-network",
39
+ is_flag=True,
40
+ help="Start a dev network to run the test",
41
+ )
42
+ @click.option(
43
+ "-n", "--name", default=None, type=str, help="Name for your development setup"
44
+ )
45
+ @click.option(
46
+ "--server-url",
47
+ type=str,
48
+ default="http://host.docker.internal",
49
+ help="Server URL to point to. If you are using Docker Desktop, "
50
+ "the default http://host.docker.internal should not be changed.",
51
+ )
52
+ @click.option(
53
+ "-i", "--image", type=str, default=None, help="Server Docker image to use"
54
+ )
55
+ @click.option(
56
+ "--keep",
57
+ type=bool,
58
+ default=False,
59
+ help="Keep the dev network after finishing the test",
60
+ )
61
+ @click.option(
62
+ "--add-dataset",
63
+ type=(str, click.Path()),
64
+ default=[],
65
+ multiple=True,
66
+ help="Add a dataset to the nodes. The first argument is the label of the database, "
67
+ "the second is the path to the dataset file.",
68
+ )
69
+ @click.pass_context
70
+ def cli_test_client_script(
71
+ click_ctx: click.Context,
72
+ script: Path,
73
+ task_arguments: str,
74
+ name: str,
75
+ server_url: str,
76
+ create_dev_network: bool,
77
+ start_dev_network: bool,
78
+ image: str,
79
+ keep: bool,
80
+ add_dataset: list[tuple[str, Path]] = (),
81
+ ) -> int:
82
+ """
83
+ Run a script for testing an algorithm on a dev network.
84
+ The path to the script must be provided as an argument.
85
+ """
86
+ if not (script or task_arguments):
87
+ raise click.UsageError("--script or --task-arguments must be set.")
88
+ elif script != TEST_FILE_PATH and task_arguments:
89
+ raise click.UsageError("--script and --task-arguments cannot be set together.")
90
+
91
+ # Check if the task_arguments is a valid JSON string
92
+ if task_arguments:
93
+ try:
94
+ json.loads(task_arguments.replace("'", '"'))
95
+ except json.JSONDecodeError:
96
+ raise click.UsageError("task-arguments must be a valid JSON string.")
97
+
98
+ # create the network
99
+ if create_dev_network:
100
+ click_ctx.invoke(
101
+ create_demo_network,
102
+ name=name,
103
+ num_nodes=3,
104
+ server_url=server_url,
105
+ server_port=Ports.DEV_SERVER.value,
106
+ image=image,
107
+ extra_server_config=None,
108
+ extra_node_config=None,
109
+ add_dataset=add_dataset,
110
+ )
111
+
112
+ # start the server and nodes
113
+ if create_dev_network or start_dev_network:
114
+ click_ctx.invoke(
115
+ start_demo_network,
116
+ name=name,
117
+ server_image=image,
118
+ node_image=image,
119
+ )
120
+
121
+ # run the test script and get the result
122
+ if not task_arguments:
123
+ subprocess_args = ["python", script]
124
+ else:
125
+ subprocess_args = ["python", script, task_arguments]
126
+
127
+ result = subprocess.run(subprocess_args, stdout=sys.stdout, stderr=sys.stderr)
128
+
129
+ # check the exit code. If the test passed, it should be 0
130
+ if result.returncode == 0:
131
+ msg = ":heavy_check_mark: [green]Test passed[/green]"
132
+ else:
133
+ msg = ":x: [red]Test failed[/red]"
134
+
135
+ console = Console()
136
+ console.print(msg)
137
+
138
+ # clean up the test resources
139
+ if not keep:
140
+ click_ctx.invoke(stop_demo_network, name=name)
141
+ click_ctx.invoke(remove_demo_network, name=name)
142
+
143
+ return result.returncode
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vantage6
3
- Version: 4.10.1rc1
3
+ Version: 4.11.0rc2
4
4
  Summary: vantage6 command line interface
5
5
  Home-page: https://github.com/vantage6/vantage6
6
6
  Requires-Python: >=3.10
@@ -16,8 +16,8 @@ Requires-Dist: questionary==1.10.0
16
16
  Requires-Dist: rich==13.5.2
17
17
  Requires-Dist: schema==0.7.5
18
18
  Requires-Dist: SQLAlchemy==1.4.46
19
- Requires-Dist: vantage6-common==4.10.1rc1
20
- Requires-Dist: vantage6-client==4.10.1rc1
19
+ Requires-Dist: vantage6-common==4.11.0rc2
20
+ Requires-Dist: vantage6-client==4.11.0rc2
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: coverage==6.4.4; extra == "dev"
23
23
  Requires-Dist: black; extra == "dev"
@@ -1,15 +1,16 @@
1
1
  tests_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tests_cli/test_client_script.py,sha256=pJiCk0Yu5xyAcj_FzdUCa4av0dKKrXvpr_Ybz9fW0ew,647
2
3
  tests_cli/test_example.py,sha256=0fw_v-lgZEacshWSDwLNyLMA1_xc48bKUGM3ll-n1L0,146
3
4
  tests_cli/test_node_cli.py,sha256=XAKRS1-SXdeBRz3ob25q467soCIB178pKVz8MJVZbCQ,17534
4
5
  tests_cli/test_server_cli.py,sha256=DLcOWM0ZCRTs-XMNsNKCOEELUMh22Ayow1FvAFLI0aA,5879
5
6
  tests_cli/test_wizard.py,sha256=NIj59eiCBuVNJXwhofrWLmLIKAsD45gSOzqOFWLmWhY,4916
6
- vantage6/cli/__build__,sha256=a4ayc_80_OGda4BO_1o_V0etpOqiLx1JwB5S3beHW0s,1
7
+ vantage6/cli/__build__,sha256=1HNeOiZeFu7gP1lxi5tdAwGcB9i2xR-Q2jpmbuwTqzU,1
7
8
  vantage6/cli/__init__.py,sha256=ZXbeQ_-g2-M4XYteWZkoO5lMFYhqjm5doQgGy1fq8i0,125
8
- vantage6/cli/_version.py,sha256=nMTSumCNc9_aCqofYJOR0p4Siz14Bqne4oF2KVFvzJk,701
9
- vantage6/cli/cli.py,sha256=NHpiPcljADaVzwGYrG4FWVMfbmDRguSZWBeGxcdQxDo,6184
10
- vantage6/cli/configuration_manager.py,sha256=QuR8Lvgjfp36aLnMV3PdbjddVJHOCvOv2Vci1bYHDHY,3642
9
+ vantage6/cli/_version.py,sha256=7g1VYEN766Mf9wGlm8bPgQO8QzE0d-l9gt02DbZwjWE,701
10
+ vantage6/cli/cli.py,sha256=L_t0sHUwVkEumkYg6MsXm_sdAEWEjiz5DkUa51ihGtU,6318
11
+ vantage6/cli/configuration_manager.py,sha256=YqObxbV4jl3K_0GiJcE0V7r2pFE2nETLEgP3yieYZaU,3695
11
12
  vantage6/cli/configuration_wizard.py,sha256=ifqvrVqHkxoM0ZVUVIwlYXFByzAbuVlahNjmwFGLVRU,20874
12
- vantage6/cli/globals.py,sha256=8AWI55FBbumVQTuI1bJzKp5hiRWtiwsVgTTKqWgRBes,1616
13
+ vantage6/cli/globals.py,sha256=TBvO_ywYXRIyX1GlKUND_s6s9dPAEpdlh7p8i6aKSYA,1953
13
14
  vantage6/cli/utils.py,sha256=Jfr6IeHMQDk_wU5X7rJ1dRY118dhVVX8PwzwMYMv9Vw,2481
14
15
  vantage6/cli/algorithm/create.py,sha256=kRT1BlBcb0fDaB2Q988WxtA6EyAZmOW5QoU2uhbwBIo,2075
15
16
  vantage6/cli/algorithm/update.py,sha256=WwAfTnq0kTOgePUsBzGoo1AJQqGMn82E9Bjk1wf61CQ,1338
@@ -27,8 +28,8 @@ vantage6/cli/context/__init__.py,sha256=e8rfY2tCyu6_SLQ-rbVzEHkDtmbnGCZRHFN_HH-2
27
28
  vantage6/cli/context/algorithm_store.py,sha256=RimxNcoqfWeu2WQede6wsOu1rx-azzXIPVkCDqVJLWs,3944
28
29
  vantage6/cli/context/base_server.py,sha256=paKSzNrKWD-J6eakHAtGELk2cD05A8NqoCAuQfF7c2s,2972
29
30
  vantage6/cli/context/node.py,sha256=4R7X__u_pyZVvRSNO3ojTnVQFTWhuIemoHcGP6hsD5I,7444
30
- vantage6/cli/context/server.py,sha256=vBGJWNsJoVcIryX5OLiWnFklNRcjOVkhqm2U5tqW5b0,3946
31
- vantage6/cli/dev/create.py,sha256=6LiK0MUZjZK_W932WnlMMVeCqX1L11F87Rk1UkU6O-4,19347
31
+ vantage6/cli/context/server.py,sha256=4BiccAakyXviGiV1SisZ0w0-0hrKAmCGVneZCRsSne8,4563
32
+ vantage6/cli/dev/create.py,sha256=ZN4BfExJVypl28ZKQEua0PIXpQCfcP8AC3RUhp1Cj-I,21280
32
33
  vantage6/cli/dev/remove.py,sha256=R_OU_LXLDCnoD-2xnegg4lh0B3t8EgpqzDqueLx16io,3730
33
34
  vantage6/cli/dev/start.py,sha256=fUMoPAEpmXoDAJidAmjIziaHZX1yqEErcrTKEXqPik8,4471
34
35
  vantage6/cli/dev/stop.py,sha256=gPy87r8T3nqe7RFJjlYE9Bns8N3RiPPcdzNIFCoqGRY,1430
@@ -47,6 +48,7 @@ vantage6/cli/node/start.py,sha256=mV4vDBBtFcxva4MJdg5ymsstefDM4bV4pmhSsVX1o4k,12
47
48
  vantage6/cli/node/stop.py,sha256=-jw0DChIoUScVVwY4SBi_AvmdXT0XlMsGVuO9_vrpJI,4085
48
49
  vantage6/cli/node/version.py,sha256=X921xyIvIPYObPac2Si5msZ2tay5ySidnPWmGj1ilZw,1959
49
50
  vantage6/cli/node/common/__init__.py,sha256=ziGS3skkX6Kf4uvFqe22kdFbSck7mubNK5a-KgDAxX8,3467
51
+ vantage6/cli/prometheus/monitoring_manager.py,sha256=fbzzUkXKogoHgfR-pKn7DASMBfXZnio8ft77VMBSjDE,4889
50
52
  vantage6/cli/rabbitmq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
53
  vantage6/cli/rabbitmq/definitions.py,sha256=CcS9jG7ZGB6LjzHQqZ2FliDurPItUvNSjHrOYptORZg,637
52
54
  vantage6/cli/rabbitmq/queue_manager.py,sha256=KGDGHy4NBN8O9xhjzfI7mh65i9lOQIqQwrOFqvGFdHI,7545
@@ -58,19 +60,22 @@ vantage6/cli/server/list.py,sha256=_kVBe7TrtUfT2ZLWPkVvKj4xx5AvS17e99Bwp0ajc_E,4
58
60
  vantage6/cli/server/new.py,sha256=JnUMMD0UUSCOdO0K7v19l5HOV7vnMd9D38WUoEJ2F00,2094
59
61
  vantage6/cli/server/remove.py,sha256=6tfKfVa5dYnZAKQYo_VlGZTuiugi7sh2F3U2cZ7mCmQ,1627
60
62
  vantage6/cli/server/shell.py,sha256=7cSdBOwvloWyCz3RXHaFZUnxgNRV4gZUDmIFecV8Hks,1589
61
- vantage6/cli/server/start.py,sha256=mPfsfkihJWf8bObNoln4YuBwOCVx3EstXtcNft8Pigo,7787
62
- vantage6/cli/server/stop.py,sha256=DY3r9VsUk_r3cqIm1iL-U-kstLVb9pZsiGDSZyAMrKA,4147
63
+ vantage6/cli/server/start.py,sha256=u3wWHlS7FjAkOHndCrebC6uCPuvRUQFci7dSlGDYMTs,9614
64
+ vantage6/cli/server/stop.py,sha256=CP8WtMLkxRhKRr6PnATkCzjJoSVzy9XR9B1ETWtarmU,4422
63
65
  vantage6/cli/server/version.py,sha256=aXAztHEky_F2jPbfPdHPfsAY7rdTurl0_3S6bL94_QQ,1318
64
66
  vantage6/cli/server/common/__init__.py,sha256=htv0mFYa4GhIHdzA2xqUUgKhHcMh09UQERlIjIgrwOM,2062
65
67
  vantage6/cli/template/algo_store_config.j2,sha256=XR-ly-47p6egH8lVh4lZZDh3YSV4kFnkZprdsfSkS2Y,552
66
- vantage6/cli/template/node_config.j2,sha256=XHJm5x5KEwuBAZERzWzzVKJxcw7Px5k-LYSMET_8dqU,743
68
+ vantage6/cli/template/node_config.j2,sha256=1uK21oLan2r48-v_HyIW2TeO5bR9ER_Wy0PICHQ-xXo,781
67
69
  vantage6/cli/template/server_config.j2,sha256=3gEPY8YlqUMAQEgfR7a1HTU8WaCRhVzTS-IwPhsU1Gg,802
68
70
  vantage6/cli/template/server_import_config.j2,sha256=9WT2XeG9-ADoYLb4ahXhof3i9Fcvg0oqwNPyFwLJpvc,1827
71
+ vantage6/cli/test/client_script.py,sha256=yKZECAMox8sgnHZQyxWTBxILnqQIAbf6v884wgaMkuE,4195
69
72
  vantage6/cli/test/feature_tester.py,sha256=M8hvebupPwYjcBZoUB8GB3qb8G1-d3ipNzRMc_3-Z8E,2761
70
73
  vantage6/cli/test/integration_test.py,sha256=yQVG72XKDNH_eOPTsf3pb65FCBwJzMxn5VNfUGemJBM,3808
74
+ vantage6/cli/test/algo_test_scripts/algo_test_arguments.py,sha256=PfJUwXXbOUvs8W2i3XqIoNNkqvnXmRTv19kKcpg9ouk,888
75
+ vantage6/cli/test/algo_test_scripts/algo_test_script.py,sha256=F1meN4vI4Nz7_RXD_-xmclfw3M5MzBKGINgzbZmDaN8,2652
71
76
  vantage6/cli/test/common/diagnostic_runner.py,sha256=x_4ikihgoSTKI914pqlgVziBSg5LpV6MheO6O_GBCeA,6657
72
- vantage6-4.10.1rc1.dist-info/METADATA,sha256=SOdtW3t66t8QFaPpgkZet9CQYIboQxydipcaDqQDpy4,10890
73
- vantage6-4.10.1rc1.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
74
- vantage6-4.10.1rc1.dist-info/entry_points.txt,sha256=YFBvwjxoeAGxYyPC-YevEgOBBYRGaXkS6jiOGGCLNy0,157
75
- vantage6-4.10.1rc1.dist-info/top_level.txt,sha256=CYDIBS8jEfFq5YCs_Fuit54K9-3wdosZppTrsymIoUk,19
76
- vantage6-4.10.1rc1.dist-info/RECORD,,
77
+ vantage6-4.11.0rc2.dist-info/METADATA,sha256=0ATXMr019zRsaUuKNRhin-W_4AUlfbNB3SOT1st4vAg,10890
78
+ vantage6-4.11.0rc2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
79
+ vantage6-4.11.0rc2.dist-info/entry_points.txt,sha256=YFBvwjxoeAGxYyPC-YevEgOBBYRGaXkS6jiOGGCLNy0,157
80
+ vantage6-4.11.0rc2.dist-info/top_level.txt,sha256=CYDIBS8jEfFq5YCs_Fuit54K9-3wdosZppTrsymIoUk,19
81
+ vantage6-4.11.0rc2.dist-info/RECORD,,