trainml 0.5.5__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.
Files changed (111) hide show
  1. {trainml-0.5.5/trainml.egg-info → trainml-0.5.6}/PKG-INFO +1 -1
  2. {trainml-0.5.5 → trainml-0.5.6}/pyproject.toml +1 -1
  3. trainml-0.5.6/tests/unit/cli/cloudbender/test_cli_reservation_unit.py +34 -0
  4. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_project_unit.py +5 -9
  5. trainml-0.5.6/tests/unit/cloudbender/test_services_unit.py +161 -0
  6. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/conftest.py +13 -13
  7. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_projects_unit.py +34 -48
  8. {trainml-0.5.5 → trainml-0.5.6}/trainml/__init__.py +1 -1
  9. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/cloudbender/__init__.py +1 -1
  10. trainml-0.5.6/trainml/cli/cloudbender/service.py +129 -0
  11. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/project.py +10 -15
  12. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/cloudbender.py +2 -2
  13. trainml-0.5.5/trainml/cloudbender/reservations.py → trainml-0.5.6/trainml/cloudbender/services.py +28 -39
  14. {trainml-0.5.5 → trainml-0.5.6}/trainml/projects.py +19 -30
  15. {trainml-0.5.5 → trainml-0.5.6/trainml.egg-info}/PKG-INFO +1 -1
  16. {trainml-0.5.5 → trainml-0.5.6}/trainml.egg-info/SOURCES.txt +3 -3
  17. trainml-0.5.5/tests/unit/cli/cloudbender/test_cli_reservation_unit.py +0 -38
  18. trainml-0.5.5/tests/unit/cloudbender/test_reservations_unit.py +0 -173
  19. trainml-0.5.5/trainml/cli/cloudbender/reservation.py +0 -159
  20. {trainml-0.5.5 → trainml-0.5.6}/LICENSE +0 -0
  21. {trainml-0.5.5 → trainml-0.5.6}/README.md +0 -0
  22. {trainml-0.5.5 → trainml-0.5.6}/examples/__init__.py +0 -0
  23. {trainml-0.5.5 → trainml-0.5.6}/examples/create_dataset_and_training_job.py +0 -0
  24. {trainml-0.5.5 → trainml-0.5.6}/examples/local_storage.py +0 -0
  25. {trainml-0.5.5 → trainml-0.5.6}/examples/training_inference_pipeline.py +0 -0
  26. {trainml-0.5.5 → trainml-0.5.6}/setup.cfg +0 -0
  27. {trainml-0.5.5 → trainml-0.5.6}/setup.py +0 -0
  28. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/__init__.py +0 -0
  29. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/cloudbender/__init__.py +0 -0
  30. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/cloudbender/test_providers_integration.py +0 -0
  31. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/conftest.py +0 -0
  32. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_checkpoints_integration.py +0 -0
  33. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_datasets_integration.py +0 -0
  34. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_environments_integration.py +0 -0
  35. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_gpu_types_integration.py +0 -0
  36. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_jobs_integration.py +0 -0
  37. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_models_integration.py +0 -0
  38. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_projects_integration.py +0 -0
  39. {trainml-0.5.5 → trainml-0.5.6}/tests/integration/test_volumes_integration.py +0 -0
  40. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/__init__.py +0 -0
  41. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/__init__.py +0 -0
  42. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/cloudbender/__init__.py +0 -0
  43. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_datastore_unit.py +0 -0
  44. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_device_unit.py +0 -0
  45. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_node_unit.py +0 -0
  46. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_provider_unit.py +0 -0
  47. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/cloudbender/test_cli_region_unit.py +0 -0
  48. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/conftest.py +0 -0
  49. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_checkpoint_unit.py +0 -0
  50. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_datasets_unit.py +0 -0
  51. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_environment_unit.py +0 -0
  52. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_gpu_unit.py +0 -0
  53. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_job_unit.py +0 -0
  54. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_model_unit.py +0 -0
  55. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cli/test_cli_volume_unit.py +0 -0
  56. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cloudbender/__init__.py +0 -0
  57. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cloudbender/test_datastores_unit.py +0 -0
  58. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cloudbender/test_device_configs_unit.py +0 -0
  59. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cloudbender/test_devices_unit.py +0 -0
  60. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cloudbender/test_nodes_unit.py +0 -0
  61. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cloudbender/test_providers_unit.py +0 -0
  62. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/cloudbender/test_regions_unit.py +0 -0
  63. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_auth.py +0 -0
  64. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_checkpoints_unit.py +0 -0
  65. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_connections_unit.py +0 -0
  66. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_datasets_unit.py +0 -0
  67. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_environments_unit.py +0 -0
  68. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_exceptions.py +0 -0
  69. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_gpu_types_unit.py +0 -0
  70. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_jobs_unit.py +0 -0
  71. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_models_unit.py +0 -0
  72. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_trainml.py +0 -0
  73. {trainml-0.5.5 → trainml-0.5.6}/tests/unit/test_volumes_unit.py +0 -0
  74. {trainml-0.5.5 → trainml-0.5.6}/trainml/__main__.py +0 -0
  75. {trainml-0.5.5 → trainml-0.5.6}/trainml/auth.py +0 -0
  76. {trainml-0.5.5 → trainml-0.5.6}/trainml/checkpoints.py +0 -0
  77. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/__init__.py +0 -0
  78. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/checkpoint.py +0 -0
  79. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/cloudbender/datastore.py +0 -0
  80. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/cloudbender/device.py +0 -0
  81. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/cloudbender/node.py +0 -0
  82. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/cloudbender/provider.py +0 -0
  83. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/cloudbender/region.py +0 -0
  84. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/connection.py +0 -0
  85. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/dataset.py +0 -0
  86. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/environment.py +0 -0
  87. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/gpu.py +0 -0
  88. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/job/__init__.py +0 -0
  89. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/job/create.py +0 -0
  90. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/model.py +0 -0
  91. {trainml-0.5.5 → trainml-0.5.6}/trainml/cli/volume.py +0 -0
  92. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/__init__.py +0 -0
  93. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/datastores.py +0 -0
  94. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/device_configs.py +0 -0
  95. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/devices.py +0 -0
  96. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/nodes.py +0 -0
  97. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/providers.py +0 -0
  98. {trainml-0.5.5 → trainml-0.5.6}/trainml/cloudbender/regions.py +0 -0
  99. {trainml-0.5.5 → trainml-0.5.6}/trainml/connections.py +0 -0
  100. {trainml-0.5.5 → trainml-0.5.6}/trainml/datasets.py +0 -0
  101. {trainml-0.5.5 → trainml-0.5.6}/trainml/environments.py +0 -0
  102. {trainml-0.5.5 → trainml-0.5.6}/trainml/exceptions.py +0 -0
  103. {trainml-0.5.5 → trainml-0.5.6}/trainml/gpu_types.py +0 -0
  104. {trainml-0.5.5 → trainml-0.5.6}/trainml/jobs.py +0 -0
  105. {trainml-0.5.5 → trainml-0.5.6}/trainml/models.py +0 -0
  106. {trainml-0.5.5 → trainml-0.5.6}/trainml/trainml.py +0 -0
  107. {trainml-0.5.5 → trainml-0.5.6}/trainml/volumes.py +0 -0
  108. {trainml-0.5.5 → trainml-0.5.6}/trainml.egg-info/dependency_links.txt +0 -0
  109. {trainml-0.5.5 → trainml-0.5.6}/trainml.egg-info/entry_points.txt +0 -0
  110. {trainml-0.5.5 → trainml-0.5.6}/trainml.egg-info/requires.txt +0 -0
  111. {trainml-0.5.5 → trainml-0.5.6}/trainml.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trainml
3
- Version: 0.5.5
3
+ Version: 0.5.6
4
4
  Summary: trainML client SDK and command line utilities
5
5
  Home-page: https://github.com/trainML/trainml-cli
6
6
  Author: trainML
@@ -21,7 +21,7 @@ markers = [
21
21
  "nodes: Nodes tests",
22
22
  "devices: Devices tests",
23
23
  "datastores: Datastores tests",
24
- "reservations: Reservations tests",
24
+ "services: Services tests",
25
25
  "device_configs: DeviceConfigs tests",
26
26
  "unit: All unit tests (no trainML environment required)",
27
27
  "integration: All integration tests (trainML environment required)",
@@ -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 test_list_reservations(runner, mock_project_reservations):
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.list_reservations = AsyncMock(
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-reservations"])
39
+ result = runner.invoke(specimen, ["list-services"])
44
40
  print(result)
45
41
  assert result.exit_code == 0
46
- mock_project.list_reservations.assert_called_once()
42
+ mock_project.list_services.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"
@@ -17,7 +17,7 @@ from trainml.projects import (
17
17
  Projects,
18
18
  Project,
19
19
  ProjectDatastore,
20
- ProjectReservation,
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.reservations import Reservation, Reservations
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 mock_reservations():
890
+ def mock_services():
891
891
  trainml = Mock()
892
892
  yield [
893
- Reservation(
893
+ Service(
894
894
  trainml,
895
895
  **{
896
896
  "provider_uuid": "prov-id-1",
897
897
  "region_uuid": "reg-id-1",
898
- "reservation_id": "res-id-1",
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
- Reservation(
905
+ Service(
906
906
  trainml,
907
907
  **{
908
908
  "provider_uuid": "prov-id-2",
909
909
  "region_uuid": "reg-id-2",
910
- "reservation_id": "res-id-2",
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 mock_project_reservations():
921
+ def mock_project_services():
922
922
  trainml = Mock()
923
923
  yield [
924
- ProjectReservation(
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
- ProjectReservation(
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
- mock_reservations,
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.reservations = create_autospec(Reservations)
1032
- trainml.cloudbender.reservations.list = AsyncMock(return_value=mock_reservations)
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
@@ -49,11 +49,11 @@ def project_datastore(mock_trainml):
49
49
 
50
50
 
51
51
  @fixture
52
- def project_reservation(mock_trainml):
53
- yield specimen.ProjectReservation(
52
+ def project_service(mock_trainml):
53
+ yield specimen.ProjectService(
54
54
  mock_trainml,
55
55
  id="res-id-1",
56
- name="reservation 1",
56
+ name="service 1",
57
57
  project_uuid="proj-id-1",
58
58
  region_uuid="reg-id-1",
59
59
  type="port",
@@ -72,9 +72,7 @@ class ProjectsTests:
72
72
  api_response = dict()
73
73
  mock_trainml._query = AsyncMock(return_value=api_response)
74
74
  await projects.get("1234")
75
- mock_trainml._query.assert_called_once_with(
76
- "/project/1234", "GET", dict()
77
- )
75
+ mock_trainml._query.assert_called_once_with("/project/1234", "GET", dict())
78
76
 
79
77
  @mark.asyncio
80
78
  async def test_list_projects(
@@ -96,9 +94,7 @@ class ProjectsTests:
96
94
  api_response = dict()
97
95
  mock_trainml._query = AsyncMock(return_value=api_response)
98
96
  await projects.remove("4567")
99
- mock_trainml._query.assert_called_once_with(
100
- "/project/4567", "DELETE", dict()
101
- )
97
+ mock_trainml._query.assert_called_once_with("/project/4567", "DELETE", dict())
102
98
 
103
99
  @mark.asyncio
104
100
  async def test_create_project_simple(self, projects, mock_trainml):
@@ -156,36 +152,36 @@ class ProjectDatastoreTests:
156
152
  assert not bool(empty_project_datastore)
157
153
 
158
154
 
159
- class ProjectReservationTests:
160
- def test_project_reservation_properties(self, project_reservation):
161
- assert isinstance(project_reservation.id, str)
162
- assert isinstance(project_reservation.name, str)
163
- assert isinstance(project_reservation.project_uuid, str)
164
- assert isinstance(project_reservation.type, str)
165
- assert isinstance(project_reservation.hostname, str)
166
- assert isinstance(project_reservation.resource, str)
167
- assert isinstance(project_reservation.region_uuid, str)
155
+ class ProjectServiceTests:
156
+ def test_project_service_properties(self, project_service):
157
+ assert isinstance(project_service.id, str)
158
+ assert isinstance(project_service.name, str)
159
+ assert isinstance(project_service.project_uuid, str)
160
+ assert isinstance(project_service.type, str)
161
+ assert isinstance(project_service.hostname, str)
162
+ assert isinstance(project_service.resource, str)
163
+ assert isinstance(project_service.region_uuid, str)
168
164
 
169
- def test_project_reservation_str(self, project_reservation):
170
- string = str(project_reservation)
171
- regex = r"^{.*\"id\": \"" + project_reservation.id + r"\".*}$"
165
+ def test_project_service_str(self, project_service):
166
+ string = str(project_service)
167
+ regex = r"^{.*\"id\": \"" + project_service.id + r"\".*}$"
172
168
  assert isinstance(string, str)
173
169
  assert re.match(regex, string)
174
170
 
175
- def test_project_reservation_repr(self, project_reservation):
176
- string = repr(project_reservation)
171
+ def test_project_service_repr(self, project_service):
172
+ string = repr(project_service)
177
173
  regex = (
178
- r"^ProjectReservation\( trainml , \*\*{.*'id': '"
179
- + project_reservation.id
174
+ r"^ProjectService\( trainml , \*\*{.*'id': '"
175
+ + project_service.id
180
176
  + r"'.*}\)$"
181
177
  )
182
178
  assert isinstance(string, str)
183
179
  assert re.match(regex, string)
184
180
 
185
- def test_project_reservation_bool(self, project_reservation, mock_trainml):
186
- empty_project_reservation = specimen.ProjectReservation(mock_trainml)
187
- assert bool(project_reservation)
188
- assert not bool(empty_project_reservation)
181
+ def test_project_service_bool(self, project_service, mock_trainml):
182
+ empty_project_service = specimen.ProjectService(mock_trainml)
183
+ assert bool(project_service)
184
+ assert not bool(empty_project_service)
189
185
 
190
186
 
191
187
  class ProjectTests:
@@ -203,9 +199,7 @@ class ProjectTests:
203
199
 
204
200
  def test_project_repr(self, project):
205
201
  string = repr(project)
206
- regex = (
207
- r"^Project\( trainml , \*\*{.*'id': '" + project.id + r"'.*}\)$"
208
- )
202
+ regex = r"^Project\( trainml , \*\*{.*'id': '" + project.id + r"'.*}\)$"
209
203
  assert isinstance(string, str)
210
204
  assert re.match(regex, string)
211
205
 
@@ -226,18 +220,14 @@ class ProjectTests:
226
220
  api_response = dict()
227
221
  mock_trainml._query = AsyncMock(return_value=api_response)
228
222
  await project.refresh_datastores()
229
- mock_trainml._query.assert_called_once_with(
230
- "/project/1/datastores", "PATCH"
231
- )
223
+ mock_trainml._query.assert_called_once_with("/project/1/datastores", "PATCH")
232
224
 
233
225
  @mark.asyncio
234
- async def test_project_refresh_reservations(self, project, mock_trainml):
226
+ async def test_project_refresh_services(self, project, mock_trainml):
235
227
  api_response = dict()
236
228
  mock_trainml._query = AsyncMock(return_value=api_response)
237
- await project.refresh_reservations()
238
- mock_trainml._query.assert_called_once_with(
239
- "/project/1/reservations", "PATCH"
240
- )
229
+ await project.refresh_services()
230
+ mock_trainml._query.assert_called_once_with("/project/1/services", "PATCH")
241
231
 
242
232
  @mark.asyncio
243
233
  async def test_project_list_datastores(self, project, mock_trainml):
@@ -259,13 +249,11 @@ class ProjectTests:
259
249
  ]
260
250
  mock_trainml._query = AsyncMock(return_value=api_response)
261
251
  resp = await project.list_datastores()
262
- mock_trainml._query.assert_called_once_with(
263
- "/project/1/datastores", "GET"
264
- )
252
+ mock_trainml._query.assert_called_once_with("/project/1/datastores", "GET")
265
253
  assert len(resp) == 2
266
254
 
267
255
  @mark.asyncio
268
- async def test_project_list_reservations(self, project, mock_trainml):
256
+ async def test_project_list_services(self, project, mock_trainml):
269
257
  api_response = [
270
258
  {
271
259
  "project_uuid": "proj-id-1",
@@ -287,8 +275,6 @@ class ProjectTests:
287
275
  },
288
276
  ]
289
277
  mock_trainml._query = AsyncMock(return_value=api_response)
290
- resp = await project.list_reservations()
291
- mock_trainml._query.assert_called_once_with(
292
- "/project/1/reservations", "GET"
293
- )
278
+ resp = await project.list_services()
279
+ mock_trainml._query.assert_called_once_with("/project/1/services", "GET")
294
280
  assert len(resp) == 2
@@ -13,5 +13,5 @@ logging.basicConfig(
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- __version__ = "0.5.5"
16
+ __version__ = "0.5.6"
17
17
  __all__ = "TrainML"
@@ -15,4 +15,4 @@ from trainml.cli.cloudbender.region import region
15
15
  from trainml.cli.cloudbender.node import node
16
16
  from trainml.cli.cloudbender.device import device
17
17
  from trainml.cli.cloudbender.datastore import datastore
18
- from trainml.cli.cloudbender.reservation import reservation
18
+ from trainml.cli.cloudbender.service import service
@@ -0,0 +1,129 @@
1
+ import click
2
+ from trainml.cli import cli, pass_config, search_by_id_name
3
+ from trainml.cli.cloudbender import cloudbender
4
+
5
+
6
+ @cloudbender.group()
7
+ @pass_config
8
+ def service(config):
9
+ """trainML CloudBender service commands."""
10
+ pass
11
+
12
+
13
+ @service.command()
14
+ @click.option(
15
+ "--provider",
16
+ "-p",
17
+ type=click.STRING,
18
+ required=True,
19
+ help="The provider ID of the region.",
20
+ )
21
+ @click.option(
22
+ "--region",
23
+ "-r",
24
+ type=click.STRING,
25
+ required=True,
26
+ help="The region ID to list services for.",
27
+ )
28
+ @pass_config
29
+ def list(config, provider, region):
30
+ """List services."""
31
+ data = [
32
+ ["ID", "NAME", "HOSTNAME"],
33
+ [
34
+ "-" * 80,
35
+ "-" * 80,
36
+ "-" * 80,
37
+ ],
38
+ ]
39
+
40
+ services = config.trainml.run(
41
+ config.trainml.client.cloudbender.services.list(
42
+ provider_uuid=provider, region_uuid=region
43
+ )
44
+ )
45
+
46
+ for service in services:
47
+ data.append(
48
+ [
49
+ service.id,
50
+ service.name,
51
+ service.hostname,
52
+ ]
53
+ )
54
+
55
+ for row in data:
56
+ click.echo(
57
+ "{: >25.24} {: >29.28} {: >40.39}" "".format(*row),
58
+ file=config.stdout,
59
+ )
60
+
61
+
62
+ @service.command()
63
+ @click.option(
64
+ "--provider",
65
+ "-p",
66
+ type=click.STRING,
67
+ required=True,
68
+ help="The provider ID of the region.",
69
+ )
70
+ @click.option(
71
+ "--region",
72
+ "-r",
73
+ type=click.STRING,
74
+ required=True,
75
+ help="The region ID to create the service in.",
76
+ )
77
+ @click.option(
78
+ "--public/--no-public",
79
+ default=True,
80
+ show_default=True,
81
+ help="Service should be accessible from the public internet.",
82
+ )
83
+ @click.argument("name", type=click.STRING, required=True)
84
+ @pass_config
85
+ def create(config, provider, region, public, name):
86
+ """
87
+ Creates a service.
88
+ """
89
+ return config.trainml.run(
90
+ config.trainml.client.cloudbender.services.create(
91
+ provider_uuid=provider, region_uuid=region, name=name, public=public
92
+ )
93
+ )
94
+
95
+
96
+ @service.command()
97
+ @click.option(
98
+ "--provider",
99
+ "-p",
100
+ type=click.STRING,
101
+ required=True,
102
+ help="The provider ID of the region.",
103
+ )
104
+ @click.option(
105
+ "--region",
106
+ "-r",
107
+ type=click.STRING,
108
+ required=True,
109
+ help="The region ID to remove the service from.",
110
+ )
111
+ @click.argument("service", type=click.STRING)
112
+ @pass_config
113
+ def remove(config, provider, region, service):
114
+ """
115
+ Remove a service.
116
+
117
+ RESERVATION may be specified by name or ID, but ID is preferred.
118
+ """
119
+ services = config.trainml.run(
120
+ config.trainml.client.cloudbender.services.list(
121
+ provider_uuid=provider, region_uuid=region
122
+ )
123
+ )
124
+
125
+ found = search_by_id_name(service, services)
126
+ if None is found:
127
+ raise click.UsageError("Cannot find specified service.")
128
+
129
+ return config.trainml.run(found.remove())