proximl 0.5.4__py3-none-any.whl → 0.5.5__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.
tests/unit/conftest.py CHANGED
@@ -7,6 +7,7 @@ from proximl.proximl import ProxiML
7
7
  from proximl.auth import Auth
8
8
  from proximl.datasets import Dataset, Datasets
9
9
  from proximl.checkpoints import Checkpoint, Checkpoints
10
+ from proximl.volumes import Volume, Volumes
10
11
  from proximl.models import Model, Models
11
12
  from proximl.gpu_types import GpuType, GpuTypes
12
13
  from proximl.environments import Environment, Environments
@@ -258,6 +259,79 @@ def mock_models():
258
259
  ]
259
260
 
260
261
 
262
+ @fixture(scope="session")
263
+ def mock_my_volumes():
264
+ proximl = Mock()
265
+ yield [
266
+ Volume(
267
+ proximl,
268
+ id="1",
269
+ project_uuid="proj-id-1",
270
+ name="first one",
271
+ status="ready",
272
+ capacity="10G",
273
+ used_size=100000000,
274
+ billed_size=100000000,
275
+ createdAt="2020-12-31T23:59:59.000Z",
276
+ ),
277
+ Volume(
278
+ proximl,
279
+ id="2",
280
+ project_uuid="proj-id-1",
281
+ name="second one",
282
+ status="ready",
283
+ capacity="10G",
284
+ used_size=100000000,
285
+ billed_size=100000000,
286
+ createdAt="2021-01-01T00:00:01.000Z",
287
+ ),
288
+ Volume(
289
+ proximl,
290
+ id="3",
291
+ project_uuid="proj-id-1",
292
+ name="first one",
293
+ status="ready",
294
+ capacity="10G",
295
+ used_size=100000000,
296
+ billed_size=100000000,
297
+ createdAt="2021-01-01T00:00:01.000Z",
298
+ ),
299
+ Volume(
300
+ proximl,
301
+ id="4",
302
+ project_uuid="proj-id-1",
303
+ name="other one",
304
+ status="ready",
305
+ capacity="10G",
306
+ used_size=100000000,
307
+ billed_size=100000000,
308
+ createdAt="2020-12-31T23:59:59.000Z",
309
+ ),
310
+ Volume(
311
+ proximl,
312
+ id="5",
313
+ project_uuid="proj-id-1",
314
+ name="not ready",
315
+ status="new",
316
+ capacity="10G",
317
+ used_size=100000000,
318
+ billed_size=100000000,
319
+ createdAt="2021-01-01T00:00:01.000Z",
320
+ ),
321
+ Volume(
322
+ proximl,
323
+ id="6",
324
+ project_uuid="proj-id-1",
325
+ name="failed",
326
+ status="failed",
327
+ capacity="10G",
328
+ used_size=100000000,
329
+ billed_size=100000000,
330
+ createdAt="2021-01-01T00:00:01.000Z",
331
+ ),
332
+ ]
333
+
334
+
261
335
  @fixture(scope="session")
262
336
  def mock_gpu_types():
263
337
  proximl = Mock()
@@ -903,6 +977,9 @@ def mock_device_configs():
903
977
  def mock_proximl(
904
978
  mock_my_datasets,
905
979
  mock_public_datasets,
980
+ mock_my_checkpoints,
981
+ mock_public_checkpoints,
982
+ mock_my_volumes,
906
983
  mock_models,
907
984
  mock_gpu_types,
908
985
  mock_environments,
@@ -921,6 +998,7 @@ def mock_proximl(
921
998
  proximl.project = "proj-id-1"
922
999
  proximl.datasets = create_autospec(Datasets)
923
1000
  proximl.checkpoints = create_autospec(Checkpoints)
1001
+ proximl.volumes = create_autospec(Volumes)
924
1002
  proximl.models = create_autospec(Models)
925
1003
  proximl.gpu_types = create_autospec(GpuTypes)
926
1004
  proximl.environments = create_autospec(Environments)
@@ -930,10 +1008,9 @@ def mock_proximl(
930
1008
  proximl.datasets.list = AsyncMock(return_value=mock_my_datasets)
931
1009
  proximl.datasets.list_public = AsyncMock(return_value=mock_public_datasets)
932
1010
  proximl.checkpoints.list = AsyncMock(return_value=mock_my_checkpoints)
933
- proximl.checkpoints.list_public = AsyncMock(
934
- return_value=mock_public_checkpoints
935
- )
1011
+ proximl.checkpoints.list_public = AsyncMock(return_value=mock_public_checkpoints)
936
1012
  proximl.models.list = AsyncMock(return_value=mock_models)
1013
+ proximl.volumes.list = AsyncMock(return_value=mock_my_volumes)
937
1014
  proximl.gpu_types.list = AsyncMock(return_value=mock_gpu_types)
938
1015
  proximl.environments.list = AsyncMock(return_value=mock_environments)
939
1016
  proximl.jobs.list = AsyncMock(return_value=mock_jobs)
@@ -950,13 +1027,9 @@ def mock_proximl(
950
1027
  proximl.cloudbender.devices = create_autospec(Nodes)
951
1028
  proximl.cloudbender.devices.list = AsyncMock(return_value=mock_devices)
952
1029
  proximl.cloudbender.datastores = create_autospec(Datastores)
953
- proximl.cloudbender.datastores.list = AsyncMock(
954
- return_value=mock_datastores
955
- )
1030
+ proximl.cloudbender.datastores.list = AsyncMock(return_value=mock_datastores)
956
1031
  proximl.cloudbender.reservations = create_autospec(Reservations)
957
- proximl.cloudbender.reservations.list = AsyncMock(
958
- return_value=mock_reservations
959
- )
1032
+ proximl.cloudbender.reservations.list = AsyncMock(return_value=mock_reservations)
960
1033
  proximl.cloudbender.device_configs = create_autospec(DeviceConfigs)
961
1034
  proximl.cloudbender.device_configs.list = AsyncMock(
962
1035
  return_value=mock_device_configs
@@ -0,0 +1,447 @@
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 proximl.volumes as specimen
9
+ from proximl.exceptions import (
10
+ ApiError,
11
+ VolumeError,
12
+ SpecificationError,
13
+ ProxiMLException,
14
+ )
15
+
16
+ pytestmark = [mark.sdk, mark.unit, mark.volumes]
17
+
18
+
19
+ @fixture
20
+ def volumes(mock_proximl):
21
+ yield specimen.Volumes(mock_proximl)
22
+
23
+
24
+ @fixture
25
+ def volume(mock_proximl):
26
+ yield specimen.Volume(
27
+ mock_proximl,
28
+ id="1",
29
+ project_uuid="proj-id-1",
30
+ name="first one",
31
+ status="downloading",
32
+ capacity="10G",
33
+ used_size=100000000,
34
+ billed_size=100000000,
35
+ createdAt="2020-12-31T23:59:59.000Z",
36
+ )
37
+
38
+
39
+ class VolumesTests:
40
+ @mark.asyncio
41
+ async def test_get_volume(
42
+ self,
43
+ volumes,
44
+ mock_proximl,
45
+ ):
46
+ api_response = dict()
47
+ mock_proximl._query = AsyncMock(return_value=api_response)
48
+ await volumes.get("1234")
49
+ mock_proximl._query.assert_called_once_with("/volume/1234", "GET", dict())
50
+
51
+ @mark.asyncio
52
+ async def test_list_volumes(
53
+ self,
54
+ volumes,
55
+ mock_proximl,
56
+ ):
57
+ api_response = dict()
58
+ mock_proximl._query = AsyncMock(return_value=api_response)
59
+ await volumes.list()
60
+ mock_proximl._query.assert_called_once_with("/volume", "GET", dict())
61
+
62
+ @mark.asyncio
63
+ async def test_remove_volume(
64
+ self,
65
+ volumes,
66
+ mock_proximl,
67
+ ):
68
+ api_response = dict()
69
+ mock_proximl._query = AsyncMock(return_value=api_response)
70
+ await volumes.remove("4567")
71
+ mock_proximl._query.assert_called_once_with(
72
+ "/volume/4567", "DELETE", dict(force=True)
73
+ )
74
+
75
+ @mark.asyncio
76
+ async def test_create_volume_simple(self, volumes, mock_proximl):
77
+ requested_config = dict(
78
+ name="new volume",
79
+ source_type="aws",
80
+ source_uri="s3://proximl-examples/volumes/resnet50",
81
+ capacity="10G",
82
+ )
83
+ expected_payload = dict(
84
+ project_uuid="proj-id-1",
85
+ name="new volume",
86
+ source_type="aws",
87
+ source_uri="s3://proximl-examples/volumes/resnet50",
88
+ capacity="10G",
89
+ )
90
+ api_response = {
91
+ "project_uuid": "cus-id-1",
92
+ "id": "volume-id-1",
93
+ "name": "new volume",
94
+ "status": "new",
95
+ "source_type": "aws",
96
+ "capacity": "10G",
97
+ "source_uri": "s3://proximl-examples/volumes/resnet50",
98
+ "createdAt": "2020-12-20T16:46:23.909Z",
99
+ }
100
+
101
+ mock_proximl._query = AsyncMock(return_value=api_response)
102
+ response = await volumes.create(**requested_config)
103
+ mock_proximl._query.assert_called_once_with(
104
+ "/volume", "POST", None, expected_payload
105
+ )
106
+ assert response.id == "volume-id-1"
107
+
108
+
109
+ class VolumeTests:
110
+ def test_volume_properties(self, volume):
111
+ assert isinstance(volume.id, str)
112
+ assert isinstance(volume.status, str)
113
+ assert isinstance(volume.name, str)
114
+ assert isinstance(volume.capacity, str)
115
+ assert isinstance(volume.used_size, int)
116
+ assert isinstance(volume.billed_size, int)
117
+
118
+ def test_volume_str(self, volume):
119
+ string = str(volume)
120
+ regex = r"^{.*\"id\": \"" + volume.id + r"\".*}$"
121
+ assert isinstance(string, str)
122
+ assert re.match(regex, string)
123
+
124
+ def test_volume_repr(self, volume):
125
+ string = repr(volume)
126
+ regex = r"^Volume\( proximl , \*\*{.*'id': '" + volume.id + r"'.*}\)$"
127
+ assert isinstance(string, str)
128
+ assert re.match(regex, string)
129
+
130
+ def test_volume_bool(self, volume, mock_proximl):
131
+ empty_volume = specimen.Volume(mock_proximl)
132
+ assert bool(volume)
133
+ assert not bool(empty_volume)
134
+
135
+ @mark.asyncio
136
+ async def test_volume_get_log_url(self, volume, mock_proximl):
137
+ api_response = (
138
+ "https://trainml-jobs-dev.s3.us-east-2.amazonaws.com/1/logs/first_one.zip"
139
+ )
140
+ mock_proximl._query = AsyncMock(return_value=api_response)
141
+ response = await volume.get_log_url()
142
+ mock_proximl._query.assert_called_once_with(
143
+ "/volume/1/logs", "GET", dict(project_uuid="proj-id-1")
144
+ )
145
+ assert response == api_response
146
+
147
+ @mark.asyncio
148
+ async def test_volume_get_details(self, volume, mock_proximl):
149
+ api_response = {
150
+ "type": "directory",
151
+ "name": "/",
152
+ "count": "8",
153
+ "used_size": "177M",
154
+ "contents": [],
155
+ }
156
+ mock_proximl._query = AsyncMock(return_value=api_response)
157
+ response = await volume.get_details()
158
+ mock_proximl._query.assert_called_once_with(
159
+ "/volume/1/details", "GET", dict(project_uuid="proj-id-1")
160
+ )
161
+ assert response == api_response
162
+
163
+ @mark.asyncio
164
+ async def test_volume_get_connection_utility_url(self, volume, mock_proximl):
165
+ api_response = (
166
+ "https://trainml-jobs-dev.s3.us-east-2.amazonaws.com/1/vpn/first_one.zip"
167
+ )
168
+ mock_proximl._query = AsyncMock(return_value=api_response)
169
+ response = await volume.get_connection_utility_url()
170
+ mock_proximl._query.assert_called_once_with(
171
+ "/volume/1/download", "GET", dict(project_uuid="proj-id-1")
172
+ )
173
+ assert response == api_response
174
+
175
+ def test_volume_get_connection_details_no_vpn(self, volume):
176
+ details = volume.get_connection_details()
177
+ expected_details = dict()
178
+ assert details == expected_details
179
+
180
+ def test_volume_get_connection_details_local_data(self, mock_proximl):
181
+ volume = specimen.Volume(
182
+ mock_proximl,
183
+ id="1",
184
+ project_uuid="a",
185
+ name="first one",
186
+ status="new",
187
+ capacity="10G",
188
+ createdAt="2020-12-31T23:59:59.000Z",
189
+ source_type="local",
190
+ source_uri="~/tensorflow-example",
191
+ vpn={
192
+ "status": "new",
193
+ "cidr": "10.106.171.0/24",
194
+ "client": {
195
+ "port": "36017",
196
+ "id": "cus-id-1",
197
+ "address": "10.106.171.253",
198
+ "ssh_port": 46600,
199
+ },
200
+ "net_prefix_type_id": 1,
201
+ },
202
+ )
203
+ details = volume.get_connection_details()
204
+ expected_details = dict(
205
+ project_uuid="a",
206
+ entity_type="volume",
207
+ cidr="10.106.171.0/24",
208
+ ssh_port=46600,
209
+ input_path="~/tensorflow-example",
210
+ output_path=None,
211
+ )
212
+ assert details == expected_details
213
+
214
+ @mark.asyncio
215
+ async def test_volume_connect(self, volume, mock_proximl):
216
+ with patch(
217
+ "proximl.volumes.Connection",
218
+ autospec=True,
219
+ ) as mock_connection:
220
+ connection = mock_connection.return_value
221
+ connection.status = "connected"
222
+ resp = await volume.connect()
223
+ connection.start.assert_called_once()
224
+ assert resp == "connected"
225
+
226
+ @mark.asyncio
227
+ async def test_volume_disconnect(self, volume, mock_proximl):
228
+ with patch(
229
+ "proximl.volumes.Connection",
230
+ autospec=True,
231
+ ) as mock_connection:
232
+ connection = mock_connection.return_value
233
+ connection.status = "removed"
234
+ resp = await volume.disconnect()
235
+ connection.stop.assert_called_once()
236
+ assert resp == "removed"
237
+
238
+ @mark.asyncio
239
+ async def test_volume_remove(self, volume, mock_proximl):
240
+ api_response = dict()
241
+ mock_proximl._query = AsyncMock(return_value=api_response)
242
+ await volume.remove()
243
+ mock_proximl._query.assert_called_once_with(
244
+ "/volume/1",
245
+ "DELETE",
246
+ dict(project_uuid="proj-id-1", force=False),
247
+ )
248
+
249
+ def test_volume_default_ws_msg_handler(self, volume, capsys):
250
+ data = {
251
+ "msg": "download: s3://proximl-examples/data/cifar10/data_batch_2.bin to ./data_batch_2.bin\n",
252
+ "time": 1613079345318,
253
+ "type": "subscription",
254
+ "stream": "worker-id-1",
255
+ "job_worker_uuid": "worker-id-1",
256
+ }
257
+
258
+ handler = volume._get_msg_handler(None)
259
+ handler(data)
260
+ captured = capsys.readouterr()
261
+ assert (
262
+ captured.out
263
+ == "02/11/2021, 15:35:45: download: s3://proximl-examples/data/cifar10/data_batch_2.bin to ./data_batch_2.bin\n"
264
+ )
265
+
266
+ def test_volume_custom_ws_msg_handler(self, volume, capsys):
267
+ def custom_handler(msg):
268
+ print(msg.get("stream"))
269
+
270
+ data = {
271
+ "msg": "download: s3://proximl-examples/data/cifar10/data_batch_2.bin to ./data_batch_2.bin\n",
272
+ "time": 1613079345318,
273
+ "type": "subscription",
274
+ "stream": "worker-id-1",
275
+ "job_worker_uuid": "worker-id-1",
276
+ }
277
+
278
+ handler = volume._get_msg_handler(custom_handler)
279
+ handler(data)
280
+ captured = capsys.readouterr()
281
+ assert captured.out == "worker-id-1\n"
282
+
283
+ @mark.asyncio
284
+ async def test_volume_attach(self, volume, mock_proximl):
285
+ api_response = None
286
+ mock_proximl._ws_subscribe = AsyncMock(return_value=api_response)
287
+ refresh_response = {
288
+ "customer_uuid": "cus-id-1",
289
+ "id": "data-id-1",
290
+ "name": "new volume",
291
+ "status": "downloading",
292
+ "source_type": "aws",
293
+ "source_uri": "s3://proximl-examples/data/cifar10",
294
+ "createdAt": "2020-12-20T16:46:23.909Z",
295
+ }
296
+ volume.refresh = AsyncMock(return_value=refresh_response)
297
+ await volume.attach()
298
+ mock_proximl._ws_subscribe.assert_called_once()
299
+
300
+ @mark.asyncio
301
+ async def test_volume_attach_immediate_return(self, mock_proximl):
302
+ volume = specimen.Volume(
303
+ mock_proximl,
304
+ id="1",
305
+ name="first one",
306
+ status="ready",
307
+ createdAt="2020-12-31T23:59:59.000Z",
308
+ )
309
+ api_response = None
310
+ mock_proximl._ws_subscribe = AsyncMock(return_value=api_response)
311
+ refresh_response = {
312
+ "customer_uuid": "cus-id-1",
313
+ "id": "1",
314
+ "name": "new volume",
315
+ "status": "ready",
316
+ "createdAt": "2020-12-20T16:46:23.909Z",
317
+ }
318
+ volume.refresh = AsyncMock(return_value=refresh_response)
319
+ await volume.attach()
320
+ mock_proximl._ws_subscribe.assert_not_called()
321
+
322
+ @mark.asyncio
323
+ async def test_volume_refresh(self, volume, mock_proximl):
324
+ api_response = {
325
+ "customer_uuid": "cus-id-1",
326
+ "id": "data-id-1",
327
+ "name": "new volume",
328
+ "status": "ready",
329
+ "source_type": "aws",
330
+ "source_uri": "s3://proximl-examples/data/cifar10",
331
+ "createdAt": "2020-12-20T16:46:23.909Z",
332
+ }
333
+ mock_proximl._query = AsyncMock(return_value=api_response)
334
+ response = await volume.refresh()
335
+ mock_proximl._query.assert_called_once_with(
336
+ f"/volume/1", "GET", dict(project_uuid="proj-id-1")
337
+ )
338
+ assert volume.id == "data-id-1"
339
+ assert response.id == "data-id-1"
340
+
341
+ @mark.asyncio
342
+ async def test_volume_wait_for_successful(self, volume, mock_proximl):
343
+ api_response = {
344
+ "customer_uuid": "cus-id-1",
345
+ "id": "data-id-1",
346
+ "name": "new volume",
347
+ "status": "ready",
348
+ "source_type": "aws",
349
+ "source_uri": "s3://proximl-examples/data/cifar10",
350
+ "createdAt": "2020-12-20T16:46:23.909Z",
351
+ }
352
+ mock_proximl._query = AsyncMock(return_value=api_response)
353
+ response = await volume.wait_for("ready")
354
+ mock_proximl._query.assert_called_once_with(
355
+ f"/volume/1", "GET", dict(project_uuid="proj-id-1")
356
+ )
357
+ assert volume.id == "data-id-1"
358
+ assert response.id == "data-id-1"
359
+
360
+ @mark.asyncio
361
+ async def test_volume_wait_for_current_status(self, mock_proximl):
362
+ volume = specimen.Volume(
363
+ mock_proximl,
364
+ id="1",
365
+ name="first one",
366
+ status="ready",
367
+ createdAt="2020-12-31T23:59:59.000Z",
368
+ )
369
+ api_response = None
370
+ mock_proximl._query = AsyncMock(return_value=api_response)
371
+ await volume.wait_for("ready")
372
+ mock_proximl._query.assert_not_called()
373
+
374
+ @mark.asyncio
375
+ async def test_volume_wait_for_incorrect_status(self, volume, mock_proximl):
376
+ api_response = None
377
+ mock_proximl._query = AsyncMock(return_value=api_response)
378
+ with raises(SpecificationError):
379
+ await volume.wait_for("stopped")
380
+ mock_proximl._query.assert_not_called()
381
+
382
+ @mark.asyncio
383
+ async def test_volume_wait_for_with_delay(self, volume, mock_proximl):
384
+ api_response_initial = dict(
385
+ id="1",
386
+ name="first one",
387
+ status="new",
388
+ createdAt="2020-12-31T23:59:59.000Z",
389
+ )
390
+ api_response_final = dict(
391
+ id="1",
392
+ name="first one",
393
+ status="ready",
394
+ createdAt="2020-12-31T23:59:59.000Z",
395
+ )
396
+ mock_proximl._query = AsyncMock()
397
+ mock_proximl._query.side_effect = [
398
+ api_response_initial,
399
+ api_response_initial,
400
+ api_response_final,
401
+ ]
402
+ response = await volume.wait_for("ready")
403
+ assert volume.status == "ready"
404
+ assert response.status == "ready"
405
+
406
+ @mark.asyncio
407
+ async def test_volume_wait_for_timeout(self, volume, mock_proximl):
408
+ api_response = dict(
409
+ id="1",
410
+ name="first one",
411
+ status="downloading",
412
+ createdAt="2020-12-31T23:59:59.000Z",
413
+ )
414
+ mock_proximl._query = AsyncMock(return_value=api_response)
415
+ with raises(ProxiMLException):
416
+ await volume.wait_for("ready", 10)
417
+ mock_proximl._query.assert_called()
418
+
419
+ @mark.asyncio
420
+ async def test_volume_wait_for_volume_failed(self, volume, mock_proximl):
421
+ api_response = dict(
422
+ id="1",
423
+ name="first one",
424
+ status="failed",
425
+ createdAt="2020-12-31T23:59:59.000Z",
426
+ )
427
+ mock_proximl._query = AsyncMock(return_value=api_response)
428
+ with raises(VolumeError):
429
+ await volume.wait_for("ready")
430
+ mock_proximl._query.assert_called()
431
+
432
+ @mark.asyncio
433
+ async def test_volume_wait_for_archived_succeeded(self, volume, mock_proximl):
434
+ mock_proximl._query = AsyncMock(
435
+ side_effect=ApiError(404, dict(errorMessage="Volume Not Found"))
436
+ )
437
+ await volume.wait_for("archived")
438
+ mock_proximl._query.assert_called()
439
+
440
+ @mark.asyncio
441
+ async def test_volume_wait_for_unexpected_api_error(self, volume, mock_proximl):
442
+ mock_proximl._query = AsyncMock(
443
+ side_effect=ApiError(404, dict(errorMessage="Volume Not Found"))
444
+ )
445
+ with raises(ApiError):
446
+ await volume.wait_for("ready")
447
+ mock_proximl._query.assert_called()