trainml 0.5.5__tar.gz → 0.5.7__tar.gz
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.
- {trainml-0.5.5/trainml.egg-info → trainml-0.5.7}/PKG-INFO +1 -1
- {trainml-0.5.5 → trainml-0.5.7}/pyproject.toml +2 -1
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_jobs_integration.py +13 -0
- trainml-0.5.7/tests/unit/cli/cloudbender/test_cli_service_unit.py +34 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_project_unit.py +5 -9
- trainml-0.5.7/tests/unit/cloudbender/test_data_connectors_unit.py +176 -0
- trainml-0.5.7/tests/unit/cloudbender/test_services_unit.py +167 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/conftest.py +13 -13
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_projects_unit.py +77 -51
- {trainml-0.5.5 → trainml-0.5.7}/trainml/__init__.py +1 -1
- {trainml-0.5.5 → trainml-0.5.7}/trainml/checkpoints.py +25 -25
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/cloudbender/__init__.py +2 -1
- trainml-0.5.5/trainml/cli/cloudbender/reservation.py → trainml-0.5.7/trainml/cli/cloudbender/data_connector.py +45 -45
- trainml-0.5.7/trainml/cli/cloudbender/service.py +146 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/project.py +10 -15
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/cloudbender.py +4 -2
- trainml-0.5.5/trainml/cloudbender/reservations.py → trainml-0.5.7/trainml/cloudbender/data_connectors.py +24 -38
- trainml-0.5.7/trainml/cloudbender/services.py +179 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/datasets.py +19 -8
- {trainml-0.5.5 → trainml-0.5.7}/trainml/jobs.py +13 -6
- {trainml-0.5.5 → trainml-0.5.7}/trainml/models.py +22 -19
- {trainml-0.5.5 → trainml-0.5.7}/trainml/projects.py +72 -31
- {trainml-0.5.5 → trainml-0.5.7}/trainml/volumes.py +9 -2
- {trainml-0.5.5 → trainml-0.5.7/trainml.egg-info}/PKG-INFO +1 -1
- {trainml-0.5.5 → trainml-0.5.7}/trainml.egg-info/SOURCES.txt +7 -4
- trainml-0.5.5/tests/unit/cli/cloudbender/test_cli_reservation_unit.py +0 -38
- trainml-0.5.5/tests/unit/cloudbender/test_reservations_unit.py +0 -173
- {trainml-0.5.5 → trainml-0.5.7}/LICENSE +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/README.md +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/examples/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/examples/create_dataset_and_training_job.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/examples/local_storage.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/examples/training_inference_pipeline.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/setup.cfg +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/setup.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/cloudbender/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/cloudbender/test_providers_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/conftest.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_checkpoints_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_datasets_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_environments_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_gpu_types_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_models_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_projects_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/integration/test_volumes_integration.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/cloudbender/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/cloudbender/test_cli_datastore_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/cloudbender/test_cli_device_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/cloudbender/test_cli_node_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/cloudbender/test_cli_provider_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/cloudbender/test_cli_region_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/conftest.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_checkpoint_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_datasets_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_environment_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_gpu_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_job_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_model_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cli/test_cli_volume_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cloudbender/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cloudbender/test_datastores_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cloudbender/test_device_configs_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cloudbender/test_devices_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cloudbender/test_nodes_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cloudbender/test_providers_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/cloudbender/test_regions_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_auth.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_checkpoints_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_connections_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_datasets_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_environments_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_exceptions.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_gpu_types_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_jobs_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_models_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_trainml.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/tests/unit/test_volumes_unit.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/__main__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/auth.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/checkpoint.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/cloudbender/datastore.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/cloudbender/device.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/cloudbender/node.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/cloudbender/provider.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/cloudbender/region.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/connection.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/dataset.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/environment.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/gpu.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/job/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/job/create.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/model.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cli/volume.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/__init__.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/datastores.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/device_configs.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/devices.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/nodes.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/providers.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/cloudbender/regions.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/connections.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/environments.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/exceptions.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/gpu_types.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml/trainml.py +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml.egg-info/dependency_links.txt +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml.egg-info/entry_points.txt +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml.egg-info/requires.txt +0 -0
- {trainml-0.5.5 → trainml-0.5.7}/trainml.egg-info/top_level.txt +0 -0
|
@@ -21,7 +21,8 @@ markers = [
|
|
|
21
21
|
"nodes: Nodes tests",
|
|
22
22
|
"devices: Devices tests",
|
|
23
23
|
"datastores: Datastores tests",
|
|
24
|
-
"
|
|
24
|
+
"data_connectors: Data Connector tests",
|
|
25
|
+
"services: Services tests",
|
|
25
26
|
"device_configs: DeviceConfigs tests",
|
|
26
27
|
"unit: All unit tests (no trainML environment required)",
|
|
27
28
|
"integration: All integration tests (trainML environment required)",
|
|
@@ -6,10 +6,19 @@ import asyncio
|
|
|
6
6
|
import aiohttp
|
|
7
7
|
from pytest import mark, fixture, raises
|
|
8
8
|
from trainml.exceptions import ApiError
|
|
9
|
+
from urllib.parse import urlparse
|
|
9
10
|
|
|
10
11
|
pytestmark = [mark.sdk, mark.integration, mark.jobs]
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
def extract_domain_suffix(hostname):
|
|
15
|
+
parts = hostname.split(".")
|
|
16
|
+
if len(parts) >= 2:
|
|
17
|
+
return ".".join(parts[-2:])
|
|
18
|
+
else:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
|
|
13
22
|
@fixture(scope="class")
|
|
14
23
|
async def job(trainml):
|
|
15
24
|
job = await trainml.jobs.create(
|
|
@@ -34,6 +43,8 @@ class JobLifeCycleTests:
|
|
|
34
43
|
assert job.status != "running"
|
|
35
44
|
job = await job.wait_for("running")
|
|
36
45
|
assert job.status == "running"
|
|
46
|
+
assert job.url
|
|
47
|
+
assert extract_domain_suffix(urlparse(job.url).hostname) == "proximl.cloud"
|
|
37
48
|
|
|
38
49
|
async def test_stop_job(self, job):
|
|
39
50
|
assert job.status == "running"
|
|
@@ -518,6 +529,7 @@ class JobIOTests:
|
|
|
518
529
|
@mark.asyncio
|
|
519
530
|
class JobTypeTests:
|
|
520
531
|
async def test_endpoint(self, trainml):
|
|
532
|
+
|
|
521
533
|
job = await trainml.jobs.create(
|
|
522
534
|
"CLI Automated Tests - Endpoint",
|
|
523
535
|
type="endpoint",
|
|
@@ -544,6 +556,7 @@ class JobTypeTests:
|
|
|
544
556
|
await job.wait_for("running")
|
|
545
557
|
await job.refresh()
|
|
546
558
|
assert job.url
|
|
559
|
+
assert extract_domain_suffix(urlparse(job.url).hostname) == "proximl.cloud"
|
|
547
560
|
tries = 0
|
|
548
561
|
await asyncio.sleep(30)
|
|
549
562
|
async with aiohttp.ClientSession() as session:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import click
|
|
4
|
+
from unittest.mock import AsyncMock, patch
|
|
5
|
+
from pytest import mark, fixture, raises
|
|
6
|
+
|
|
7
|
+
pytestmark = [mark.cli, mark.unit, mark.cloudbender, mark.services]
|
|
8
|
+
|
|
9
|
+
from trainml.cli.cloudbender import service as specimen
|
|
10
|
+
from trainml.cloudbender.services import Service
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_list(runner, mock_services):
|
|
14
|
+
with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
|
|
15
|
+
mock_trainml.cloudbender = AsyncMock()
|
|
16
|
+
mock_trainml.cloudbender.services = AsyncMock()
|
|
17
|
+
mock_trainml.cloudbender.services.list = AsyncMock(return_value=mock_services)
|
|
18
|
+
result = runner.invoke(
|
|
19
|
+
specimen,
|
|
20
|
+
args=["list", "--provider=prov-id-1", "--region=reg-id-1"],
|
|
21
|
+
)
|
|
22
|
+
assert result.exit_code == 0
|
|
23
|
+
mock_trainml.cloudbender.services.list.assert_called_once_with(
|
|
24
|
+
provider_uuid="prov-id-1", region_uuid="reg-id-1"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_list_no_provider(runner, mock_services):
|
|
29
|
+
with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
|
|
30
|
+
mock_trainml.cloudbender = AsyncMock()
|
|
31
|
+
mock_trainml.cloudbender.services = AsyncMock()
|
|
32
|
+
mock_trainml.cloudbender.services.list = AsyncMock(return_value=mock_services)
|
|
33
|
+
result = runner.invoke(specimen, ["list"])
|
|
34
|
+
assert result.exit_code != 0
|
|
@@ -23,9 +23,7 @@ def test_list(runner, mock_projects):
|
|
|
23
23
|
def test_list_datastores(runner, mock_project_datastores):
|
|
24
24
|
with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
|
|
25
25
|
mock_project = create_autospec(Project)
|
|
26
|
-
mock_project.list_datastores = AsyncMock(
|
|
27
|
-
return_value=mock_project_datastores
|
|
28
|
-
)
|
|
26
|
+
mock_project.list_datastores = AsyncMock(return_value=mock_project_datastores)
|
|
29
27
|
mock_trainml.projects.get = AsyncMock(return_value=mock_project)
|
|
30
28
|
result = runner.invoke(specimen, ["list-datastores"])
|
|
31
29
|
print(result)
|
|
@@ -33,14 +31,12 @@ def test_list_datastores(runner, mock_project_datastores):
|
|
|
33
31
|
mock_project.list_datastores.assert_called_once()
|
|
34
32
|
|
|
35
33
|
|
|
36
|
-
def
|
|
34
|
+
def test_list_services(runner, mock_project_services):
|
|
37
35
|
with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
|
|
38
36
|
mock_project = create_autospec(Project)
|
|
39
|
-
mock_project.
|
|
40
|
-
return_value=mock_project_reservations
|
|
41
|
-
)
|
|
37
|
+
mock_project.list_services = AsyncMock(return_value=mock_project_services)
|
|
42
38
|
mock_trainml.projects.get = AsyncMock(return_value=mock_project)
|
|
43
|
-
result = runner.invoke(specimen, ["list-
|
|
39
|
+
result = runner.invoke(specimen, ["list-services"])
|
|
44
40
|
print(result)
|
|
45
41
|
assert result.exit_code == 0
|
|
46
|
-
mock_project.
|
|
42
|
+
mock_project.list_services.assert_called_once()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from unittest.mock import AsyncMock, patch
|
|
5
|
+
from pytest import mark, fixture, raises
|
|
6
|
+
from aiohttp import WSMessage, WSMsgType
|
|
7
|
+
|
|
8
|
+
import trainml.cloudbender.data_connectors as specimen
|
|
9
|
+
from trainml.exceptions import (
|
|
10
|
+
ApiError,
|
|
11
|
+
SpecificationError,
|
|
12
|
+
TrainMLException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
pytestmark = [mark.sdk, mark.unit, mark.cloudbender, mark.data_connectors]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@fixture
|
|
19
|
+
def data_connectors(mock_trainml):
|
|
20
|
+
yield specimen.DataConnectors(mock_trainml)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@fixture
|
|
24
|
+
def data_connector(mock_trainml):
|
|
25
|
+
yield specimen.DataConnector(
|
|
26
|
+
mock_trainml,
|
|
27
|
+
provider_uuid="1",
|
|
28
|
+
region_uuid="a",
|
|
29
|
+
connector_id="x",
|
|
30
|
+
name="On-Prem Data Connector",
|
|
31
|
+
type="custom",
|
|
32
|
+
cidr="192.168.0.50/32",
|
|
33
|
+
port="443",
|
|
34
|
+
protocol="tcp",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RegionsTests:
|
|
39
|
+
@mark.asyncio
|
|
40
|
+
async def test_get_data_connector(
|
|
41
|
+
self,
|
|
42
|
+
data_connectors,
|
|
43
|
+
mock_trainml,
|
|
44
|
+
):
|
|
45
|
+
api_response = dict()
|
|
46
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
47
|
+
await data_connectors.get("1234", "5687", "91011")
|
|
48
|
+
mock_trainml._query.assert_called_once_with(
|
|
49
|
+
"/provider/1234/region/5687/data_connector/91011", "GET", {}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@mark.asyncio
|
|
53
|
+
async def test_list_data_connectors(
|
|
54
|
+
self,
|
|
55
|
+
data_connectors,
|
|
56
|
+
mock_trainml,
|
|
57
|
+
):
|
|
58
|
+
api_response = dict()
|
|
59
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
60
|
+
await data_connectors.list("1234", "5687")
|
|
61
|
+
mock_trainml._query.assert_called_once_with(
|
|
62
|
+
"/provider/1234/region/5687/data_connector", "GET", {}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@mark.asyncio
|
|
66
|
+
async def test_remove_data_connector(
|
|
67
|
+
self,
|
|
68
|
+
data_connectors,
|
|
69
|
+
mock_trainml,
|
|
70
|
+
):
|
|
71
|
+
api_response = dict()
|
|
72
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
73
|
+
await data_connectors.remove("1234", "4567", "8910")
|
|
74
|
+
mock_trainml._query.assert_called_once_with(
|
|
75
|
+
"/provider/1234/region/4567/data_connector/8910", "DELETE", {}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@mark.asyncio
|
|
79
|
+
async def test_create_data_connector(self, data_connectors, mock_trainml):
|
|
80
|
+
requested_config = dict(
|
|
81
|
+
provider_uuid="provider-id-1",
|
|
82
|
+
region_uuid="region-id-1",
|
|
83
|
+
name="On-Prem DataConnector",
|
|
84
|
+
type="custom",
|
|
85
|
+
cidr="192.168.0.50/32",
|
|
86
|
+
port="443",
|
|
87
|
+
protocol="tcp",
|
|
88
|
+
)
|
|
89
|
+
expected_payload = dict(
|
|
90
|
+
name="On-Prem DataConnector",
|
|
91
|
+
type="custom",
|
|
92
|
+
cidr="192.168.0.50/32",
|
|
93
|
+
port="443",
|
|
94
|
+
protocol="tcp",
|
|
95
|
+
)
|
|
96
|
+
api_response = {
|
|
97
|
+
"provider_uuid": "provider-id-1",
|
|
98
|
+
"region_uuid": "region-id-1",
|
|
99
|
+
"connector_id": "connector-id-1",
|
|
100
|
+
"name": "On-Prem DataConnector",
|
|
101
|
+
"type": "custom",
|
|
102
|
+
"cidr": "192.168.0.50/32",
|
|
103
|
+
"port": "443",
|
|
104
|
+
"protocol": "tcp",
|
|
105
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
109
|
+
response = await data_connectors.create(**requested_config)
|
|
110
|
+
mock_trainml._query.assert_called_once_with(
|
|
111
|
+
"/provider/provider-id-1/region/region-id-1/data_connector",
|
|
112
|
+
"POST",
|
|
113
|
+
None,
|
|
114
|
+
expected_payload,
|
|
115
|
+
)
|
|
116
|
+
assert response.id == "connector-id-1"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DataConnectorTests:
|
|
120
|
+
def test_data_connector_properties(self, data_connector):
|
|
121
|
+
assert isinstance(data_connector.id, str)
|
|
122
|
+
assert isinstance(data_connector.provider_uuid, str)
|
|
123
|
+
assert isinstance(data_connector.region_uuid, str)
|
|
124
|
+
assert isinstance(data_connector.type, str)
|
|
125
|
+
assert isinstance(data_connector.name, str)
|
|
126
|
+
|
|
127
|
+
def test_data_connector_str(self, data_connector):
|
|
128
|
+
string = str(data_connector)
|
|
129
|
+
regex = r"^{.*\"connector_id\": \"" + data_connector.id + r"\".*}$"
|
|
130
|
+
assert isinstance(string, str)
|
|
131
|
+
assert re.match(regex, string)
|
|
132
|
+
|
|
133
|
+
def test_data_connector_repr(self, data_connector):
|
|
134
|
+
string = repr(data_connector)
|
|
135
|
+
regex = (
|
|
136
|
+
r"^DataConnector\( trainml , \*\*{.*'connector_id': '"
|
|
137
|
+
+ data_connector.id
|
|
138
|
+
+ r"'.*}\)$"
|
|
139
|
+
)
|
|
140
|
+
assert isinstance(string, str)
|
|
141
|
+
assert re.match(regex, string)
|
|
142
|
+
|
|
143
|
+
def test_data_connector_bool(self, data_connector, mock_trainml):
|
|
144
|
+
empty_data_connector = specimen.DataConnector(mock_trainml)
|
|
145
|
+
assert bool(data_connector)
|
|
146
|
+
assert not bool(empty_data_connector)
|
|
147
|
+
|
|
148
|
+
@mark.asyncio
|
|
149
|
+
async def test_data_connector_remove(self, data_connector, mock_trainml):
|
|
150
|
+
api_response = dict()
|
|
151
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
152
|
+
await data_connector.remove()
|
|
153
|
+
mock_trainml._query.assert_called_once_with(
|
|
154
|
+
"/provider/1/region/a/data_connector/x", "DELETE"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
@mark.asyncio
|
|
158
|
+
async def test_data_connector_refresh(self, data_connector, mock_trainml):
|
|
159
|
+
api_response = {
|
|
160
|
+
"provider_uuid": "provider-id-1",
|
|
161
|
+
"region_uuid": "region-id-1",
|
|
162
|
+
"connector_id": "connector-id-1",
|
|
163
|
+
"name": "On-Prem DataConnector",
|
|
164
|
+
"type": "custom",
|
|
165
|
+
"cidr": "192.168.0.50/32",
|
|
166
|
+
"port": "443",
|
|
167
|
+
"protocol": "tcp",
|
|
168
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
169
|
+
}
|
|
170
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
171
|
+
response = await data_connector.refresh()
|
|
172
|
+
mock_trainml._query.assert_called_once_with(
|
|
173
|
+
f"/provider/1/region/a/data_connector/x", "GET"
|
|
174
|
+
)
|
|
175
|
+
assert data_connector.id == "connector-id-1"
|
|
176
|
+
assert response.id == "connector-id-1"
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from unittest.mock import AsyncMock, patch
|
|
5
|
+
from pytest import mark, fixture, raises
|
|
6
|
+
from aiohttp import WSMessage, WSMsgType
|
|
7
|
+
|
|
8
|
+
import trainml.cloudbender.services as specimen
|
|
9
|
+
from trainml.exceptions import (
|
|
10
|
+
ApiError,
|
|
11
|
+
SpecificationError,
|
|
12
|
+
TrainMLException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
pytestmark = [mark.sdk, mark.unit, mark.cloudbender, mark.services]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@fixture
|
|
19
|
+
def services(mock_trainml):
|
|
20
|
+
yield specimen.Services(mock_trainml)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@fixture
|
|
24
|
+
def service(mock_trainml):
|
|
25
|
+
yield specimen.Service(
|
|
26
|
+
mock_trainml,
|
|
27
|
+
provider_uuid="1",
|
|
28
|
+
region_uuid="a",
|
|
29
|
+
service_id="x",
|
|
30
|
+
name="On-Prem Service",
|
|
31
|
+
type="https",
|
|
32
|
+
public=False,
|
|
33
|
+
hostname="app1.proximl.cloud",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RegionsTests:
|
|
38
|
+
@mark.asyncio
|
|
39
|
+
async def test_get_service(
|
|
40
|
+
self,
|
|
41
|
+
services,
|
|
42
|
+
mock_trainml,
|
|
43
|
+
):
|
|
44
|
+
api_response = dict()
|
|
45
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
46
|
+
await services.get("1234", "5687", "91011")
|
|
47
|
+
mock_trainml._query.assert_called_once_with(
|
|
48
|
+
"/provider/1234/region/5687/service/91011", "GET", {}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@mark.asyncio
|
|
52
|
+
async def test_list_services(
|
|
53
|
+
self,
|
|
54
|
+
services,
|
|
55
|
+
mock_trainml,
|
|
56
|
+
):
|
|
57
|
+
api_response = dict()
|
|
58
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
59
|
+
await services.list("1234", "5687")
|
|
60
|
+
mock_trainml._query.assert_called_once_with(
|
|
61
|
+
"/provider/1234/region/5687/service", "GET", {}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@mark.asyncio
|
|
65
|
+
async def test_remove_service(
|
|
66
|
+
self,
|
|
67
|
+
services,
|
|
68
|
+
mock_trainml,
|
|
69
|
+
):
|
|
70
|
+
api_response = dict()
|
|
71
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
72
|
+
await services.remove("1234", "4567", "8910")
|
|
73
|
+
mock_trainml._query.assert_called_once_with(
|
|
74
|
+
"/provider/1234/region/4567/service/8910", "DELETE", {}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
@mark.asyncio
|
|
78
|
+
async def test_create_service(self, services, mock_trainml):
|
|
79
|
+
requested_config = dict(
|
|
80
|
+
provider_uuid="provider-id-1",
|
|
81
|
+
region_uuid="region-id-1",
|
|
82
|
+
name="On-Prem Service",
|
|
83
|
+
type="https",
|
|
84
|
+
public=False,
|
|
85
|
+
)
|
|
86
|
+
expected_payload = dict(
|
|
87
|
+
name="On-Prem Service",
|
|
88
|
+
type="https",
|
|
89
|
+
public=False,
|
|
90
|
+
)
|
|
91
|
+
api_response = {
|
|
92
|
+
"provider_uuid": "provider-id-1",
|
|
93
|
+
"region_uuid": "region-id-1",
|
|
94
|
+
"service_id": "service-id-1",
|
|
95
|
+
"name": "On-Prem Service",
|
|
96
|
+
"type": "https",
|
|
97
|
+
"public": False,
|
|
98
|
+
"hostname": "app1.proximl.cloud",
|
|
99
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
103
|
+
response = await services.create(**requested_config)
|
|
104
|
+
mock_trainml._query.assert_called_once_with(
|
|
105
|
+
"/provider/provider-id-1/region/region-id-1/service",
|
|
106
|
+
"POST",
|
|
107
|
+
None,
|
|
108
|
+
expected_payload,
|
|
109
|
+
)
|
|
110
|
+
assert response.id == "service-id-1"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class serviceTests:
|
|
114
|
+
def test_service_properties(self, service):
|
|
115
|
+
assert isinstance(service.id, str)
|
|
116
|
+
assert isinstance(service.provider_uuid, str)
|
|
117
|
+
assert isinstance(service.region_uuid, str)
|
|
118
|
+
assert isinstance(service.public, bool)
|
|
119
|
+
assert isinstance(service.name, str)
|
|
120
|
+
assert isinstance(service.hostname, str)
|
|
121
|
+
assert isinstance(service.type, str)
|
|
122
|
+
|
|
123
|
+
def test_service_str(self, service):
|
|
124
|
+
string = str(service)
|
|
125
|
+
regex = r"^{.*\"service_id\": \"" + service.id + r"\".*}$"
|
|
126
|
+
assert isinstance(string, str)
|
|
127
|
+
assert re.match(regex, string)
|
|
128
|
+
|
|
129
|
+
def test_service_repr(self, service):
|
|
130
|
+
string = repr(service)
|
|
131
|
+
regex = r"^Service\( trainml , \*\*{.*'service_id': '" + service.id + r"'.*}\)$"
|
|
132
|
+
assert isinstance(string, str)
|
|
133
|
+
assert re.match(regex, string)
|
|
134
|
+
|
|
135
|
+
def test_service_bool(self, service, mock_trainml):
|
|
136
|
+
empty_service = specimen.Service(mock_trainml)
|
|
137
|
+
assert bool(service)
|
|
138
|
+
assert not bool(empty_service)
|
|
139
|
+
|
|
140
|
+
@mark.asyncio
|
|
141
|
+
async def test_service_remove(self, service, mock_trainml):
|
|
142
|
+
api_response = dict()
|
|
143
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
144
|
+
await service.remove()
|
|
145
|
+
mock_trainml._query.assert_called_once_with(
|
|
146
|
+
"/provider/1/region/a/service/x", "DELETE"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
@mark.asyncio
|
|
150
|
+
async def test_service_refresh(self, service, mock_trainml):
|
|
151
|
+
api_response = {
|
|
152
|
+
"provider_uuid": "provider-id-1",
|
|
153
|
+
"region_uuid": "region-id-1",
|
|
154
|
+
"service_id": "service-id-1",
|
|
155
|
+
"name": "On-Prem Service",
|
|
156
|
+
"type": "https",
|
|
157
|
+
"public": False,
|
|
158
|
+
"hostname": "app1.proximl.cloud",
|
|
159
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
160
|
+
}
|
|
161
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
162
|
+
response = await service.refresh()
|
|
163
|
+
mock_trainml._query.assert_called_once_with(
|
|
164
|
+
f"/provider/1/region/a/service/x", "GET"
|
|
165
|
+
)
|
|
166
|
+
assert service.id == "service-id-1"
|
|
167
|
+
assert response.id == "service-id-1"
|
|
@@ -17,7 +17,7 @@ from trainml.projects import (
|
|
|
17
17
|
Projects,
|
|
18
18
|
Project,
|
|
19
19
|
ProjectDatastore,
|
|
20
|
-
|
|
20
|
+
ProjectService,
|
|
21
21
|
)
|
|
22
22
|
from trainml.cloudbender import Cloudbender
|
|
23
23
|
from trainml.cloudbender.providers import Provider, Providers
|
|
@@ -25,7 +25,7 @@ from trainml.cloudbender.regions import Region, Regions
|
|
|
25
25
|
from trainml.cloudbender.nodes import Node, Nodes
|
|
26
26
|
from trainml.cloudbender.devices import Device, Devices
|
|
27
27
|
from trainml.cloudbender.datastores import Datastore, Datastores
|
|
28
|
-
from trainml.cloudbender.
|
|
28
|
+
from trainml.cloudbender.services import Service, Services
|
|
29
29
|
from trainml.cloudbender.device_configs import DeviceConfig, DeviceConfigs
|
|
30
30
|
|
|
31
31
|
|
|
@@ -887,27 +887,27 @@ def mock_project_datastores():
|
|
|
887
887
|
|
|
888
888
|
|
|
889
889
|
@fixture(scope="session")
|
|
890
|
-
def
|
|
890
|
+
def mock_services():
|
|
891
891
|
trainml = Mock()
|
|
892
892
|
yield [
|
|
893
|
-
|
|
893
|
+
Service(
|
|
894
894
|
trainml,
|
|
895
895
|
**{
|
|
896
896
|
"provider_uuid": "prov-id-1",
|
|
897
897
|
"region_uuid": "reg-id-1",
|
|
898
|
-
"
|
|
898
|
+
"service_id": "res-id-1",
|
|
899
899
|
"type": "port",
|
|
900
900
|
"name": "On-Prem Service A",
|
|
901
901
|
"resource": "8001",
|
|
902
902
|
"hostname": "service-a.local",
|
|
903
903
|
},
|
|
904
904
|
),
|
|
905
|
-
|
|
905
|
+
Service(
|
|
906
906
|
trainml,
|
|
907
907
|
**{
|
|
908
908
|
"provider_uuid": "prov-id-2",
|
|
909
909
|
"region_uuid": "reg-id-2",
|
|
910
|
-
"
|
|
910
|
+
"service_id": "res-id-2",
|
|
911
911
|
"type": "port",
|
|
912
912
|
"name": "Cloud Service B",
|
|
913
913
|
"resource": "8001",
|
|
@@ -918,10 +918,10 @@ def mock_reservations():
|
|
|
918
918
|
|
|
919
919
|
|
|
920
920
|
@fixture(scope="session")
|
|
921
|
-
def
|
|
921
|
+
def mock_project_services():
|
|
922
922
|
trainml = Mock()
|
|
923
923
|
yield [
|
|
924
|
-
|
|
924
|
+
ProjectService(
|
|
925
925
|
trainml,
|
|
926
926
|
**{
|
|
927
927
|
"project_uuid": "proj-id-1",
|
|
@@ -933,7 +933,7 @@ def mock_project_reservations():
|
|
|
933
933
|
"hostname": "service-a.local",
|
|
934
934
|
},
|
|
935
935
|
),
|
|
936
|
-
|
|
936
|
+
ProjectService(
|
|
937
937
|
trainml,
|
|
938
938
|
**{
|
|
939
939
|
"project_uuid": "proj-id-1",
|
|
@@ -990,7 +990,7 @@ def mock_trainml(
|
|
|
990
990
|
mock_nodes,
|
|
991
991
|
mock_devices,
|
|
992
992
|
mock_datastores,
|
|
993
|
-
|
|
993
|
+
mock_services,
|
|
994
994
|
mock_device_configs,
|
|
995
995
|
):
|
|
996
996
|
trainml = create_autospec(TrainML)
|
|
@@ -1028,8 +1028,8 @@ def mock_trainml(
|
|
|
1028
1028
|
trainml.cloudbender.devices.list = AsyncMock(return_value=mock_devices)
|
|
1029
1029
|
trainml.cloudbender.datastores = create_autospec(Datastores)
|
|
1030
1030
|
trainml.cloudbender.datastores.list = AsyncMock(return_value=mock_datastores)
|
|
1031
|
-
trainml.cloudbender.
|
|
1032
|
-
trainml.cloudbender.
|
|
1031
|
+
trainml.cloudbender.services = create_autospec(Services)
|
|
1032
|
+
trainml.cloudbender.services.list = AsyncMock(return_value=mock_services)
|
|
1033
1033
|
trainml.cloudbender.device_configs = create_autospec(DeviceConfigs)
|
|
1034
1034
|
trainml.cloudbender.device_configs.list = AsyncMock(
|
|
1035
1035
|
return_value=mock_device_configs
|