vantage6 4.1.3__py3-none-any.whl → 4.2.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.
- tests_cli/test_node_cli.py +1 -1
- tests_cli/test_server_cli.py +2 -13
- vantage6/cli/__build__ +1 -1
- vantage6/cli/_version.py +1 -1
- vantage6/cli/cli.py +18 -0
- vantage6/cli/dev/create.py +68 -14
- vantage6/cli/dev/remove.py +6 -7
- vantage6/cli/globals.py +3 -0
- vantage6/cli/node/start.py +116 -74
- vantage6/cli/server/remove.py +41 -0
- vantage6/cli/server/stop.py +0 -51
- vantage6/cli/template/node_config.j2 +1 -0
- vantage6/cli/template/server_config.j2 +2 -1
- vantage6/cli/test/common/diagnostic_runner.py +200 -0
- vantage6/cli/test/feature_tester.py +54 -0
- vantage6/cli/test/integration_test.py +82 -0
- {vantage6-4.1.3.dist-info → vantage6-4.2.0rc2.dist-info}/METADATA +5 -4
- {vantage6-4.1.3.dist-info → vantage6-4.2.0rc2.dist-info}/RECORD +21 -17
- {vantage6-4.1.3.dist-info → vantage6-4.2.0rc2.dist-info}/WHEEL +0 -0
- {vantage6-4.1.3.dist-info → vantage6-4.2.0rc2.dist-info}/entry_points.txt +0 -0
- {vantage6-4.1.3.dist-info → vantage6-4.2.0rc2.dist-info}/top_level.txt +0 -0
tests_cli/test_node_cli.py
CHANGED
tests_cli/test_server_cli.py
CHANGED
|
@@ -126,23 +126,14 @@ class ServerCLITest(unittest.TestCase):
|
|
|
126
126
|
self.assertIsNone(result.exception)
|
|
127
127
|
self.assertEqual(result.exit_code, 0)
|
|
128
128
|
|
|
129
|
-
@patch("vantage6.cli.server.stop.ServerContext")
|
|
130
129
|
@patch("vantage6.cli.server.stop.docker.from_env")
|
|
131
|
-
|
|
132
|
-
def test_stop(self, docker_check, containers, context):
|
|
130
|
+
def test_stop(self, containers):
|
|
133
131
|
"""Stop server without errors."""
|
|
134
132
|
|
|
135
133
|
container1 = MagicMock()
|
|
136
134
|
container1.name = f"{APPNAME}-iknl-system-server"
|
|
137
135
|
containers.containers.list.return_value = [container1]
|
|
138
136
|
|
|
139
|
-
ctx = MagicMock(
|
|
140
|
-
config={
|
|
141
|
-
'rabbitmq_uri': None
|
|
142
|
-
}
|
|
143
|
-
)
|
|
144
|
-
context.return_value = ctx
|
|
145
|
-
|
|
146
137
|
runner = CliRunner()
|
|
147
138
|
result = runner.invoke(cli_server_stop, ["--name", "iknl"])
|
|
148
139
|
|
|
@@ -151,9 +142,7 @@ class ServerCLITest(unittest.TestCase):
|
|
|
151
142
|
|
|
152
143
|
@patch("vantage6.cli.server.attach.time.sleep")
|
|
153
144
|
@patch("docker.DockerClient.containers")
|
|
154
|
-
|
|
155
|
-
return_value=True)
|
|
156
|
-
def test_attach(self, docker_check, containers, sleep):
|
|
145
|
+
def test_attach(self, containers, sleep):
|
|
157
146
|
"""Attach log to the console without errors."""
|
|
158
147
|
container1 = MagicMock()
|
|
159
148
|
container1.name = f"{APPNAME}-iknl-system-server"
|
vantage6/cli/__build__
CHANGED
|
@@ -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
|
+
version_info = (4, 2, 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
|
@@ -5,6 +5,7 @@ from vantage6.cli.server.files import cli_server_files
|
|
|
5
5
|
from vantage6.cli.server.import_ import cli_server_import
|
|
6
6
|
from vantage6.cli.server.list import cli_server_configuration_list
|
|
7
7
|
from vantage6.cli.server.new import cli_server_new
|
|
8
|
+
from vantage6.cli.server.remove import cli_server_remove
|
|
8
9
|
from vantage6.cli.server.shell import cli_server_shell
|
|
9
10
|
from vantage6.cli.server.start import cli_server_start
|
|
10
11
|
from vantage6.cli.server.stop import cli_server_stop
|
|
@@ -26,6 +27,8 @@ from vantage6.cli.dev.start import start_demo_network
|
|
|
26
27
|
from vantage6.cli.dev.stop import stop_demo_network
|
|
27
28
|
from vantage6.cli.algorithm.create import cli_algorithm_create
|
|
28
29
|
from vantage6.cli.algorithm.update import cli_algorithm_update
|
|
30
|
+
from vantage6.cli.test.feature_tester import cli_test_features
|
|
31
|
+
from vantage6.cli.test.integration_test import cli_test_integration
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
# Define the server group
|
|
@@ -42,6 +45,7 @@ cli_server.add_command(cli_server_files, name='files')
|
|
|
42
45
|
cli_server.add_command(cli_server_import, name='import')
|
|
43
46
|
cli_server.add_command(cli_server_configuration_list, name='list')
|
|
44
47
|
cli_server.add_command(cli_server_new, name='new')
|
|
48
|
+
cli_server.add_command(cli_server_remove, name='remove')
|
|
45
49
|
cli_server.add_command(cli_server_shell, name='shell')
|
|
46
50
|
cli_server.add_command(cli_server_start, name='start')
|
|
47
51
|
cli_server.add_command(cli_server_stop, name='stop')
|
|
@@ -100,6 +104,19 @@ cli_algorithm.add_command(cli_algorithm_create, name="create")
|
|
|
100
104
|
cli_algorithm.add_command(cli_algorithm_update, name="update")
|
|
101
105
|
|
|
102
106
|
|
|
107
|
+
# Define the test group
|
|
108
|
+
@click.group(name="test")
|
|
109
|
+
def cli_test() -> None:
|
|
110
|
+
"""
|
|
111
|
+
Execute tests on your vantage6 infrastructure.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Define the commands for the test group
|
|
116
|
+
cli_test.add_command(cli_test_features, name="feature-test")
|
|
117
|
+
cli_test.add_command(cli_test_integration, name="integration-test")
|
|
118
|
+
|
|
119
|
+
|
|
103
120
|
# Define the overall group
|
|
104
121
|
@click.group(name='cli')
|
|
105
122
|
def cli_complete() -> None:
|
|
@@ -116,3 +133,4 @@ cli_complete.add_command(cli_node)
|
|
|
116
133
|
cli_complete.add_command(cli_server)
|
|
117
134
|
cli_complete.add_command(cli_dev)
|
|
118
135
|
cli_complete.add_command(cli_algorithm)
|
|
136
|
+
cli_complete.add_command(cli_test)
|
vantage6/cli/dev/create.py
CHANGED
|
@@ -69,7 +69,8 @@ def create_node_config_file(server_url: str, port: int, config: dict,
|
|
|
69
69
|
port : int
|
|
70
70
|
Port of the dummy server.
|
|
71
71
|
config : dict
|
|
72
|
-
Configuration dictionary containing org_id, api_key
|
|
72
|
+
Configuration dictionary containing org_id, api_key, node name and
|
|
73
|
+
additional user_defined_config.
|
|
73
74
|
server_name : str
|
|
74
75
|
Configuration name of the dummy server.
|
|
75
76
|
"""
|
|
@@ -103,7 +104,8 @@ def create_node_config_file(server_url: str, port: int, config: dict,
|
|
|
103
104
|
},
|
|
104
105
|
"port": port,
|
|
105
106
|
"server_url": server_url,
|
|
106
|
-
"task_dir": str(path_to_data_dir)
|
|
107
|
+
"task_dir": str(path_to_data_dir),
|
|
108
|
+
"user_provided_config": config['user_defined_config']
|
|
107
109
|
})
|
|
108
110
|
|
|
109
111
|
try:
|
|
@@ -117,9 +119,31 @@ def create_node_config_file(server_url: str, port: int, config: dict,
|
|
|
117
119
|
f"{Style.RESET_ALL}")
|
|
118
120
|
|
|
119
121
|
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
def _read_extra_config_file(extra_config_file: Path | None) -> str:
|
|
123
|
+
"""Reads extra configuration file.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
extra_config_file : Path | None
|
|
128
|
+
Path to file with additional configuration.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
str
|
|
133
|
+
Extra configuration file content
|
|
134
|
+
"""
|
|
135
|
+
if extra_config_file:
|
|
136
|
+
# read the YAML file as string, so it can be appended to the
|
|
137
|
+
# configuration easily
|
|
138
|
+
with open(extra_config_file, 'r', encoding='utf-8') as f:
|
|
139
|
+
return f.read()
|
|
140
|
+
return ''
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def generate_node_configs(
|
|
144
|
+
num_nodes: int, server_url: str, port: int, server_name: str,
|
|
145
|
+
extra_node_config: Path | None
|
|
146
|
+
) -> list[dict]:
|
|
123
147
|
"""Generates ``num_nodes`` node configuration files.
|
|
124
148
|
|
|
125
149
|
Parameters
|
|
@@ -132,6 +156,8 @@ def generate_node_configs(num_nodes: int, server_url: str, port: int,
|
|
|
132
156
|
Port of the dummy server.
|
|
133
157
|
server_name : str
|
|
134
158
|
Configuration name of the dummy server.
|
|
159
|
+
extra_node_config : Path | None
|
|
160
|
+
Path to file with additional node configuration.
|
|
135
161
|
|
|
136
162
|
Returns
|
|
137
163
|
-------
|
|
@@ -139,11 +165,13 @@ def generate_node_configs(num_nodes: int, server_url: str, port: int,
|
|
|
139
165
|
List of dictionaries containing node configurations.
|
|
140
166
|
"""
|
|
141
167
|
configs = []
|
|
168
|
+
extra_config = _read_extra_config_file(extra_node_config)
|
|
142
169
|
for i in range(num_nodes):
|
|
143
170
|
config = {
|
|
144
171
|
'org_id': i + 1,
|
|
145
172
|
'api_key': generate_apikey(),
|
|
146
|
-
'node_name': f"{server_name}_node_{i + 1}"
|
|
173
|
+
'node_name': f"{server_name}_node_{i + 1}",
|
|
174
|
+
"user_defined_config": extra_config
|
|
147
175
|
}
|
|
148
176
|
create_node_config_file(server_url, port, config, server_name)
|
|
149
177
|
configs.append(config)
|
|
@@ -211,7 +239,9 @@ def create_vserver_import_config(node_configs: list[dict], server_name: str) \
|
|
|
211
239
|
return full_path
|
|
212
240
|
|
|
213
241
|
|
|
214
|
-
def create_vserver_config(
|
|
242
|
+
def create_vserver_config(
|
|
243
|
+
server_name: str, port: int, extra_config_file: Path
|
|
244
|
+
) -> Path:
|
|
215
245
|
"""Creates server configuration file (YAML).
|
|
216
246
|
|
|
217
247
|
Parameters
|
|
@@ -220,6 +250,8 @@ def create_vserver_config(server_name: str, port: int) -> Path:
|
|
|
220
250
|
Server name.
|
|
221
251
|
port : int
|
|
222
252
|
Server port.
|
|
253
|
+
extra_config_file : Path
|
|
254
|
+
Path to file with additional server configuration.
|
|
223
255
|
|
|
224
256
|
Returns
|
|
225
257
|
-------
|
|
@@ -229,10 +261,14 @@ def create_vserver_config(server_name: str, port: int) -> Path:
|
|
|
229
261
|
environment = Environment(
|
|
230
262
|
loader=FileSystemLoader(PACKAGE_FOLDER / APPNAME / "cli" / "template"),
|
|
231
263
|
trim_blocks=True, lstrip_blocks=True, autoescape=True)
|
|
264
|
+
|
|
265
|
+
extra_config = _read_extra_config_file(extra_config_file)
|
|
266
|
+
|
|
232
267
|
template = environment.get_template("server_config.j2")
|
|
233
268
|
server_config = template.render(
|
|
234
269
|
port=port,
|
|
235
|
-
jwt_secret_key=generate_apikey()
|
|
270
|
+
jwt_secret_key=generate_apikey(),
|
|
271
|
+
user_provided_config=extra_config
|
|
236
272
|
)
|
|
237
273
|
folders = ServerContext.instance_folders(
|
|
238
274
|
instance_type='server', instance_name=server_name,
|
|
@@ -257,8 +293,10 @@ def create_vserver_config(server_name: str, port: int) -> Path:
|
|
|
257
293
|
return full_path
|
|
258
294
|
|
|
259
295
|
|
|
260
|
-
def demo_network(
|
|
261
|
-
|
|
296
|
+
def demo_network(
|
|
297
|
+
num_nodes: int, server_url: str, server_port: int, server_name: str,
|
|
298
|
+
extra_server_config: Path, extra_node_config: Path
|
|
299
|
+
) -> tuple[list[dict], Path, Path]:
|
|
262
300
|
"""Generates the demo network.
|
|
263
301
|
|
|
264
302
|
Parameters
|
|
@@ -271,6 +309,10 @@ def demo_network(num_nodes: int, server_url: str, server_port: int,
|
|
|
271
309
|
Port of the dummy server.
|
|
272
310
|
server_name : str
|
|
273
311
|
Server name.
|
|
312
|
+
extra_server_config : Path
|
|
313
|
+
Path to file with additional server configuration.
|
|
314
|
+
extra_node_config : Path
|
|
315
|
+
Path to file with additional node configuration.
|
|
274
316
|
|
|
275
317
|
Returns
|
|
276
318
|
-------
|
|
@@ -278,10 +320,11 @@ def demo_network(num_nodes: int, server_url: str, server_port: int,
|
|
|
278
320
|
Tuple containing node, server import and server configurations.
|
|
279
321
|
"""
|
|
280
322
|
node_configs = generate_node_configs(num_nodes, server_url, server_port,
|
|
281
|
-
server_name)
|
|
323
|
+
server_name, extra_node_config)
|
|
282
324
|
server_import_config = create_vserver_import_config(node_configs,
|
|
283
325
|
server_name)
|
|
284
|
-
server_config = create_vserver_config(server_name, server_port
|
|
326
|
+
server_config = create_vserver_config(server_name, server_port,
|
|
327
|
+
extra_server_config)
|
|
285
328
|
return (node_configs, server_import_config, server_config)
|
|
286
329
|
|
|
287
330
|
|
|
@@ -298,10 +341,18 @@ def demo_network(num_nodes: int, server_url: str, server_port: int,
|
|
|
298
341
|
@click.option('-i', '--image', type=str, default=None,
|
|
299
342
|
help='Server docker image to use when setting up resources for '
|
|
300
343
|
'the development server')
|
|
344
|
+
@click.option('--extra-server-config', type=click.Path(exists=True),
|
|
345
|
+
default=None, help='YAML File with additional server '
|
|
346
|
+
'configuration. This will be appended to the server '
|
|
347
|
+
'configuration file')
|
|
348
|
+
@click.option('--extra-node-config', type=click.Path('rb'), default=None,
|
|
349
|
+
help='YAML File with additional node configuration. This will be'
|
|
350
|
+
' appended to each of the node configuration files')
|
|
301
351
|
@click.pass_context
|
|
302
352
|
def create_demo_network(
|
|
303
353
|
click_ctx: click.Context, name: str, num_nodes: int, server_url: str,
|
|
304
|
-
server_port: int, image: str = None
|
|
354
|
+
server_port: int, image: str = None, extra_server_config: Path = None,
|
|
355
|
+
extra_node_config: Path = None
|
|
305
356
|
) -> dict:
|
|
306
357
|
"""Creates a demo network.
|
|
307
358
|
|
|
@@ -312,7 +363,10 @@ def create_demo_network(
|
|
|
312
363
|
"""
|
|
313
364
|
server_name = prompt_config_name(name)
|
|
314
365
|
if not ServerContext.config_exists(server_name):
|
|
315
|
-
demo = demo_network(
|
|
366
|
+
demo = demo_network(
|
|
367
|
+
num_nodes, server_url, server_port, server_name,
|
|
368
|
+
extra_server_config, extra_node_config
|
|
369
|
+
)
|
|
316
370
|
info(f"Created {Fore.GREEN}{len(demo[0])}{Style.RESET_ALL} node "
|
|
317
371
|
f"configuration(s), attaching them to {Fore.GREEN}{server_name}"
|
|
318
372
|
f"{Style.RESET_ALL}.")
|
vantage6/cli/dev/remove.py
CHANGED
|
@@ -8,15 +8,16 @@ import click
|
|
|
8
8
|
from vantage6.common import info
|
|
9
9
|
from vantage6.cli.context import ServerContext, NodeContext
|
|
10
10
|
from vantage6.cli.server.common import click_insert_context
|
|
11
|
-
from vantage6.cli.server.
|
|
11
|
+
from vantage6.cli.server.remove import cli_server_remove
|
|
12
12
|
from vantage6.cli.utils import remove_file
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@click.command()
|
|
16
16
|
@click_insert_context
|
|
17
|
-
@click.
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
@click.pass_context
|
|
18
|
+
def remove_demo_network(
|
|
19
|
+
click_ctx: click.Context, ctx: ServerContext
|
|
20
|
+
) -> None:
|
|
20
21
|
""" Remove all related demo network files and folders.
|
|
21
22
|
|
|
22
23
|
Select a server configuration to remove that server and the nodes attached
|
|
@@ -26,7 +27,7 @@ def remove_demo_network(ctx: ServerContext, force: bool) -> None:
|
|
|
26
27
|
# remove the server
|
|
27
28
|
for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
|
|
28
29
|
handler.close()
|
|
29
|
-
|
|
30
|
+
click_ctx.invoke(cli_server_remove, ctx=ctx, force=True)
|
|
30
31
|
|
|
31
32
|
# removing the server import config
|
|
32
33
|
info("Deleting demo import config file")
|
|
@@ -41,8 +42,6 @@ def remove_demo_network(ctx: ServerContext, force: bool) -> None:
|
|
|
41
42
|
server_folder = server_configs['data']
|
|
42
43
|
if server_folder.is_dir():
|
|
43
44
|
rmtree(server_folder)
|
|
44
|
-
# TODO BvB 2023-07-31 can it happen that the server folder is not a
|
|
45
|
-
# directory? What then?
|
|
46
45
|
|
|
47
46
|
# remove the nodes
|
|
48
47
|
configs, _ = NodeContext.available_configurations(system_folders=False)
|
vantage6/cli/globals.py
CHANGED
|
@@ -36,3 +36,6 @@ DEFAULT_UI_PORT = 5001
|
|
|
36
36
|
|
|
37
37
|
# Location of repository to create new algorithm templates from
|
|
38
38
|
ALGORITHM_TEMPLATE_REPO = "gh:vantage6/v6-algorithm-template.git"
|
|
39
|
+
|
|
40
|
+
# image to use for diagnostics in `v6 test` commands
|
|
41
|
+
DIAGNOSTICS_IMAGE = "harbor2.vantage6.ai/algorithms/diagnostic"
|
vantage6/cli/node/start.py
CHANGED
|
@@ -10,10 +10,7 @@ import docker
|
|
|
10
10
|
|
|
11
11
|
from colorama import Fore, Style
|
|
12
12
|
|
|
13
|
-
from vantage6.common import
|
|
14
|
-
warning, error, info, debug,
|
|
15
|
-
get_database_config
|
|
16
|
-
)
|
|
13
|
+
from vantage6.common import warning, error, info, debug, get_database_config
|
|
17
14
|
from vantage6.common.globals import (
|
|
18
15
|
APPNAME,
|
|
19
16
|
DEFAULT_DOCKER_REGISTRY,
|
|
@@ -21,15 +18,13 @@ from vantage6.common.globals import (
|
|
|
21
18
|
DEFAULT_NODE_IMAGE_WO_TAG,
|
|
22
19
|
)
|
|
23
20
|
from vantage6.common.docker.addons import (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
pull_if_newer,
|
|
22
|
+
remove_container_if_exists,
|
|
23
|
+
check_docker_running,
|
|
27
24
|
)
|
|
28
25
|
|
|
29
26
|
from vantage6.cli.context import NodeContext
|
|
30
|
-
from vantage6.cli.globals import
|
|
31
|
-
DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
|
|
32
|
-
)
|
|
27
|
+
from vantage6.cli.globals import DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
|
|
33
28
|
from vantage6.cli.configuration_wizard import (
|
|
34
29
|
configuration_wizard,
|
|
35
30
|
select_configuration_questionaire,
|
|
@@ -41,28 +36,54 @@ from vantage6.cli.node.common import print_log_worker, create_client
|
|
|
41
36
|
|
|
42
37
|
@click.command()
|
|
43
38
|
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
44
|
-
@click.option(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@click.option(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
39
|
+
@click.option(
|
|
40
|
+
"-c", "--config", default=None, help="Path to configuration-file; overrides NAME"
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
"--system",
|
|
44
|
+
"system_folders",
|
|
45
|
+
flag_value=True,
|
|
46
|
+
help="Search for the configuration in the system folders",
|
|
47
|
+
)
|
|
48
|
+
@click.option(
|
|
49
|
+
"--user",
|
|
50
|
+
"system_folders",
|
|
51
|
+
flag_value=False,
|
|
52
|
+
default=N_FOL,
|
|
53
|
+
help="Search for the configuration in the user folders. This is " "the default",
|
|
54
|
+
)
|
|
55
|
+
@click.option("-i", "--image", default=None, help="Node Docker image to use")
|
|
56
|
+
@click.option(
|
|
57
|
+
"--keep/--auto-remove",
|
|
58
|
+
default=False,
|
|
59
|
+
help="Keep node container after finishing. Useful for debugging",
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--force-db-mount",
|
|
63
|
+
is_flag=True,
|
|
64
|
+
help="Always mount node databases; skip the check if they are " "existing files.",
|
|
65
|
+
)
|
|
66
|
+
@click.option(
|
|
67
|
+
"--attach/--detach",
|
|
68
|
+
default=False,
|
|
69
|
+
help="Show node logs on the current console after starting the " "node",
|
|
70
|
+
)
|
|
71
|
+
@click.option(
|
|
72
|
+
"--mount-src",
|
|
73
|
+
default="",
|
|
74
|
+
help="Override vantage6 source code in container with the source"
|
|
75
|
+
" code in this path",
|
|
76
|
+
)
|
|
77
|
+
def cli_node_start(
|
|
78
|
+
name: str,
|
|
79
|
+
config: str,
|
|
80
|
+
system_folders: bool,
|
|
81
|
+
image: str,
|
|
82
|
+
keep: bool,
|
|
83
|
+
mount_src: str,
|
|
84
|
+
attach: bool,
|
|
85
|
+
force_db_mount: bool,
|
|
86
|
+
) -> None:
|
|
66
87
|
"""
|
|
67
88
|
Start the node.
|
|
68
89
|
"""
|
|
@@ -82,8 +103,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
82
103
|
|
|
83
104
|
# check that config exists, if not a questionaire will be invoked
|
|
84
105
|
if not NodeContext.config_exists(name, system_folders):
|
|
85
|
-
warning(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not"
|
|
86
|
-
" exist.")
|
|
106
|
+
warning(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not exist.")
|
|
87
107
|
|
|
88
108
|
if q.confirm("Create this configuration now?").ask():
|
|
89
109
|
configuration_wizard("node", name, system_folders)
|
|
@@ -117,7 +137,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
117
137
|
# Then we check if the image has been specified in the config file, and
|
|
118
138
|
# finally we use the default settings from the package.
|
|
119
139
|
if not image:
|
|
120
|
-
custom_images: dict = ctx.config.get(
|
|
140
|
+
custom_images: dict = ctx.config.get("images")
|
|
121
141
|
if custom_images:
|
|
122
142
|
image = custom_images.get("node")
|
|
123
143
|
else:
|
|
@@ -127,16 +147,19 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
127
147
|
major_minor = None
|
|
128
148
|
try:
|
|
129
149
|
# try to get server version, skip if can't get a connection
|
|
130
|
-
version = client.util.get_server_version(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
major_minor =
|
|
134
|
-
image = (
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
version = client.util.get_server_version(attempts_on_timeout=3)[
|
|
151
|
+
"version"
|
|
152
|
+
]
|
|
153
|
+
major_minor = ".".join(version.split(".")[:2])
|
|
154
|
+
image = (
|
|
155
|
+
f"{DEFAULT_DOCKER_REGISTRY}/"
|
|
156
|
+
f"{DEFAULT_NODE_IMAGE_WO_TAG}"
|
|
157
|
+
f":{major_minor}"
|
|
158
|
+
)
|
|
137
159
|
except Exception:
|
|
138
|
-
warning(
|
|
139
|
-
|
|
160
|
+
warning(
|
|
161
|
+
"Could not determine server version. Using default " "node image"
|
|
162
|
+
)
|
|
140
163
|
|
|
141
164
|
if major_minor and not __version__.startswith(major_minor):
|
|
142
165
|
warning(
|
|
@@ -158,7 +181,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
158
181
|
pull_if_newer(docker.from_env(), image)
|
|
159
182
|
|
|
160
183
|
except Exception as e:
|
|
161
|
-
warning(
|
|
184
|
+
warning(" ... Getting latest node image failed:")
|
|
162
185
|
warning(f" {e}")
|
|
163
186
|
else:
|
|
164
187
|
info(" ... success!")
|
|
@@ -186,7 +209,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
186
209
|
if mount_src:
|
|
187
210
|
# If mount_src is a relative path, docker will consider it a volume.
|
|
188
211
|
mount_src = os.path.abspath(mount_src)
|
|
189
|
-
mounts.append((
|
|
212
|
+
mounts.append(("/vantage6", mount_src))
|
|
190
213
|
|
|
191
214
|
# FIXME: Code duplication: Node.__init__() (vantage6/node/__init__.py)
|
|
192
215
|
# uses a lot of the same logic. Suggest moving this to
|
|
@@ -194,10 +217,10 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
194
217
|
filename = ctx.config.get("encryption", {}).get("private_key")
|
|
195
218
|
# filename may be set to an empty string
|
|
196
219
|
if not filename:
|
|
197
|
-
filename =
|
|
220
|
+
filename = "private_key.pem"
|
|
198
221
|
|
|
199
222
|
# Location may be overridden by the environment
|
|
200
|
-
filename = os.environ.get(
|
|
223
|
+
filename = os.environ.get("PRIVATE_KEY", filename)
|
|
201
224
|
|
|
202
225
|
# If ctx.get_data_file() receives an absolute path, it is returned as-is
|
|
203
226
|
fullpath = Path(ctx.get_data_file(filename))
|
|
@@ -205,8 +228,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
205
228
|
if Path(fullpath).exists():
|
|
206
229
|
mounts.append(("/mnt/private_key.pem", str(fullpath)))
|
|
207
230
|
else:
|
|
208
|
-
warning(f"private key file provided {fullpath}, "
|
|
209
|
-
"but does not exists")
|
|
231
|
+
warning(f"private key file provided {fullpath}, " "but does not exists")
|
|
210
232
|
|
|
211
233
|
# Mount private keys for ssh tunnels
|
|
212
234
|
ssh_tunnels = ctx.config.get("ssh-tunnels", [])
|
|
@@ -214,13 +236,17 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
214
236
|
hostname = ssh_tunnel.get("hostname")
|
|
215
237
|
key_path = ssh_tunnel.get("ssh", {}).get("identity", {}).get("key")
|
|
216
238
|
if not key_path:
|
|
217
|
-
error(
|
|
218
|
-
|
|
239
|
+
error(
|
|
240
|
+
f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
|
|
241
|
+
"key not provided. Continuing to start without this tunnel."
|
|
242
|
+
)
|
|
219
243
|
key_path = Path(key_path)
|
|
220
244
|
if not key_path.exists():
|
|
221
|
-
error(
|
|
222
|
-
|
|
223
|
-
|
|
245
|
+
error(
|
|
246
|
+
f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
|
|
247
|
+
"key does not exist. Continuing to start without this "
|
|
248
|
+
"tunnel."
|
|
249
|
+
)
|
|
224
250
|
|
|
225
251
|
info(f" Mounting private key for {hostname} at {key_path}")
|
|
226
252
|
|
|
@@ -233,20 +259,29 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
233
259
|
env = {
|
|
234
260
|
"DATA_VOLUME_NAME": data_volume.name,
|
|
235
261
|
"VPN_VOLUME_NAME": vpn_volume.name,
|
|
236
|
-
"PRIVATE_KEY": "/mnt/private_key.pem"
|
|
262
|
+
"PRIVATE_KEY": "/mnt/private_key.pem",
|
|
237
263
|
}
|
|
238
264
|
|
|
239
265
|
# only mount the DB if it is a file
|
|
240
266
|
info("Setting up databases")
|
|
241
|
-
db_labels = [db[
|
|
267
|
+
db_labels = [db["label"] for db in ctx.databases]
|
|
242
268
|
for label in db_labels:
|
|
243
269
|
|
|
270
|
+
# check that label contains only valid characters
|
|
271
|
+
if not label.isidentifier():
|
|
272
|
+
error(f"Database label {Fore.RED}{label}{Style.RESET_ALL} contains"
|
|
273
|
+
" invalid characters. Only letters, numbers, and underscores"
|
|
274
|
+
" are allowed, and it cannot start with a number.")
|
|
275
|
+
exit(1)
|
|
276
|
+
|
|
244
277
|
db_config = get_database_config(ctx.databases, label)
|
|
245
|
-
uri = db_config[
|
|
246
|
-
db_type = db_config[
|
|
278
|
+
uri = db_config["uri"]
|
|
279
|
+
db_type = db_config["type"]
|
|
247
280
|
|
|
248
|
-
info(
|
|
249
|
-
|
|
281
|
+
info(
|
|
282
|
+
f" Processing {Fore.GREEN}{db_type}{Style.RESET_ALL} database "
|
|
283
|
+
f"{Fore.GREEN}{label}:{uri}{Style.RESET_ALL}"
|
|
284
|
+
)
|
|
250
285
|
label_capitals = label.upper()
|
|
251
286
|
|
|
252
287
|
try:
|
|
@@ -259,22 +294,24 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
259
294
|
file_based = False
|
|
260
295
|
|
|
261
296
|
if not file_based and not force_db_mount:
|
|
262
|
-
debug(
|
|
263
|
-
env[f
|
|
297
|
+
debug(" - non file-based database added")
|
|
298
|
+
env[f"{label_capitals}_DATABASE_URI"] = uri
|
|
264
299
|
else:
|
|
265
|
-
debug(
|
|
300
|
+
debug(" - file-based database added")
|
|
266
301
|
suffix = Path(uri).suffix
|
|
267
|
-
env[f
|
|
268
|
-
mounts.append((f
|
|
302
|
+
env[f"{label_capitals}_DATABASE_URI"] = f"{label}{suffix}"
|
|
303
|
+
mounts.append((f"/mnt/{label}{suffix}", str(uri)))
|
|
269
304
|
|
|
270
305
|
system_folders_option = "--system" if system_folders else "--user"
|
|
271
|
-
cmd =
|
|
272
|
-
|
|
306
|
+
cmd = (
|
|
307
|
+
f"vnode-local start -c /mnt/config/{name}.yaml -n {name} "
|
|
308
|
+
f" --dockerized {system_folders_option}"
|
|
309
|
+
)
|
|
273
310
|
|
|
274
311
|
info("Running Docker container")
|
|
275
312
|
volumes = []
|
|
276
313
|
for mount in mounts:
|
|
277
|
-
volumes.append(f
|
|
314
|
+
volumes.append(f"{mount[1]}:{mount[0]}")
|
|
278
315
|
|
|
279
316
|
remove_container_if_exists(
|
|
280
317
|
docker_client=docker_client, name=ctx.docker_container_name
|
|
@@ -288,15 +325,15 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
288
325
|
labels={
|
|
289
326
|
f"{APPNAME}-type": "node",
|
|
290
327
|
"system": str(system_folders),
|
|
291
|
-
"name": ctx.config_file_name
|
|
328
|
+
"name": ctx.config_file_name,
|
|
292
329
|
},
|
|
293
330
|
environment=env,
|
|
294
331
|
name=ctx.docker_container_name,
|
|
295
332
|
auto_remove=not keep,
|
|
296
|
-
tty=True
|
|
333
|
+
tty=True,
|
|
297
334
|
)
|
|
298
335
|
|
|
299
|
-
info(
|
|
336
|
+
info("Node container was successfully started!")
|
|
300
337
|
|
|
301
338
|
if attach:
|
|
302
339
|
logs = container.attach(stream=True, logs=True)
|
|
@@ -306,6 +343,11 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
|
306
343
|
time.sleep(1)
|
|
307
344
|
except KeyboardInterrupt:
|
|
308
345
|
info("Closing log file. Keyboard Interrupt.")
|
|
309
|
-
info(
|
|
310
|
-
|
|
346
|
+
info(
|
|
347
|
+
"Note that your node is still running! Shut it down with "
|
|
348
|
+
f"'{Fore.RED}v6 node stop{Style.RESET_ALL}'"
|
|
349
|
+
)
|
|
311
350
|
exit(0)
|
|
351
|
+
else:
|
|
352
|
+
info(f"To see the logs, run: {Fore.GREEN}v6 node attach --name "
|
|
353
|
+
f"{ctx.name}{Style.RESET_ALL}")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import click
|
|
3
|
+
import questionary as q
|
|
4
|
+
|
|
5
|
+
from vantage6.common import info
|
|
6
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
7
|
+
from vantage6.cli.server.common import click_insert_context
|
|
8
|
+
from vantage6.cli.context import ServerContext
|
|
9
|
+
from vantage6.cli.utils import remove_file
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
@click_insert_context
|
|
14
|
+
@click.option('-f', '--force', 'force', flag_value=True)
|
|
15
|
+
def cli_server_remove(ctx: ServerContext, force: bool) -> None:
|
|
16
|
+
"""
|
|
17
|
+
Function to remove a server.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
ctx : ServerContext
|
|
22
|
+
Server context object
|
|
23
|
+
force : bool
|
|
24
|
+
Whether to ask for confirmation before removing or not
|
|
25
|
+
"""
|
|
26
|
+
check_docker_running()
|
|
27
|
+
|
|
28
|
+
if not force:
|
|
29
|
+
if not q.confirm(
|
|
30
|
+
"This server will be deleted permanently including its "
|
|
31
|
+
"configuration. Are you sure?", default=False
|
|
32
|
+
).ask():
|
|
33
|
+
info("Server will not be deleted")
|
|
34
|
+
exit(0)
|
|
35
|
+
|
|
36
|
+
# now remove the folders...
|
|
37
|
+
remove_file(ctx.config_file, 'configuration')
|
|
38
|
+
|
|
39
|
+
for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
|
|
40
|
+
handler.close()
|
|
41
|
+
remove_file(ctx.log_file, 'log')
|
vantage6/cli/server/stop.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import itertools
|
|
2
|
-
|
|
3
1
|
import click
|
|
4
2
|
import questionary as q
|
|
5
3
|
import docker
|
|
@@ -16,8 +14,6 @@ from vantage6.common.globals import APPNAME
|
|
|
16
14
|
from vantage6.cli.rabbitmq import split_rabbitmq_uri
|
|
17
15
|
|
|
18
16
|
from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
|
|
19
|
-
from vantage6.cli.context import ServerContext
|
|
20
|
-
from vantage6.cli.utils import remove_file
|
|
21
17
|
from vantage6.cli.server.common import get_server_context, stop_ui
|
|
22
18
|
|
|
23
19
|
|
|
@@ -109,50 +105,3 @@ def _stop_server_containers(client: DockerClient, container_name: str,
|
|
|
109
105
|
remove_container(rabbit_container, kill=True)
|
|
110
106
|
info(f"Stopped the {Fore.GREEN}{rabbit_container_name}"
|
|
111
107
|
f"{Style.RESET_ALL} container.")
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# TODO this should be refactored into its own module and get a click command
|
|
115
|
-
# attached
|
|
116
|
-
@click.pass_context
|
|
117
|
-
def vserver_remove(
|
|
118
|
-
click_ctx: click.Context, ctx: ServerContext, name: str,
|
|
119
|
-
system_folders: bool, force: bool
|
|
120
|
-
) -> None:
|
|
121
|
-
"""
|
|
122
|
-
Function to remove a server.
|
|
123
|
-
|
|
124
|
-
Parameters
|
|
125
|
-
----------
|
|
126
|
-
ctx : ServerContext
|
|
127
|
-
Server context object
|
|
128
|
-
name : str
|
|
129
|
-
Name of the server to remove
|
|
130
|
-
system_folders : bool
|
|
131
|
-
Whether to use system folders or not
|
|
132
|
-
force : bool
|
|
133
|
-
Whether to ask for confirmation before removing or not
|
|
134
|
-
"""
|
|
135
|
-
check_docker_running()
|
|
136
|
-
|
|
137
|
-
# first stop server
|
|
138
|
-
click_ctx.invoke(
|
|
139
|
-
cli_server_stop, name=name, system_folders=system_folders,
|
|
140
|
-
all_servers=False
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
if not force:
|
|
144
|
-
if not q.confirm(
|
|
145
|
-
"This server will be deleted permanently including its "
|
|
146
|
-
"configuration. Are you sure?", default=False
|
|
147
|
-
).ask():
|
|
148
|
-
info("Server will not be deleted")
|
|
149
|
-
exit(0)
|
|
150
|
-
|
|
151
|
-
# now remove the folders...
|
|
152
|
-
info(f"Removing configuration file {ctx.config_file}")
|
|
153
|
-
remove_file(ctx.config_file, 'configuration')
|
|
154
|
-
|
|
155
|
-
info(f"Removing log file {ctx.log_file}")
|
|
156
|
-
for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
|
|
157
|
-
handler.close()
|
|
158
|
-
remove_file(ctx.log_file, 'log')
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from vantage6.client import UserClient
|
|
8
|
+
from vantage6.common import info, debug
|
|
9
|
+
from vantage6.cli.globals import DIAGNOSTICS_IMAGE
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DiagnosticRunner:
|
|
13
|
+
"""
|
|
14
|
+
Class to run the diagnostic algorithm on a vantage6 network.
|
|
15
|
+
|
|
16
|
+
This class will create a task in the requested collaboration that will test
|
|
17
|
+
the functionality of vantage6, and will report back the results.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
client : UserClient
|
|
22
|
+
The client to use for communication with the server.
|
|
23
|
+
collaboration_id : int
|
|
24
|
+
The ID of the collaboration to run the diagnostics in.
|
|
25
|
+
organizations : list[int] | None
|
|
26
|
+
The ID(s) of the organization(s) to run the diagnostics for. If None
|
|
27
|
+
(default), run the diagnostics for all organizations in the
|
|
28
|
+
collaboration.
|
|
29
|
+
online_only : bool
|
|
30
|
+
Whether to run the diagnostics only on nodes that are online. By
|
|
31
|
+
default False
|
|
32
|
+
"""
|
|
33
|
+
def __init__(
|
|
34
|
+
self, client: UserClient, collaboration_id: int,
|
|
35
|
+
organizations: list[int] | None = None, online_only: bool = False
|
|
36
|
+
) -> None:
|
|
37
|
+
self.client = client
|
|
38
|
+
self.collaboration_id = collaboration_id
|
|
39
|
+
|
|
40
|
+
if not organizations:
|
|
41
|
+
# run on all organizations in the collaboration
|
|
42
|
+
# TODO use pagination properly, instead of just getting the first
|
|
43
|
+
# 1000 organizations (which very likely is enough though)
|
|
44
|
+
orgs = self.client.organization.list(
|
|
45
|
+
collaboration=self.collaboration_id, per_page=1000
|
|
46
|
+
)
|
|
47
|
+
self.organization_ids = [org['id'] for org in orgs['data']]
|
|
48
|
+
else:
|
|
49
|
+
self.organization_ids = organizations
|
|
50
|
+
|
|
51
|
+
if online_only:
|
|
52
|
+
nodes = self.client.node.list(
|
|
53
|
+
collaboration=self.collaboration_id,
|
|
54
|
+
is_online=True
|
|
55
|
+
)
|
|
56
|
+
debug(nodes)
|
|
57
|
+
online_orgs = [
|
|
58
|
+
node['organization']['id'] for node in nodes['data']
|
|
59
|
+
]
|
|
60
|
+
self.organization_ids = \
|
|
61
|
+
list(set(self.organization_ids).intersection(online_orgs))
|
|
62
|
+
|
|
63
|
+
info(f"Running diagnostics to {len(self.organization_ids)} "
|
|
64
|
+
"organization(s)")
|
|
65
|
+
info(f" organizations: {self.organization_ids}")
|
|
66
|
+
info(f" collaboration: {self.collaboration_id}")
|
|
67
|
+
|
|
68
|
+
def __call__(self, base: bool = True, vpn: bool = True) -> Any:
|
|
69
|
+
"""
|
|
70
|
+
Run the diagnostics.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
----------
|
|
74
|
+
base: bool
|
|
75
|
+
Whether to run the base features of the diagnostic algorithm. By
|
|
76
|
+
default True.
|
|
77
|
+
vpn: bool
|
|
78
|
+
Whether to run the VPN features of the diagnostic algorithm. By
|
|
79
|
+
default True.
|
|
80
|
+
"""
|
|
81
|
+
base_results = self.base_features() if base else []
|
|
82
|
+
vpn_results = self.vpn_features() if vpn else []
|
|
83
|
+
return base_results + vpn_results
|
|
84
|
+
|
|
85
|
+
def base_features(self) -> list[dict]:
|
|
86
|
+
"""
|
|
87
|
+
Create a task to run the base features of the diagnostic algorithm.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
list[dict]
|
|
92
|
+
The results of the diagnostic algorithm.
|
|
93
|
+
"""
|
|
94
|
+
task = self.client.task.create(
|
|
95
|
+
collaboration=self.collaboration_id,
|
|
96
|
+
name="test",
|
|
97
|
+
description="Basic Diagnostic test",
|
|
98
|
+
image=DIAGNOSTICS_IMAGE,
|
|
99
|
+
input_={
|
|
100
|
+
"method": "base_features",
|
|
101
|
+
},
|
|
102
|
+
organizations=self.organization_ids,
|
|
103
|
+
databases=[
|
|
104
|
+
{'label': 'default'}
|
|
105
|
+
]
|
|
106
|
+
)
|
|
107
|
+
debug(task)
|
|
108
|
+
|
|
109
|
+
return self._wait_and_display(task.get("id"))
|
|
110
|
+
|
|
111
|
+
def vpn_features(self) -> list[dict]:
|
|
112
|
+
"""
|
|
113
|
+
Create a task to run the VPN features of the diagnostic algorithm.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
list[dict]
|
|
118
|
+
The results of the diagnostic algorithm.
|
|
119
|
+
"""
|
|
120
|
+
self.client.node.list(collaboration=self.collaboration_id)
|
|
121
|
+
|
|
122
|
+
task = self.client.task.create(
|
|
123
|
+
collaboration=self.collaboration_id,
|
|
124
|
+
name="test",
|
|
125
|
+
description="VPN Diagnostic test",
|
|
126
|
+
image=DIAGNOSTICS_IMAGE,
|
|
127
|
+
input_={
|
|
128
|
+
"method": "vpn_features",
|
|
129
|
+
"kwargs": {"other_nodes": self.organization_ids}
|
|
130
|
+
},
|
|
131
|
+
organizations=self.organization_ids,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return self._wait_and_display(task.get("id"))
|
|
135
|
+
|
|
136
|
+
def _wait_and_display(self, task_id: int) -> list[dict]:
|
|
137
|
+
"""
|
|
138
|
+
Wait for the task to finish and then display the results.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
task_id : int
|
|
143
|
+
The ID of the task to wait for.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
list[dict]
|
|
148
|
+
The results of the diagnostic algorithm.
|
|
149
|
+
"""
|
|
150
|
+
# TODO should we have the option to combine these in one request? Seems
|
|
151
|
+
# like it would be more efficient
|
|
152
|
+
# TODO ensure that we get all pages of results
|
|
153
|
+
results = self.client.wait_for_results(task_id=task_id)['data']
|
|
154
|
+
runs = self.client.run.from_task(task_id=task_id)['data']
|
|
155
|
+
print("\n")
|
|
156
|
+
for res in results:
|
|
157
|
+
matched_run = [
|
|
158
|
+
run for run in runs if run['id'] == res['run']['id']
|
|
159
|
+
][0]
|
|
160
|
+
self.display_diagnostic_results(
|
|
161
|
+
res, matched_run['organization']['id']
|
|
162
|
+
)
|
|
163
|
+
print()
|
|
164
|
+
return results
|
|
165
|
+
|
|
166
|
+
def display_diagnostic_results(self, result: dict, org_id: int) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Print the results of the diagnostic algorithm.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
result : list[dict]
|
|
173
|
+
The result of the diagnostic algorithm.
|
|
174
|
+
org_id : int
|
|
175
|
+
The ID of the organization that the diagnostic algorithm was run on
|
|
176
|
+
"""
|
|
177
|
+
res = json.loads(result["result"])
|
|
178
|
+
t_ = Table(title=f"Basic Diagnostics Summary (organization {org_id})")
|
|
179
|
+
t_.add_column('name')
|
|
180
|
+
t_.add_column('success')
|
|
181
|
+
e_ = Table(title=f"Basic Diagnostics Errors (organization {org_id})")
|
|
182
|
+
e_.add_column('name')
|
|
183
|
+
e_.add_column('exception')
|
|
184
|
+
e_.add_column('traceback')
|
|
185
|
+
e_.add_column('payload')
|
|
186
|
+
errors = False
|
|
187
|
+
for diag in res:
|
|
188
|
+
if diag['success']:
|
|
189
|
+
success = ":heavy_check_mark: [green]success[/green]"
|
|
190
|
+
else:
|
|
191
|
+
success = ":x: [red]failed[/red]"
|
|
192
|
+
e_.add_row(diag["name"], diag["exception"], diag["traceback"],
|
|
193
|
+
diag["payload"])
|
|
194
|
+
errors = True
|
|
195
|
+
t_.add_row(diag["name"], success)
|
|
196
|
+
|
|
197
|
+
console = Console()
|
|
198
|
+
console.print(t_)
|
|
199
|
+
if errors:
|
|
200
|
+
console.print(e_)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
from vantage6.client import UserClient
|
|
5
|
+
from vantage6.cli.utils import error
|
|
6
|
+
from vantage6.cli.test.common.diagnostic_runner import DiagnosticRunner
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.command()
|
|
10
|
+
@click.option("--host", type=str, default="http://localhost",
|
|
11
|
+
help="URL of the server")
|
|
12
|
+
@click.option("--port", type=int, default=5000, help="Port of the server")
|
|
13
|
+
@click.option("--api-path", type=str, default="/api",
|
|
14
|
+
help="API path of the server")
|
|
15
|
+
@click.option("--username", type=str, default="root",
|
|
16
|
+
help="Username of vantage6 user account to create the task with")
|
|
17
|
+
@click.option("--password", type=str, default="root",
|
|
18
|
+
help="Password of vantage6 user account to create the task with")
|
|
19
|
+
@click.option("--collaboration", type=int, default=1,
|
|
20
|
+
help="ID of the collaboration to create the task in")
|
|
21
|
+
@click.option("-o", "--organizations", type=int, default=[], multiple=True,
|
|
22
|
+
help="ID(s) of the organization(s) to create the task for")
|
|
23
|
+
@click.option("--all-nodes", is_flag=True,
|
|
24
|
+
help="Run the diagnostic test on all nodes in the collaboration")
|
|
25
|
+
@click.option("--online-only", is_flag=True,
|
|
26
|
+
help="Run the diagnostic test on only nodes that are online")
|
|
27
|
+
@click.option("--no-vpn", is_flag=True,
|
|
28
|
+
help="Don't execute VPN tests")
|
|
29
|
+
def cli_test_features(
|
|
30
|
+
host: str, port: int, api_path: str, username: str, password: str,
|
|
31
|
+
collaboration: int, organizations: list[int] | None, all_nodes: bool,
|
|
32
|
+
online_only: bool, no_vpn: bool
|
|
33
|
+
) -> list[dict]:
|
|
34
|
+
"""
|
|
35
|
+
Run diagnostic checks on an existing vantage6 network.
|
|
36
|
+
|
|
37
|
+
This command will create a task in the requested collaboration that will
|
|
38
|
+
test the functionality of vantage6, and will report back the results.
|
|
39
|
+
"""
|
|
40
|
+
if all_nodes and organizations:
|
|
41
|
+
error("Cannot use --all-nodes and --organization at the same time.")
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
if all_nodes or not organizations:
|
|
45
|
+
organizations = None
|
|
46
|
+
|
|
47
|
+
client = UserClient(host=host, port=port, path=api_path,
|
|
48
|
+
log_level='critical')
|
|
49
|
+
client.authenticate(username=username, password=password)
|
|
50
|
+
client.setup_encryption(None)
|
|
51
|
+
diagnose = DiagnosticRunner(client, collaboration, organizations,
|
|
52
|
+
online_only)
|
|
53
|
+
res = diagnose(base=True, vpn=not no_vpn)
|
|
54
|
+
return res
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
from vantage6.cli.utils import info
|
|
5
|
+
from vantage6.cli.dev.create import create_demo_network
|
|
6
|
+
from vantage6.cli.dev.start import start_demo_network
|
|
7
|
+
from vantage6.cli.dev.stop import stop_demo_network
|
|
8
|
+
from vantage6.cli.dev.remove import remove_demo_network
|
|
9
|
+
from vantage6.cli.utils import prompt_config_name, check_config_name_allowed
|
|
10
|
+
from vantage6.cli.test.feature_tester import cli_test_features
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.option('-n', '--name', default=None, type=str,
|
|
15
|
+
help="Name for your development setup")
|
|
16
|
+
@click.option('--server-url', type=str, default='http://host.docker.internal',
|
|
17
|
+
help='Server URL to point to. If you are using Docker Desktop, '
|
|
18
|
+
'the default http://host.docker.internal should not be changed.')
|
|
19
|
+
@click.option('-i', '--image', type=str, default=None,
|
|
20
|
+
help='Server Docker image to use')
|
|
21
|
+
@click.option('--keep', type=bool, default=False,
|
|
22
|
+
help='Keep the dev network after finishing the test')
|
|
23
|
+
@click.option('--extra-server-config', type=click.Path(exists=True),
|
|
24
|
+
default=None, help='YAML File with additional server '
|
|
25
|
+
'configuration. This will be appended to the server '
|
|
26
|
+
'configuration file')
|
|
27
|
+
@click.option('--extra-node-config', type=click.Path('rb'), default=None,
|
|
28
|
+
help='YAML File with additional node configuration. This will be'
|
|
29
|
+
' appended to each of the node configuration files')
|
|
30
|
+
@click.pass_context
|
|
31
|
+
def cli_test_integration(
|
|
32
|
+
click_ctx: click.Context, name: str, server_url: str, image: str,
|
|
33
|
+
keep: bool = False, extra_server_config: Path = None,
|
|
34
|
+
extra_node_config: Path = None
|
|
35
|
+
) -> list[dict]:
|
|
36
|
+
"""
|
|
37
|
+
Create dev network and run diagnostic checks on it.
|
|
38
|
+
|
|
39
|
+
This is a full integration test of the vantage6 network. It will create
|
|
40
|
+
a test server with some nodes using the `vdev` commands, and then run the
|
|
41
|
+
v6-diagnostics algorithm to test all functionality.
|
|
42
|
+
"""
|
|
43
|
+
# get name for the development setup - if not given - and check if it is
|
|
44
|
+
# allowed
|
|
45
|
+
name = prompt_config_name(name)
|
|
46
|
+
check_config_name_allowed(name)
|
|
47
|
+
|
|
48
|
+
# create server & node configurations and create test resources (
|
|
49
|
+
# collaborations, organizations, etc)
|
|
50
|
+
click_ctx.invoke(
|
|
51
|
+
create_demo_network, name=name, num_nodes=3, server_url=server_url,
|
|
52
|
+
server_port=5000, image=image, extra_server_config=extra_server_config,
|
|
53
|
+
extra_node_config=extra_node_config
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# start the server and nodes
|
|
57
|
+
click_ctx.invoke(
|
|
58
|
+
start_demo_network, name=name, system_folders=True, server_image=image,
|
|
59
|
+
node_image=image
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# run the diagnostic tests
|
|
63
|
+
# TODO the username and password should be coordinated with the vdev
|
|
64
|
+
# command - at present it spits out this username/password combination by
|
|
65
|
+
# default but both should be defined in the same place
|
|
66
|
+
# TODO VPN testing is always excluded - allow to include it with a flag
|
|
67
|
+
# when vdev commands can handle extra config parameters
|
|
68
|
+
diagnose_results = click_ctx.invoke(
|
|
69
|
+
cli_test_features, host="http://localhost", port=5000, api_path='/api',
|
|
70
|
+
username='org_1-admin', password='password', collaboration=1,
|
|
71
|
+
organizations=None, all_nodes=True, online_only=False, no_vpn=True
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# clean up the test resources
|
|
75
|
+
click_ctx.invoke(stop_demo_network, name=name, system_folders=True)
|
|
76
|
+
if not keep:
|
|
77
|
+
click_ctx.invoke(remove_demo_network, name=name, system_folders=True)
|
|
78
|
+
else:
|
|
79
|
+
info("Keeping the demo network {name}. You can start it with `v6 dev "
|
|
80
|
+
"start-demo-network`")
|
|
81
|
+
|
|
82
|
+
return diagnose_results
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vantage6
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.0rc2
|
|
4
4
|
Summary: vantage6 command line interface
|
|
5
5
|
Home-page: https://github.com/vantage6/vantage6
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -10,12 +10,13 @@ Requires-Dist: colorama ==0.4.6
|
|
|
10
10
|
Requires-Dist: copier ==8.3.0
|
|
11
11
|
Requires-Dist: docker ==6.1.2
|
|
12
12
|
Requires-Dist: ipython ==8.10.0
|
|
13
|
-
Requires-Dist: jinja2 ==3.1.
|
|
13
|
+
Requires-Dist: jinja2 ==3.1.3
|
|
14
14
|
Requires-Dist: questionary ==1.10.0
|
|
15
|
+
Requires-Dist: rich ==13.5.2
|
|
15
16
|
Requires-Dist: schema ==0.7.5
|
|
16
17
|
Requires-Dist: SQLAlchemy ==1.4.46
|
|
17
|
-
Requires-Dist: vantage6-common ==4.
|
|
18
|
-
Requires-Dist: vantage6-client ==4.
|
|
18
|
+
Requires-Dist: vantage6-common ==4.2.0rc2
|
|
19
|
+
Requires-Dist: vantage6-client ==4.2.0rc2
|
|
19
20
|
Provides-Extra: dev
|
|
20
21
|
Requires-Dist: coverage ==6.4.4 ; extra == 'dev'
|
|
21
22
|
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
tests_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
tests_cli/test_example.py,sha256=TV8JA52UR-Yh1WeVwx8g8ngA9ZTiRCwsJkUKxsXUXMg,147
|
|
3
|
-
tests_cli/test_node_cli.py,sha256=
|
|
4
|
-
tests_cli/test_server_cli.py,sha256=
|
|
3
|
+
tests_cli/test_node_cli.py,sha256=8tfCNvdZT7uMgOJ0QbBlY73kox7RUIDHhxQWVtJiSiA,15454
|
|
4
|
+
tests_cli/test_server_cli.py,sha256=hWnGEV6JnXN_SqpUiQxCzITBlbYKRykb8B4MkFX7aEU,5798
|
|
5
5
|
tests_cli/test_wizard.py,sha256=dRrOScN79z8nKGNWmn6tPAVfnDOmCsGckiDJMKufhms,3580
|
|
6
|
-
vantage6/cli/__build__,sha256=
|
|
6
|
+
vantage6/cli/__build__,sha256=1HNeOiZeFu7gP1lxi5tdAwGcB9i2xR-Q2jpmbuwTqzU,1
|
|
7
7
|
vantage6/cli/__init__.py,sha256=dEbhLHe9VfU3otY3CQkMVrj39uPGWjqzAvHW2B2Flgs,62
|
|
8
|
-
vantage6/cli/_version.py,sha256=
|
|
9
|
-
vantage6/cli/cli.py,sha256=
|
|
8
|
+
vantage6/cli/_version.py,sha256=vNBdXaoKkSVejAo0RNxmIyEGekryfMYJeh21XE5SACo,688
|
|
9
|
+
vantage6/cli/cli.py,sha256=Tz7dj6X8GVPrzHd9GehURdbPFIDzGvyYW2MXRUJwFs4,4875
|
|
10
10
|
vantage6/cli/configuration_manager.py,sha256=4EHlOW16nfIiplzA0sw9udc4-VLKcmdKgWzzFa5Nw7A,3841
|
|
11
11
|
vantage6/cli/configuration_wizard.py,sha256=c8L4xm9_HYjAkyTmRmLDMwzTDqdKdgnBZy915TAIfRI,11569
|
|
12
12
|
vantage6/cli/context.py,sha256=dJZWmOZGqQ3MazrPu8HbnXbNC2tzHcLjns9KoIHGpFk,13045
|
|
13
|
-
vantage6/cli/globals.py,sha256=
|
|
13
|
+
vantage6/cli/globals.py,sha256=yDYhbzWgwrd549lTkVH7g59orQuN30zwP60VHiUiMD4,1027
|
|
14
14
|
vantage6/cli/utils.py,sha256=Lx1xYKXDBeHjJpXGkVAhidy79P-zV3TOQYfa2nMbvSs,2353
|
|
15
15
|
vantage6/cli/algorithm/create.py,sha256=cUjehkwRa6eWRSnWLPk9iLk4_qf8x_MI2WxmaAUMVT0,1496
|
|
16
16
|
vantage6/cli/algorithm/update.py,sha256=0GCkFIRGV-HWHWagTSqkE9_SPuVrTMQQhtytpitMiQc,821
|
|
17
|
-
vantage6/cli/dev/create.py,sha256=
|
|
18
|
-
vantage6/cli/dev/remove.py,sha256=
|
|
17
|
+
vantage6/cli/dev/create.py,sha256=Xij_DOunyjzoDulpIrL00kasHiXbjp_YJWR3OjScCZc,13249
|
|
18
|
+
vantage6/cli/dev/remove.py,sha256=m5Yjt7U3pTmlZm1f8G8JsxMm9k-CWTgoy-sCZ7GTnZA,2211
|
|
19
19
|
vantage6/cli/dev/start.py,sha256=IJbXzezMx4MK7rO9nx3AvidEZfdUL5mX0gA7AlwETLU,1629
|
|
20
20
|
vantage6/cli/dev/stop.py,sha256=XDvjyxxU4gVnC9sKHxYMLYPmDwRq_4lnpXE9imO614Q,993
|
|
21
21
|
vantage6/cli/node/attach.py,sha256=n7mJaDJq7tngMVigXe0_JBAdueV_5YEz8FmVOvAF-PU,2152
|
|
@@ -26,7 +26,7 @@ vantage6/cli/node/list.py,sha256=b8ht09bvzVFiQx909r8zeTruCWC2uLyrNVeiipCjgAI,170
|
|
|
26
26
|
vantage6/cli/node/new.py,sha256=01sp9005TidvHA_awuaahBHV1-LImspStLTYhruek_A,1858
|
|
27
27
|
vantage6/cli/node/remove.py,sha256=kLMK2Xsfgwrb3FuqRc9cKCadc8p0P3dLTLt9wN1_sBg,3564
|
|
28
28
|
vantage6/cli/node/set_api_key.py,sha256=cJXWe1G5m2LTlyhBX85KFlCcL7voO-YNHUvhnsrwHhE,1796
|
|
29
|
-
vantage6/cli/node/start.py,sha256=
|
|
29
|
+
vantage6/cli/node/start.py,sha256=ojv2csH0PcqfoT9rZNJpmuGerqBN74Ypt5bta6caLic,12404
|
|
30
30
|
vantage6/cli/node/stop.py,sha256=08SWafKBpCY_pnMopRkcIcHNaBoxqRHmlDSfQDzWjuQ,2704
|
|
31
31
|
vantage6/cli/node/version.py,sha256=HD0iCf2VGZK8HVYfNcMpDAQdxYQ--5ff10XYCsX7Hf4,1843
|
|
32
32
|
vantage6/cli/node/common/__init__.py,sha256=nUDpIehzfX9Zd3tL2Wa9nP5940SH27gcXGRiCHcxjrA,3166
|
|
@@ -39,16 +39,20 @@ vantage6/cli/server/files.py,sha256=owBO6lj_ucHY00MxJUOOdacz4NbdOqp-vrfkVrGjPUE,
|
|
|
39
39
|
vantage6/cli/server/import_.py,sha256=Up3IAK3c9jyFpr7y9_zl4HNViFj6jT9OLh_S6v6t5Hs,4611
|
|
40
40
|
vantage6/cli/server/list.py,sha256=p5SRRGsz6G83Hc53eYoySWi32egm7jie_lo1gkS0NCk,1734
|
|
41
41
|
vantage6/cli/server/new.py,sha256=ZHnjWlEKXOMdu6WYHDcrD62Qc2au-YuYIv2SW2KXS7M,1913
|
|
42
|
+
vantage6/cli/server/remove.py,sha256=1Ogw2npPA9QsExVforzLe3dI3eTwxuBd9oasviX4Z_g,1182
|
|
42
43
|
vantage6/cli/server/shell.py,sha256=SRw-CrD_ro9DRfqCI6IlifJk_UDr6EIke5ypN4-85yo,1384
|
|
43
44
|
vantage6/cli/server/start.py,sha256=-qD1rB5dRXWLL13OVWFb4DwdshGF708bL19WOlO9uUo,10952
|
|
44
|
-
vantage6/cli/server/stop.py,sha256=
|
|
45
|
+
vantage6/cli/server/stop.py,sha256=aEOqa2hYPLTGPkMBzFrFI4FLP0zMskjYD1vqzDyKEog,4005
|
|
45
46
|
vantage6/cli/server/version.py,sha256=upvy6VdO3XGfovEW-2Ybc0c0niM2ieCOrz1SlsBrqkw,1743
|
|
46
47
|
vantage6/cli/server/common/__init__.py,sha256=kUcDOfGuNvf8j7g5er9D_7z8IHRk53QBQ2E0NcZ_1iE,4818
|
|
47
|
-
vantage6/cli/template/node_config.j2,sha256
|
|
48
|
-
vantage6/cli/template/server_config.j2,sha256=
|
|
48
|
+
vantage6/cli/template/node_config.j2,sha256=ebTifJuRlfUhjtI8jm9A2SWXGpUcg8L-XUBInSwAppA,522
|
|
49
|
+
vantage6/cli/template/server_config.j2,sha256=rKXMky8rA7Lnf5USlPx0Es9AskwmL4bILWfACPvygv4,394
|
|
49
50
|
vantage6/cli/template/server_import_config.j2,sha256=PRB0ym_FYjx9vhkmY9C0xzlv_85Y5kBfWdUYs089bNQ,1844
|
|
50
|
-
vantage6
|
|
51
|
-
vantage6
|
|
52
|
-
vantage6
|
|
53
|
-
vantage6-4.
|
|
54
|
-
vantage6-4.
|
|
51
|
+
vantage6/cli/test/feature_tester.py,sha256=8MJGotRonEje6GVTkqOr5u1LbZi3W4A4gQnf5OYt-2I,2368
|
|
52
|
+
vantage6/cli/test/integration_test.py,sha256=GpYG0uHoD1lVKwtSFk_QhdR1e_AYzKWIVPDI5CSwJ8o,3669
|
|
53
|
+
vantage6/cli/test/common/diagnostic_runner.py,sha256=j6MWV0_dKZScYBwd--3_dv_JSeWyeUmp8T7E-7ZSjs8,6755
|
|
54
|
+
vantage6-4.2.0rc2.dist-info/METADATA,sha256=L8_VGCM7QnL-1uiADLAJ_kFq47UEvGI5cK2dL7RFSSA,9767
|
|
55
|
+
vantage6-4.2.0rc2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
56
|
+
vantage6-4.2.0rc2.dist-info/entry_points.txt,sha256=YFBvwjxoeAGxYyPC-YevEgOBBYRGaXkS6jiOGGCLNy0,157
|
|
57
|
+
vantage6-4.2.0rc2.dist-info/top_level.txt,sha256=CYDIBS8jEfFq5YCs_Fuit54K9-3wdosZppTrsymIoUk,19
|
|
58
|
+
vantage6-4.2.0rc2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|