trainml 0.5.4__tar.gz → 0.5.6__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.4/trainml.egg-info → trainml-0.5.6}/PKG-INFO +1 -1
- {trainml-0.5.4 → trainml-0.5.6}/pyproject.toml +5 -6
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/test_checkpoints_integration.py +7 -5
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/test_datasets_integration.py +4 -5
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/test_jobs_integration.py +40 -2
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/test_models_integration.py +8 -10
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/test_projects_integration.py +2 -6
- trainml-0.5.6/tests/integration/test_volumes_integration.py +100 -0
- trainml-0.5.6/tests/unit/cli/cloudbender/test_cli_reservation_unit.py +34 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/test_cli_project_unit.py +5 -9
- trainml-0.5.6/tests/unit/cli/test_cli_volume_unit.py +20 -0
- trainml-0.5.6/tests/unit/cloudbender/test_services_unit.py +161 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/conftest.py +94 -21
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_projects_unit.py +34 -48
- trainml-0.5.6/tests/unit/test_volumes_unit.py +447 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/__init__.py +1 -1
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/__init__.py +3 -6
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/cloudbender/__init__.py +1 -1
- trainml-0.5.6/trainml/cli/cloudbender/service.py +129 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/project.py +10 -15
- trainml-0.5.6/trainml/cli/volume.py +235 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/cloudbender.py +2 -2
- trainml-0.5.4/trainml/cloudbender/reservations.py → trainml-0.5.6/trainml/cloudbender/services.py +28 -39
- {trainml-0.5.4 → trainml-0.5.6}/trainml/exceptions.py +21 -12
- {trainml-0.5.4 → trainml-0.5.6}/trainml/jobs.py +36 -39
- {trainml-0.5.4 → trainml-0.5.6}/trainml/projects.py +19 -30
- {trainml-0.5.4 → trainml-0.5.6}/trainml/trainml.py +7 -15
- trainml-0.5.6/trainml/volumes.py +255 -0
- {trainml-0.5.4 → trainml-0.5.6/trainml.egg-info}/PKG-INFO +1 -1
- {trainml-0.5.4 → trainml-0.5.6}/trainml.egg-info/SOURCES.txt +8 -3
- trainml-0.5.4/tests/unit/cli/cloudbender/test_cli_reservation_unit.py +0 -38
- trainml-0.5.4/tests/unit/cloudbender/test_reservations_unit.py +0 -173
- trainml-0.5.4/trainml/cli/cloudbender/reservation.py +0 -159
- {trainml-0.5.4 → trainml-0.5.6}/LICENSE +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/README.md +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/examples/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/examples/create_dataset_and_training_job.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/examples/local_storage.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/examples/training_inference_pipeline.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/setup.cfg +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/setup.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/cloudbender/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/cloudbender/test_providers_integration.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/conftest.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/test_environments_integration.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/integration/test_gpu_types_integration.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/cloudbender/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_datastore_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_device_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_node_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_provider_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_region_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/conftest.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/test_cli_checkpoint_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/test_cli_datasets_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/test_cli_environment_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/test_cli_gpu_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/test_cli_job_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cli/test_cli_model_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cloudbender/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cloudbender/test_datastores_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cloudbender/test_device_configs_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cloudbender/test_devices_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cloudbender/test_nodes_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cloudbender/test_providers_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/cloudbender/test_regions_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_auth.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_checkpoints_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_connections_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_datasets_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_environments_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_exceptions.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_gpu_types_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_jobs_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_models_unit.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/tests/unit/test_trainml.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/__main__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/auth.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/checkpoints.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/checkpoint.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/cloudbender/datastore.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/cloudbender/device.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/cloudbender/node.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/cloudbender/provider.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/cloudbender/region.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/connection.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/dataset.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/environment.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/gpu.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/job/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/job/create.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cli/model.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/__init__.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/datastores.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/device_configs.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/devices.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/nodes.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/providers.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/cloudbender/regions.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/connections.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/datasets.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/environments.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/gpu_types.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml/models.py +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml.egg-info/dependency_links.txt +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml.egg-info/entry_points.txt +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml.egg-info/requires.txt +0 -0
- {trainml-0.5.4 → trainml-0.5.6}/trainml.egg-info/top_level.txt +0 -0
|
@@ -3,9 +3,7 @@ python_files = "test_*"
|
|
|
3
3
|
python_classes = "*Tests"
|
|
4
4
|
python_functions = "test_*"
|
|
5
5
|
asyncio_mode = "auto"
|
|
6
|
-
testpaths = [
|
|
7
|
-
"tests",
|
|
8
|
-
]
|
|
6
|
+
testpaths = ["tests"]
|
|
9
7
|
markers = [
|
|
10
8
|
"create: Tests that create resources on the trainml platform",
|
|
11
9
|
"gpu_types: Gpu Type tests",
|
|
@@ -13,6 +11,7 @@ markers = [
|
|
|
13
11
|
"datasets: Datasets tests",
|
|
14
12
|
"models: Models tests",
|
|
15
13
|
"checkpoints: Checkpoints tests",
|
|
14
|
+
"volumes: Volumes tests",
|
|
16
15
|
"jobs: Jobs tests",
|
|
17
16
|
"connections: Connections tests",
|
|
18
17
|
"projects: Projects tests",
|
|
@@ -22,10 +21,10 @@ markers = [
|
|
|
22
21
|
"nodes: Nodes tests",
|
|
23
22
|
"devices: Devices tests",
|
|
24
23
|
"datastores: Datastores tests",
|
|
25
|
-
"
|
|
24
|
+
"services: Services tests",
|
|
26
25
|
"device_configs: DeviceConfigs tests",
|
|
27
26
|
"unit: All unit tests (no trainML environment required)",
|
|
28
27
|
"integration: All integration tests (trainML environment required)",
|
|
29
28
|
"sdk: All tests of the SDK",
|
|
30
|
-
"cli: All test of the cli"
|
|
31
|
-
]
|
|
29
|
+
"cli: All test of the cli",
|
|
30
|
+
]
|
|
@@ -54,18 +54,20 @@ class GetCheckpointTests:
|
|
|
54
54
|
|
|
55
55
|
@mark.create
|
|
56
56
|
@mark.asyncio
|
|
57
|
-
async def
|
|
57
|
+
async def test_checkpoint_wasabi(trainml, capsys):
|
|
58
58
|
checkpoint = await trainml.checkpoints.create(
|
|
59
|
-
name="CLI Automated
|
|
60
|
-
source_type="
|
|
61
|
-
source_uri="s3://trainml-
|
|
59
|
+
name="CLI Automated Wasabi",
|
|
60
|
+
source_type="wasabi",
|
|
61
|
+
source_uri="s3://trainml-example/models/trainml-examples",
|
|
62
|
+
capacity="10G",
|
|
63
|
+
source_options=dict(endpoint_url="https://s3.wasabisys.com"),
|
|
62
64
|
)
|
|
63
65
|
checkpoint = await checkpoint.wait_for("ready", 300)
|
|
64
66
|
status = checkpoint.status
|
|
65
67
|
size = checkpoint.size
|
|
66
68
|
await checkpoint.remove()
|
|
67
69
|
assert status == "ready"
|
|
68
|
-
assert size >=
|
|
70
|
+
assert size >= 500000
|
|
69
71
|
|
|
70
72
|
|
|
71
73
|
@mark.create
|
|
@@ -13,8 +13,9 @@ class GetDatasetTests:
|
|
|
13
13
|
async def dataset(self, trainml):
|
|
14
14
|
dataset = await trainml.datasets.create(
|
|
15
15
|
name="CLI Automated",
|
|
16
|
-
source_type="
|
|
17
|
-
source_uri="s3://trainml-
|
|
16
|
+
source_type="wasabi",
|
|
17
|
+
source_uri="s3://trainml-example/input/cifar-10/cifar-10-batches-bin",
|
|
18
|
+
source_options=dict(endpoint_url="https://s3.wasabisys.com"),
|
|
18
19
|
)
|
|
19
20
|
dataset = await dataset.wait_for("ready", 300)
|
|
20
21
|
yield dataset
|
|
@@ -48,9 +49,7 @@ class GetDatasetTests:
|
|
|
48
49
|
async def test_dataset_repr(self, dataset):
|
|
49
50
|
string = repr(dataset)
|
|
50
51
|
regex = (
|
|
51
|
-
r"^Dataset\( trainml , \*\*{.*'dataset_uuid': '"
|
|
52
|
-
+ dataset.id
|
|
53
|
-
+ r"'.*}\)$"
|
|
52
|
+
r"^Dataset\( trainml , \*\*{.*'dataset_uuid': '" + dataset.id + r"'.*}\)$"
|
|
54
53
|
)
|
|
55
54
|
assert isinstance(string, str)
|
|
56
55
|
assert re.match(regex, string)
|
|
@@ -269,7 +269,7 @@ class JobAPIDataValidationTests:
|
|
|
269
269
|
),
|
|
270
270
|
)
|
|
271
271
|
assert (
|
|
272
|
-
"Invalid Request -
|
|
272
|
+
"Invalid Request - output_type invalid for Notebook and Endpoint jobs"
|
|
273
273
|
in error.value.message
|
|
274
274
|
)
|
|
275
275
|
|
|
@@ -298,7 +298,45 @@ class JobAPIDataValidationTests:
|
|
|
298
298
|
),
|
|
299
299
|
)
|
|
300
300
|
assert (
|
|
301
|
-
"Invalid Request -
|
|
301
|
+
"Invalid Request - output_type invalid for Notebook and Endpoint jobs"
|
|
302
|
+
in error.value.message
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
async def test_invalid_volumes_for_training(self, trainml):
|
|
306
|
+
with raises(ApiError) as error:
|
|
307
|
+
await trainml.jobs.create(
|
|
308
|
+
name="Invalid Volumes for Training",
|
|
309
|
+
type="training",
|
|
310
|
+
gpu_types=["rtx3090"],
|
|
311
|
+
disk_size=10,
|
|
312
|
+
data=dict(
|
|
313
|
+
output_uri="s3://trainml-examples/output/resnet_cifar10",
|
|
314
|
+
output_type="aws",
|
|
315
|
+
volumes=["volume-id"],
|
|
316
|
+
),
|
|
317
|
+
workers=["python train.py"],
|
|
318
|
+
)
|
|
319
|
+
assert (
|
|
320
|
+
"Invalid Request - Only Notebook and Endpoint job types can use writable volumes"
|
|
321
|
+
in error.value.message
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
async def test_invalid_volumes_for_inference(self, trainml):
|
|
325
|
+
with raises(ApiError) as error:
|
|
326
|
+
await trainml.jobs.create(
|
|
327
|
+
name="Invalid Volumes for Inference",
|
|
328
|
+
type="inference",
|
|
329
|
+
gpu_types=["rtx3090"],
|
|
330
|
+
disk_size=10,
|
|
331
|
+
data=dict(
|
|
332
|
+
output_uri="s3://trainml-examples/output/resnet_cifar10",
|
|
333
|
+
output_type="aws",
|
|
334
|
+
volumes=["volume-id"],
|
|
335
|
+
),
|
|
336
|
+
workers=["python predict.py"],
|
|
337
|
+
)
|
|
338
|
+
assert (
|
|
339
|
+
"Invalid Request - Only Notebook and Endpoint job types can use writable volumes"
|
|
302
340
|
in error.value.message
|
|
303
341
|
)
|
|
304
342
|
|
|
@@ -43,29 +43,27 @@ class GetModelTests:
|
|
|
43
43
|
|
|
44
44
|
async def test_model_repr(self, model):
|
|
45
45
|
string = repr(model)
|
|
46
|
-
regex = (
|
|
47
|
-
r"^Model\( trainml , \*\*{.*'model_uuid': '"
|
|
48
|
-
+ model.id
|
|
49
|
-
+ r"'.*}\)$"
|
|
50
|
-
)
|
|
46
|
+
regex = r"^Model\( trainml , \*\*{.*'model_uuid': '" + model.id + r"'.*}\)$"
|
|
51
47
|
assert isinstance(string, str)
|
|
52
48
|
assert re.match(regex, string)
|
|
53
49
|
|
|
54
50
|
|
|
55
51
|
@mark.create
|
|
56
52
|
@mark.asyncio
|
|
57
|
-
async def
|
|
53
|
+
async def test_model_wasabi(trainml, capsys):
|
|
58
54
|
model = await trainml.models.create(
|
|
59
|
-
name="CLI Automated
|
|
60
|
-
source_type="
|
|
61
|
-
source_uri="s3://trainml-
|
|
55
|
+
name="CLI Automated Wasabi",
|
|
56
|
+
source_type="wasabi",
|
|
57
|
+
source_uri="s3://trainml-example/models/trainml-examples",
|
|
58
|
+
capacity="10G",
|
|
59
|
+
source_options=dict(endpoint_url="https://s3.wasabisys.com"),
|
|
62
60
|
)
|
|
63
61
|
model = await model.wait_for("ready", 300)
|
|
64
62
|
status = model.status
|
|
65
63
|
size = model.size
|
|
66
64
|
await model.remove()
|
|
67
65
|
assert status == "ready"
|
|
68
|
-
assert size >=
|
|
66
|
+
assert size >= 500000
|
|
69
67
|
|
|
70
68
|
|
|
71
69
|
@mark.create
|
|
@@ -11,9 +11,7 @@ pytestmark = [mark.sdk, mark.integration, mark.projects]
|
|
|
11
11
|
class GetProjectsTests:
|
|
12
12
|
@fixture(scope="class")
|
|
13
13
|
async def project(self, trainml):
|
|
14
|
-
project = await trainml.projects.create(
|
|
15
|
-
name="New Project", copy_keys=False
|
|
16
|
-
)
|
|
14
|
+
project = await trainml.projects.create(name="New Project", copy_keys=False)
|
|
17
15
|
yield project
|
|
18
16
|
await project.remove()
|
|
19
17
|
|
|
@@ -41,8 +39,6 @@ class GetProjectsTests:
|
|
|
41
39
|
|
|
42
40
|
async def test_project_repr(self, project):
|
|
43
41
|
string = repr(project)
|
|
44
|
-
regex = (
|
|
45
|
-
r"^Project\( trainml , \*\*{.*'id': '" + project.id + r"'.*}\)$"
|
|
46
|
-
)
|
|
42
|
+
regex = r"^Project\( trainml , \*\*{.*'id': '" + project.id + r"'.*}\)$"
|
|
47
43
|
assert isinstance(string, str)
|
|
48
44
|
assert re.match(regex, string)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
import asyncio
|
|
4
|
+
from pytest import mark, fixture
|
|
5
|
+
|
|
6
|
+
pytestmark = [mark.sdk, mark.integration, mark.volumes]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@mark.create
|
|
10
|
+
@mark.asyncio
|
|
11
|
+
class GetVolumeTests:
|
|
12
|
+
@fixture(scope="class")
|
|
13
|
+
async def volume(self, trainml):
|
|
14
|
+
volume = await trainml.volumes.create(
|
|
15
|
+
name="CLI Automated",
|
|
16
|
+
source_type="git",
|
|
17
|
+
source_uri="git@github.com:trainML/environment-tests.git",
|
|
18
|
+
capacity="10G",
|
|
19
|
+
)
|
|
20
|
+
volume = await volume.wait_for("ready", 120)
|
|
21
|
+
yield volume
|
|
22
|
+
await volume.remove()
|
|
23
|
+
volume = await volume.wait_for("archived", 60)
|
|
24
|
+
|
|
25
|
+
async def test_get_volumes(self, trainml, volume):
|
|
26
|
+
volumes = await trainml.volumes.list()
|
|
27
|
+
assert len(volumes) > 0
|
|
28
|
+
|
|
29
|
+
async def test_get_volume(self, trainml, volume):
|
|
30
|
+
response = await trainml.volumes.get(volume.id)
|
|
31
|
+
assert response.id == volume.id
|
|
32
|
+
|
|
33
|
+
async def test_volume_properties(self, volume):
|
|
34
|
+
assert isinstance(volume.id, str)
|
|
35
|
+
assert isinstance(volume.status, str)
|
|
36
|
+
assert isinstance(volume.name, str)
|
|
37
|
+
assert isinstance(volume.capacity, str)
|
|
38
|
+
assert isinstance(volume.used_size, int)
|
|
39
|
+
assert isinstance(volume.billed_size, int)
|
|
40
|
+
|
|
41
|
+
async def test_volume_str(self, volume):
|
|
42
|
+
string = str(volume)
|
|
43
|
+
regex = r"^{.*\"id\": \"" + volume.id + r"\".*}$"
|
|
44
|
+
assert isinstance(string, str)
|
|
45
|
+
assert re.match(regex, string)
|
|
46
|
+
|
|
47
|
+
async def test_volume_repr(self, volume):
|
|
48
|
+
string = repr(volume)
|
|
49
|
+
regex = r"^Volume\( trainml , \*\*{.*'id': '" + volume.id + r"'.*}\)$"
|
|
50
|
+
assert isinstance(string, str)
|
|
51
|
+
assert re.match(regex, string)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@mark.create
|
|
55
|
+
@mark.asyncio
|
|
56
|
+
async def test_volume_wasabi(trainml, capsys):
|
|
57
|
+
volume = await trainml.volumes.create(
|
|
58
|
+
name="CLI Automated Wasabi",
|
|
59
|
+
source_type="wasabi",
|
|
60
|
+
source_uri="s3://trainml-example/models/trainml-examples",
|
|
61
|
+
capacity="10G",
|
|
62
|
+
source_options=dict(endpoint_url="https://s3.wasabisys.com"),
|
|
63
|
+
)
|
|
64
|
+
volume = await volume.wait_for("ready", 300)
|
|
65
|
+
status = volume.status
|
|
66
|
+
billed_size = volume.billed_size
|
|
67
|
+
used_size = volume.used_size
|
|
68
|
+
await volume.remove()
|
|
69
|
+
assert status == "ready"
|
|
70
|
+
assert billed_size >= 10000000
|
|
71
|
+
assert used_size >= 500000
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@mark.create
|
|
75
|
+
@mark.asyncio
|
|
76
|
+
async def test_volume_local(trainml, capsys):
|
|
77
|
+
volume = await trainml.volumes.create(
|
|
78
|
+
name="CLI Automated Local",
|
|
79
|
+
source_type="local",
|
|
80
|
+
source_uri="~/tensorflow-model",
|
|
81
|
+
capacity="10G",
|
|
82
|
+
)
|
|
83
|
+
attach_task = asyncio.create_task(volume.attach())
|
|
84
|
+
connect_task = asyncio.create_task(volume.connect())
|
|
85
|
+
await asyncio.gather(attach_task, connect_task)
|
|
86
|
+
await volume.disconnect()
|
|
87
|
+
await volume.refresh()
|
|
88
|
+
status = volume.status
|
|
89
|
+
billed_size = volume.billed_size
|
|
90
|
+
used_size = volume.used_size
|
|
91
|
+
await volume.remove()
|
|
92
|
+
assert status == "ready"
|
|
93
|
+
assert billed_size >= 10000000
|
|
94
|
+
assert used_size >= 1000000
|
|
95
|
+
captured = capsys.readouterr()
|
|
96
|
+
sys.stdout.write(captured.out)
|
|
97
|
+
sys.stderr.write(captured.err)
|
|
98
|
+
assert "Starting data upload from local" in captured.out
|
|
99
|
+
assert "official/LICENSE 11456 bytes" in captured.out
|
|
100
|
+
assert "Upload complete" in captured.out
|
|
@@ -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,20 @@
|
|
|
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.volumes]
|
|
8
|
+
|
|
9
|
+
from trainml.cli import volume as specimen
|
|
10
|
+
from trainml.volumes import Volume
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_list(runner, mock_my_volumes):
|
|
14
|
+
with patch("trainml.cli.TrainML", new=AsyncMock) as mock_trainml:
|
|
15
|
+
mock_trainml.volumes = AsyncMock()
|
|
16
|
+
mock_trainml.volumes.list = AsyncMock(return_value=mock_my_volumes)
|
|
17
|
+
result = runner.invoke(specimen, ["list"])
|
|
18
|
+
print(result)
|
|
19
|
+
assert result.exit_code == 0
|
|
20
|
+
mock_trainml.volumes.list.assert_called_once()
|
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
public=False,
|
|
32
|
+
hostname="app1.proximl.cloud",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RegionsTests:
|
|
37
|
+
@mark.asyncio
|
|
38
|
+
async def test_get_service(
|
|
39
|
+
self,
|
|
40
|
+
services,
|
|
41
|
+
mock_trainml,
|
|
42
|
+
):
|
|
43
|
+
api_response = dict()
|
|
44
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
45
|
+
await services.get("1234", "5687", "91011")
|
|
46
|
+
mock_trainml._query.assert_called_once_with(
|
|
47
|
+
"/provider/1234/region/5687/service/91011", "GET", {}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@mark.asyncio
|
|
51
|
+
async def test_list_services(
|
|
52
|
+
self,
|
|
53
|
+
services,
|
|
54
|
+
mock_trainml,
|
|
55
|
+
):
|
|
56
|
+
api_response = dict()
|
|
57
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
58
|
+
await services.list("1234", "5687")
|
|
59
|
+
mock_trainml._query.assert_called_once_with(
|
|
60
|
+
"/provider/1234/region/5687/service", "GET", {}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
@mark.asyncio
|
|
64
|
+
async def test_remove_service(
|
|
65
|
+
self,
|
|
66
|
+
services,
|
|
67
|
+
mock_trainml,
|
|
68
|
+
):
|
|
69
|
+
api_response = dict()
|
|
70
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
71
|
+
await services.remove("1234", "4567", "8910")
|
|
72
|
+
mock_trainml._query.assert_called_once_with(
|
|
73
|
+
"/provider/1234/region/4567/service/8910", "DELETE", {}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@mark.asyncio
|
|
77
|
+
async def test_create_service(self, services, mock_trainml):
|
|
78
|
+
requested_config = dict(
|
|
79
|
+
provider_uuid="provider-id-1",
|
|
80
|
+
region_uuid="region-id-1",
|
|
81
|
+
name="On-Prem Service",
|
|
82
|
+
public=False,
|
|
83
|
+
)
|
|
84
|
+
expected_payload = dict(
|
|
85
|
+
name="On-Prem Service",
|
|
86
|
+
public=False,
|
|
87
|
+
)
|
|
88
|
+
api_response = {
|
|
89
|
+
"provider_uuid": "provider-id-1",
|
|
90
|
+
"region_uuid": "region-id-1",
|
|
91
|
+
"service_id": "service-id-1",
|
|
92
|
+
"name": "On-Prem Service",
|
|
93
|
+
"public": False,
|
|
94
|
+
"hostname": "app1.proximl.cloud",
|
|
95
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
99
|
+
response = await services.create(**requested_config)
|
|
100
|
+
mock_trainml._query.assert_called_once_with(
|
|
101
|
+
"/provider/provider-id-1/region/region-id-1/service",
|
|
102
|
+
"POST",
|
|
103
|
+
None,
|
|
104
|
+
expected_payload,
|
|
105
|
+
)
|
|
106
|
+
assert response.id == "service-id-1"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class serviceTests:
|
|
110
|
+
def test_service_properties(self, service):
|
|
111
|
+
assert isinstance(service.id, str)
|
|
112
|
+
assert isinstance(service.provider_uuid, str)
|
|
113
|
+
assert isinstance(service.region_uuid, str)
|
|
114
|
+
assert isinstance(service.public, bool)
|
|
115
|
+
assert isinstance(service.name, str)
|
|
116
|
+
assert isinstance(service.hostname, str)
|
|
117
|
+
|
|
118
|
+
def test_service_str(self, service):
|
|
119
|
+
string = str(service)
|
|
120
|
+
regex = r"^{.*\"service_id\": \"" + service.id + r"\".*}$"
|
|
121
|
+
assert isinstance(string, str)
|
|
122
|
+
assert re.match(regex, string)
|
|
123
|
+
|
|
124
|
+
def test_service_repr(self, service):
|
|
125
|
+
string = repr(service)
|
|
126
|
+
regex = r"^Service\( trainml , \*\*{.*'service_id': '" + service.id + r"'.*}\)$"
|
|
127
|
+
assert isinstance(string, str)
|
|
128
|
+
assert re.match(regex, string)
|
|
129
|
+
|
|
130
|
+
def test_service_bool(self, service, mock_trainml):
|
|
131
|
+
empty_service = specimen.Service(mock_trainml)
|
|
132
|
+
assert bool(service)
|
|
133
|
+
assert not bool(empty_service)
|
|
134
|
+
|
|
135
|
+
@mark.asyncio
|
|
136
|
+
async def test_service_remove(self, service, mock_trainml):
|
|
137
|
+
api_response = dict()
|
|
138
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
139
|
+
await service.remove()
|
|
140
|
+
mock_trainml._query.assert_called_once_with(
|
|
141
|
+
"/provider/1/region/a/service/x", "DELETE"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@mark.asyncio
|
|
145
|
+
async def test_service_refresh(self, service, mock_trainml):
|
|
146
|
+
api_response = {
|
|
147
|
+
"provider_uuid": "provider-id-1",
|
|
148
|
+
"region_uuid": "region-id-1",
|
|
149
|
+
"service_id": "service-id-1",
|
|
150
|
+
"name": "On-Prem Service",
|
|
151
|
+
"public": False,
|
|
152
|
+
"hostname": "app1.proximl.cloud",
|
|
153
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
154
|
+
}
|
|
155
|
+
mock_trainml._query = AsyncMock(return_value=api_response)
|
|
156
|
+
response = await service.refresh()
|
|
157
|
+
mock_trainml._query.assert_called_once_with(
|
|
158
|
+
f"/provider/1/region/a/service/x", "GET"
|
|
159
|
+
)
|
|
160
|
+
assert service.id == "service-id-1"
|
|
161
|
+
assert response.id == "service-id-1"
|