proximl 0.5.16__py3-none-any.whl → 1.0.0__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.
- examples/local_storage.py +0 -2
- proximl/__init__.py +1 -1
- proximl/checkpoints.py +56 -57
- proximl/cli/__init__.py +6 -3
- proximl/cli/checkpoint.py +18 -57
- proximl/cli/dataset.py +17 -57
- proximl/cli/job/__init__.py +11 -53
- proximl/cli/job/create.py +51 -24
- proximl/cli/model.py +14 -56
- proximl/cli/volume.py +18 -57
- proximl/datasets.py +50 -55
- proximl/jobs.py +239 -68
- proximl/models.py +51 -55
- proximl/projects/projects.py +2 -2
- proximl/proximl.py +50 -16
- proximl/utils/__init__.py +1 -0
- proximl/{auth.py → utils/auth.py} +4 -3
- proximl/utils/transfer.py +587 -0
- proximl/volumes.py +48 -53
- {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/METADATA +3 -3
- {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/RECORD +53 -51
- tests/integration/test_checkpoints_integration.py +4 -3
- tests/integration/test_datasets_integration.py +5 -3
- tests/integration/test_jobs_integration.py +33 -27
- tests/integration/test_models_integration.py +7 -3
- tests/integration/test_volumes_integration.py +2 -2
- tests/unit/cli/test_cli_checkpoint_unit.py +312 -1
- tests/unit/cloudbender/test_nodes_unit.py +112 -0
- tests/unit/cloudbender/test_providers_unit.py +96 -0
- tests/unit/cloudbender/test_regions_unit.py +106 -0
- tests/unit/cloudbender/test_services_unit.py +141 -0
- tests/unit/conftest.py +23 -10
- tests/unit/projects/test_project_data_connectors_unit.py +39 -0
- tests/unit/projects/test_project_datastores_unit.py +37 -0
- tests/unit/projects/test_project_members_unit.py +46 -0
- tests/unit/projects/test_project_services_unit.py +65 -0
- tests/unit/projects/test_projects_unit.py +17 -1
- tests/unit/test_auth_unit.py +17 -2
- tests/unit/test_checkpoints_unit.py +256 -71
- tests/unit/test_datasets_unit.py +218 -68
- tests/unit/test_exceptions.py +133 -0
- tests/unit/test_gpu_types_unit.py +11 -1
- tests/unit/test_jobs_unit.py +1014 -95
- tests/unit/test_main_unit.py +20 -0
- tests/unit/test_models_unit.py +218 -70
- tests/unit/test_proximl_unit.py +627 -3
- tests/unit/test_volumes_unit.py +211 -70
- tests/unit/utils/__init__.py +1 -0
- tests/unit/utils/test_transfer_unit.py +4260 -0
- proximl/cli/connection.py +0 -61
- proximl/connections.py +0 -621
- tests/unit/test_connections_unit.py +0 -182
- {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/LICENSE +0 -0
- {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/WHEEL +0 -0
- {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/entry_points.txt +0 -0
- {proximl-0.5.16.dist-info → proximl-1.0.0.dist-info}/top_level.txt +0 -0
proximl/cli/connection.py
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
from proximl.cli import cli, pass_config, search_by_id_name
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@cli.group()
|
|
6
|
-
@pass_config
|
|
7
|
-
def connection(config):
|
|
8
|
-
"""proxiML connection commands."""
|
|
9
|
-
pass
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@connection.command()
|
|
13
|
-
@pass_config
|
|
14
|
-
def list(config):
|
|
15
|
-
"""List connections."""
|
|
16
|
-
data = [["ID", "TYPE", "STATUS"], ["-" * 80, "-" * 80, "-" * 80]]
|
|
17
|
-
|
|
18
|
-
connections = config.proximl.run(config.proximl.client.connections.list())
|
|
19
|
-
|
|
20
|
-
for con in connections:
|
|
21
|
-
data.append([con.id, con.type, con.status])
|
|
22
|
-
for row in data:
|
|
23
|
-
click.echo(
|
|
24
|
-
"{: >38.36} {: >9.7} {: >15.13}".format(*row), file=config.stdout
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@connection.command()
|
|
29
|
-
@click.argument("id", type=click.STRING)
|
|
30
|
-
@pass_config
|
|
31
|
-
def remove(config, id):
|
|
32
|
-
"""Remove connection."""
|
|
33
|
-
connections = config.proximl.run(config.proximl.client.connections.list())
|
|
34
|
-
|
|
35
|
-
found = search_by_id_name(id, connections)
|
|
36
|
-
if None is found:
|
|
37
|
-
raise click.UsageError("Connection ID specified does not exist.")
|
|
38
|
-
|
|
39
|
-
if found.type == "dataset":
|
|
40
|
-
this = config.proximl.run(config.proximl.client.datasets.get(id))
|
|
41
|
-
elif found.type == "job":
|
|
42
|
-
this = config.proximl.run(config.proximl.client.jobs.get(id))
|
|
43
|
-
else:
|
|
44
|
-
raise click.UsageError("Unknown connection type.")
|
|
45
|
-
|
|
46
|
-
return config.proximl.run(this.disconnect())
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@connection.command()
|
|
50
|
-
@click.option(
|
|
51
|
-
"--all-projects/--no-all-projects",
|
|
52
|
-
default=False,
|
|
53
|
-
show_default=True,
|
|
54
|
-
help="Auto attach to dataset and show creation logs.",
|
|
55
|
-
)
|
|
56
|
-
@pass_config
|
|
57
|
-
def remove_all(config, all_projects):
|
|
58
|
-
"""Clear and clean-up all proxiML connections."""
|
|
59
|
-
return config.proximl.run(
|
|
60
|
-
config.proximl.client.connections.remove_all(all_projects=all_projects)
|
|
61
|
-
)
|
proximl/connections.py
DELETED
|
@@ -1,621 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import os
|
|
3
|
-
import shutil
|
|
4
|
-
import asyncio
|
|
5
|
-
import aiohttp
|
|
6
|
-
import aiodocker
|
|
7
|
-
import zipfile
|
|
8
|
-
import re
|
|
9
|
-
import logging
|
|
10
|
-
|
|
11
|
-
from .exceptions import (
|
|
12
|
-
ConnectionError,
|
|
13
|
-
ApiError,
|
|
14
|
-
SpecificationError,
|
|
15
|
-
ProxiMLException,
|
|
16
|
-
)
|
|
17
|
-
from aiodocker.exceptions import DockerError
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
VPN_IMAGE = "proximl/connection:no-upnp"
|
|
21
|
-
STORAGE_IMAGE = "proximl/local-storage"
|
|
22
|
-
STATUSES = dict(
|
|
23
|
-
UNKNOWN="unknown",
|
|
24
|
-
NEW="new",
|
|
25
|
-
CONNECTING="connecting",
|
|
26
|
-
CONNECTED="connected",
|
|
27
|
-
NOT_CONNECTED="not connected",
|
|
28
|
-
STOPPED="stopped",
|
|
29
|
-
REMOVED="removed",
|
|
30
|
-
)
|
|
31
|
-
CONFIG_DIR = os.path.expanduser(
|
|
32
|
-
os.environ.get("PROXIML_CONFIG_DIR") or "~/.proximl"
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class Connections(object):
|
|
37
|
-
def __init__(self, proximl):
|
|
38
|
-
self.proximl = proximl
|
|
39
|
-
self.dir = f"{CONFIG_DIR}/connections/{self.proximl.project}"
|
|
40
|
-
os.makedirs(
|
|
41
|
-
self.dir,
|
|
42
|
-
exist_ok=True,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
async def list(self):
|
|
46
|
-
con_dirs = os.listdir(self.dir)
|
|
47
|
-
connections = []
|
|
48
|
-
con_tasks = []
|
|
49
|
-
for con_dir in con_dirs:
|
|
50
|
-
try:
|
|
51
|
-
con_type, con_id = con_dir.split("_")
|
|
52
|
-
except ValueError:
|
|
53
|
-
# unintelligible directory
|
|
54
|
-
continue
|
|
55
|
-
connection = Connection(self.proximl, con_type, con_id)
|
|
56
|
-
connections.append(connection)
|
|
57
|
-
con_task = asyncio.create_task(connection.check())
|
|
58
|
-
con_tasks.append(con_task)
|
|
59
|
-
await asyncio.gather(*con_tasks)
|
|
60
|
-
return connections
|
|
61
|
-
|
|
62
|
-
async def cleanup_connections(self):
|
|
63
|
-
logging.info("Start cleanup connections")
|
|
64
|
-
con_dirs = os.listdir(self.dir)
|
|
65
|
-
con_tasks = []
|
|
66
|
-
logging.debug(f"con_dirs: {con_dirs}")
|
|
67
|
-
for con_dir in con_dirs:
|
|
68
|
-
try:
|
|
69
|
-
con_type, con_id = con_dir.split("_")
|
|
70
|
-
except ValueError:
|
|
71
|
-
# unintelligible directory
|
|
72
|
-
logging.debug(f"unintelligible con_dir: {con_dir}")
|
|
73
|
-
shutil.rmtree(con_dir)
|
|
74
|
-
continue
|
|
75
|
-
connection = Connection(self.proximl, con_type, con_id)
|
|
76
|
-
con_task = asyncio.create_task(connection._validate_entity())
|
|
77
|
-
con_tasks.append(con_task)
|
|
78
|
-
await asyncio.gather(*con_tasks, return_exceptions=True)
|
|
79
|
-
await self.cleanup_containers(project=self.proximl.project)
|
|
80
|
-
logging.info("Finish cleanup connections")
|
|
81
|
-
|
|
82
|
-
async def cleanup_containers(self, project=None):
|
|
83
|
-
logging.info("Start cleanup containers")
|
|
84
|
-
if project:
|
|
85
|
-
con_dirs = os.listdir(f"{CONFIG_DIR}/connections/{project}")
|
|
86
|
-
await asyncio.gather(
|
|
87
|
-
asyncio.create_task(
|
|
88
|
-
_cleanup_containers(
|
|
89
|
-
project,
|
|
90
|
-
self.dir,
|
|
91
|
-
con_dirs,
|
|
92
|
-
"vpn",
|
|
93
|
-
)
|
|
94
|
-
),
|
|
95
|
-
asyncio.create_task(
|
|
96
|
-
_cleanup_containers(
|
|
97
|
-
project,
|
|
98
|
-
self.dir,
|
|
99
|
-
con_dirs,
|
|
100
|
-
"storage",
|
|
101
|
-
)
|
|
102
|
-
),
|
|
103
|
-
)
|
|
104
|
-
else:
|
|
105
|
-
con_dirs = []
|
|
106
|
-
proj_dirs = os.listdir(f"{CONFIG_DIR}/connections")
|
|
107
|
-
logging.info(f"proj_dirs {proj_dirs}")
|
|
108
|
-
for proj_dir in proj_dirs:
|
|
109
|
-
con_dirs += [
|
|
110
|
-
f"{proj_dir}/{con_dir}"
|
|
111
|
-
for con_dir in os.listdir(
|
|
112
|
-
f"{CONFIG_DIR}/connections/{project}"
|
|
113
|
-
)
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
logging.info(f"con_dirs {con_dirs}")
|
|
117
|
-
await asyncio.gather(
|
|
118
|
-
asyncio.create_task(
|
|
119
|
-
_cleanup_containers(
|
|
120
|
-
None,
|
|
121
|
-
f"{CONFIG_DIR}/connections",
|
|
122
|
-
con_dirs,
|
|
123
|
-
"vpn",
|
|
124
|
-
)
|
|
125
|
-
),
|
|
126
|
-
asyncio.create_task(
|
|
127
|
-
_cleanup_containers(
|
|
128
|
-
None,
|
|
129
|
-
f"{CONFIG_DIR}/connections",
|
|
130
|
-
con_dirs,
|
|
131
|
-
"storage",
|
|
132
|
-
)
|
|
133
|
-
),
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
logging.info("Finish cleanup containers")
|
|
137
|
-
|
|
138
|
-
async def remove_all(self, all_projects=False):
|
|
139
|
-
if all_projects:
|
|
140
|
-
proj_dirs = os.listdir(f"{CONFIG_DIR}/connections")
|
|
141
|
-
for proj_dir in proj_dirs:
|
|
142
|
-
shutil.rmtree(f"{CONFIG_DIR}/connections/{proj_dir}")
|
|
143
|
-
os.makedirs(
|
|
144
|
-
f"{CONFIG_DIR}/connections/{proj_dir}",
|
|
145
|
-
exist_ok=True,
|
|
146
|
-
)
|
|
147
|
-
await self.cleanup_containers()
|
|
148
|
-
else:
|
|
149
|
-
shutil.rmtree(self.dir)
|
|
150
|
-
os.makedirs(
|
|
151
|
-
self.dir,
|
|
152
|
-
exist_ok=True,
|
|
153
|
-
)
|
|
154
|
-
await self.cleanup_containers(project=self.proximl.project)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class Connection:
|
|
158
|
-
def __init__(self, proximl, entity_type, id, entity=None, **kwargs):
|
|
159
|
-
self.proximl = proximl
|
|
160
|
-
self._id = id
|
|
161
|
-
self._type = entity_type
|
|
162
|
-
self._status = STATUSES.get("UNKNOWN")
|
|
163
|
-
self._entity = entity
|
|
164
|
-
CONNECTIONS_DIR = f"{CONFIG_DIR}/connections/{self.proximl.project}"
|
|
165
|
-
self._dir = f"{CONNECTIONS_DIR}/{entity_type}_{id}"
|
|
166
|
-
os.makedirs(
|
|
167
|
-
self._dir,
|
|
168
|
-
exist_ok=True,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
@property
|
|
172
|
-
def id(self) -> str:
|
|
173
|
-
return self._id
|
|
174
|
-
|
|
175
|
-
@property
|
|
176
|
-
def type(self) -> str:
|
|
177
|
-
return self._type
|
|
178
|
-
|
|
179
|
-
@property
|
|
180
|
-
def status(self) -> str:
|
|
181
|
-
return self._status
|
|
182
|
-
|
|
183
|
-
def __str__(self):
|
|
184
|
-
return f"Connection for {self.type} - {self.id}: {self.status}"
|
|
185
|
-
|
|
186
|
-
def __repr__(self):
|
|
187
|
-
return f"Connection( proximl , {self.id}, {self.type})"
|
|
188
|
-
|
|
189
|
-
async def _get_entity(self):
|
|
190
|
-
if self.type == "dataset":
|
|
191
|
-
self._entity = await self.proximl.datasets.get(self.id)
|
|
192
|
-
elif self.type == "job":
|
|
193
|
-
self._entity = await self.proximl.jobs.get(self.id)
|
|
194
|
-
elif self.type == "model":
|
|
195
|
-
self._entity = await self.proximl.models.get(self.id)
|
|
196
|
-
elif self.type == "checkpoint":
|
|
197
|
-
self._entity = await self.proximl.checkpoints.get(self.id)
|
|
198
|
-
else:
|
|
199
|
-
raise TypeError(
|
|
200
|
-
"Connection type must be in: ['dataset', 'model', 'checkpoint', 'job']"
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
async def _download_connection_details(self):
|
|
204
|
-
zip_file = f"{self._dir}/details.zip"
|
|
205
|
-
url = await self._entity.get_connection_utility_url()
|
|
206
|
-
async with aiohttp.ClientSession() as session:
|
|
207
|
-
async with session.request("GET", url) as resp:
|
|
208
|
-
with open(
|
|
209
|
-
zip_file,
|
|
210
|
-
"wb",
|
|
211
|
-
) as fd:
|
|
212
|
-
content = await resp.read()
|
|
213
|
-
fd.write(content)
|
|
214
|
-
with zipfile.ZipFile(zip_file, "r") as zipf:
|
|
215
|
-
for info in zipf.infolist():
|
|
216
|
-
extracted_path = zipf.extract(info, self._dir)
|
|
217
|
-
if info.create_system == 3 and os.path.isfile(
|
|
218
|
-
extracted_path
|
|
219
|
-
): ## 3 - ZIP_UNIX_SYSTEM
|
|
220
|
-
unix_attributes = info.external_attr >> 16
|
|
221
|
-
if unix_attributes:
|
|
222
|
-
os.chmod(extracted_path, unix_attributes)
|
|
223
|
-
|
|
224
|
-
os.remove(zip_file)
|
|
225
|
-
|
|
226
|
-
async def _test_connection(self, container):
|
|
227
|
-
entity_details = self._entity.get_connection_details()
|
|
228
|
-
if not entity_details:
|
|
229
|
-
return False
|
|
230
|
-
net = _parse_cidr(entity_details.get("cidr"))
|
|
231
|
-
target_ip = f"{net.get('first_octet')}.{net.get('second_octet')}.{net.get('third_octet')}.254"
|
|
232
|
-
|
|
233
|
-
logging.debug("Testing connection")
|
|
234
|
-
ping = await container.exec(
|
|
235
|
-
["ping", "-c", "1", target_ip],
|
|
236
|
-
stdout=True,
|
|
237
|
-
stderr=True,
|
|
238
|
-
)
|
|
239
|
-
stream = ping.start()
|
|
240
|
-
await stream.read_out()
|
|
241
|
-
data = await ping.inspect()
|
|
242
|
-
while data["ExitCode"] is None:
|
|
243
|
-
await stream.read_out()
|
|
244
|
-
data = await ping.inspect()
|
|
245
|
-
await stream.close()
|
|
246
|
-
if data["ExitCode"] == 0:
|
|
247
|
-
return True
|
|
248
|
-
return False
|
|
249
|
-
|
|
250
|
-
async def _validate_entity(self):
|
|
251
|
-
try:
|
|
252
|
-
await self._get_entity()
|
|
253
|
-
logging.debug(f"entity: {self._entity}")
|
|
254
|
-
if self._entity.status in [
|
|
255
|
-
"failed",
|
|
256
|
-
"finished",
|
|
257
|
-
"canceled",
|
|
258
|
-
"archived",
|
|
259
|
-
"removed",
|
|
260
|
-
"removing",
|
|
261
|
-
"ready",
|
|
262
|
-
]:
|
|
263
|
-
shutil.rmtree(self._dir)
|
|
264
|
-
logging.debug(f"remove: {self._dir}")
|
|
265
|
-
return False
|
|
266
|
-
else:
|
|
267
|
-
return True
|
|
268
|
-
except ApiError as e:
|
|
269
|
-
if e.status == 404:
|
|
270
|
-
shutil.rmtree(self._dir)
|
|
271
|
-
logging.debug(f"remove: {self._dir}")
|
|
272
|
-
return False
|
|
273
|
-
else:
|
|
274
|
-
raise e
|
|
275
|
-
|
|
276
|
-
async def check(self):
|
|
277
|
-
if not self._entity:
|
|
278
|
-
valid = await self._validate_entity()
|
|
279
|
-
if not valid:
|
|
280
|
-
self._status = STATUSES.get("REMOVED")
|
|
281
|
-
return
|
|
282
|
-
if not os.path.isdir(f"{self._dir}/data"):
|
|
283
|
-
self._status = STATUSES.get("NEW")
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
try:
|
|
287
|
-
with open(f"{self._dir}/vpn_id", "r") as f:
|
|
288
|
-
vpn_id = f.read()
|
|
289
|
-
except OSError as e:
|
|
290
|
-
self._status = STATUSES.get("STOPPED")
|
|
291
|
-
return
|
|
292
|
-
|
|
293
|
-
docker = aiodocker.Docker()
|
|
294
|
-
await _check_docker_status(docker)
|
|
295
|
-
|
|
296
|
-
try:
|
|
297
|
-
container = await docker.containers.get(vpn_id)
|
|
298
|
-
except DockerError as e:
|
|
299
|
-
if e.status == 404:
|
|
300
|
-
self._status = STATUSES.get("STOPPED")
|
|
301
|
-
await docker.close()
|
|
302
|
-
return
|
|
303
|
-
raise e
|
|
304
|
-
|
|
305
|
-
data = await container.show()
|
|
306
|
-
if not data["State"]["Running"]:
|
|
307
|
-
self._status = STATUSES.get("STOPPED")
|
|
308
|
-
await container.delete()
|
|
309
|
-
os.remove(f"{self._dir}/vpn_id")
|
|
310
|
-
try:
|
|
311
|
-
with open(f"{self._dir}/storage_id", "r") as f:
|
|
312
|
-
storage_id = f.read()
|
|
313
|
-
try:
|
|
314
|
-
storage_container = await docker.containers.get(storage_id)
|
|
315
|
-
await storage_container.delete(force=True)
|
|
316
|
-
except DockerError as e:
|
|
317
|
-
if e.status != 404:
|
|
318
|
-
raise e
|
|
319
|
-
except OSError as e:
|
|
320
|
-
pass
|
|
321
|
-
await docker.close()
|
|
322
|
-
return
|
|
323
|
-
|
|
324
|
-
connected = await self._test_connection(container)
|
|
325
|
-
await docker.close()
|
|
326
|
-
if connected:
|
|
327
|
-
self._status = STATUSES.get("CONNECTED")
|
|
328
|
-
else:
|
|
329
|
-
self._status = STATUSES.get("NOT_CONNECTED")
|
|
330
|
-
|
|
331
|
-
async def start(self):
|
|
332
|
-
logging.info(f"Beginning start {self.type} connection {self.id}")
|
|
333
|
-
|
|
334
|
-
if self.status == STATUSES.get("UNKNOWN"):
|
|
335
|
-
await self.check()
|
|
336
|
-
if self.status in [
|
|
337
|
-
STATUSES.get("CONNECTING"),
|
|
338
|
-
STATUSES.get("CONNECTED"),
|
|
339
|
-
STATUSES.get("NOT_CONNECTED"),
|
|
340
|
-
]:
|
|
341
|
-
raise SpecificationError(
|
|
342
|
-
"status", "Only inactive connections can be started."
|
|
343
|
-
)
|
|
344
|
-
self._status = STATUSES.get("CONNECTING")
|
|
345
|
-
logging.info(f"Connecting...")
|
|
346
|
-
if not self._entity:
|
|
347
|
-
await self._get_entity()
|
|
348
|
-
if not os.path.isdir(f"{self._dir}/data"):
|
|
349
|
-
await self._download_connection_details()
|
|
350
|
-
|
|
351
|
-
docker = aiodocker.Docker()
|
|
352
|
-
await _check_docker_status(docker)
|
|
353
|
-
try:
|
|
354
|
-
await asyncio.gather(
|
|
355
|
-
docker.pull(VPN_IMAGE), docker.pull(STORAGE_IMAGE)
|
|
356
|
-
)
|
|
357
|
-
except DockerError as e:
|
|
358
|
-
exists = await asyncio.gather(
|
|
359
|
-
_image_exists(docker, VPN_IMAGE),
|
|
360
|
-
_image_exists(docker, STORAGE_IMAGE),
|
|
361
|
-
)
|
|
362
|
-
if any([not i for i in exists]):
|
|
363
|
-
raise e
|
|
364
|
-
|
|
365
|
-
entity_details = self._entity.get_connection_details()
|
|
366
|
-
if (
|
|
367
|
-
entity_details.get("model_path")
|
|
368
|
-
or entity_details.get("input_path")
|
|
369
|
-
or entity_details.get("output_path")
|
|
370
|
-
):
|
|
371
|
-
logging.debug(f"Starting storage container")
|
|
372
|
-
storage_container = await docker.containers.run(
|
|
373
|
-
_get_storage_container_config(
|
|
374
|
-
self.id,
|
|
375
|
-
entity_details.get("project_uuid"),
|
|
376
|
-
entity_details.get("entity_type"),
|
|
377
|
-
entity_details.get("cidr"),
|
|
378
|
-
f"{self._dir}/data",
|
|
379
|
-
entity_details.get("ssh_port"),
|
|
380
|
-
model_path=entity_details.get("model_path"),
|
|
381
|
-
input_path=entity_details.get("input_path"),
|
|
382
|
-
output_path=entity_details.get("output_path"),
|
|
383
|
-
)
|
|
384
|
-
)
|
|
385
|
-
logging.debug(
|
|
386
|
-
f"Storage container started, id: {storage_container.id}"
|
|
387
|
-
)
|
|
388
|
-
with open(f"{self._dir}/storage_id", "w") as f:
|
|
389
|
-
f.write(storage_container.id)
|
|
390
|
-
|
|
391
|
-
logging.debug(f"Starting VPN container")
|
|
392
|
-
vpn_container = await docker.containers.run(
|
|
393
|
-
_get_vpn_container_config(
|
|
394
|
-
self.id,
|
|
395
|
-
entity_details.get("project_uuid"),
|
|
396
|
-
entity_details.get("entity_type"),
|
|
397
|
-
entity_details.get("cidr"),
|
|
398
|
-
f"{self._dir}/data",
|
|
399
|
-
)
|
|
400
|
-
)
|
|
401
|
-
logging.debug(f"VPN container started, id: {vpn_container.id}")
|
|
402
|
-
|
|
403
|
-
with open(f"{self._dir}/vpn_id", "w") as f:
|
|
404
|
-
f.write(vpn_container.id)
|
|
405
|
-
|
|
406
|
-
# cleanup_task = asyncio.create_task(
|
|
407
|
-
# self.proximl.connections.cleanup_connections()
|
|
408
|
-
# )
|
|
409
|
-
|
|
410
|
-
count = 0
|
|
411
|
-
while count <= 30:
|
|
412
|
-
logging.debug(f"Test connectivity attempt {count+1}")
|
|
413
|
-
res = await self._test_connection(vpn_container)
|
|
414
|
-
if res:
|
|
415
|
-
logging.debug(f"Test connectivity successful {count+1}")
|
|
416
|
-
break
|
|
417
|
-
count += 1
|
|
418
|
-
await docker.close()
|
|
419
|
-
if count > 30:
|
|
420
|
-
self._status = STATUSES.get("NOT_CONNECTED")
|
|
421
|
-
raise ConnectionError(f"Unable to connect {self.type} {self.id}")
|
|
422
|
-
self._status = STATUSES.get("CONNECTED")
|
|
423
|
-
logging.info(f"Connection Successful.")
|
|
424
|
-
# await cleanup_task
|
|
425
|
-
logging.debug(f"Completed start {self.type} connection {self.id}")
|
|
426
|
-
|
|
427
|
-
async def stop(self):
|
|
428
|
-
logging.debug(f"Beginning stop {self.type} connection {self.id}")
|
|
429
|
-
if not self._entity:
|
|
430
|
-
await self._get_entity()
|
|
431
|
-
docker = aiodocker.Docker()
|
|
432
|
-
await _check_docker_status(docker)
|
|
433
|
-
|
|
434
|
-
tasks = []
|
|
435
|
-
logging.info("Disconnecting...")
|
|
436
|
-
try:
|
|
437
|
-
with open(f"{self._dir}/vpn_id", "r") as f:
|
|
438
|
-
vpn_id = f.read()
|
|
439
|
-
logging.debug(f"vpn container id: {vpn_id}")
|
|
440
|
-
vpn_container = await docker.containers.get(vpn_id)
|
|
441
|
-
vpn_delete_task = asyncio.create_task(
|
|
442
|
-
vpn_container.delete(force=True)
|
|
443
|
-
)
|
|
444
|
-
tasks.append(vpn_delete_task)
|
|
445
|
-
os.remove(f"{self._dir}/vpn_id")
|
|
446
|
-
except OSError:
|
|
447
|
-
logging.debug("vpn container not found")
|
|
448
|
-
|
|
449
|
-
storage_delete_task = None
|
|
450
|
-
try:
|
|
451
|
-
with open(f"{self._dir}/storage_id", "r") as f:
|
|
452
|
-
storage_id = f.read()
|
|
453
|
-
logging.debug(f"storage container id: {vpn_id}")
|
|
454
|
-
storage_container = await docker.containers.get(storage_id)
|
|
455
|
-
storage_delete_task = asyncio.create_task(
|
|
456
|
-
storage_container.delete(force=True)
|
|
457
|
-
)
|
|
458
|
-
tasks.append(storage_delete_task)
|
|
459
|
-
os.remove(f"{self._dir}/storage_id")
|
|
460
|
-
except OSError:
|
|
461
|
-
logging.debug("storage container not found")
|
|
462
|
-
|
|
463
|
-
await asyncio.gather(*tasks)
|
|
464
|
-
await docker.close()
|
|
465
|
-
self._status = STATUSES.get("REMOVED")
|
|
466
|
-
shutil.rmtree(self._dir)
|
|
467
|
-
logging.info("Disconnected.")
|
|
468
|
-
logging.debug(f"Completed stop {self.type} connection {self.id}")
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
async def _cleanup_containers(project, path, con_dirs, type):
|
|
472
|
-
containers_target = []
|
|
473
|
-
for con_dir in con_dirs:
|
|
474
|
-
try:
|
|
475
|
-
with open(f"{path}/{con_dir}/{type}_id", "r") as f:
|
|
476
|
-
id = f.read()
|
|
477
|
-
containers_target.append(id)
|
|
478
|
-
except OSError:
|
|
479
|
-
continue
|
|
480
|
-
|
|
481
|
-
docker = aiodocker.Docker()
|
|
482
|
-
await _check_docker_status(docker)
|
|
483
|
-
|
|
484
|
-
if project:
|
|
485
|
-
filter = dict(
|
|
486
|
-
label=[
|
|
487
|
-
"service=proximl",
|
|
488
|
-
f"type={type}",
|
|
489
|
-
f"project={project}",
|
|
490
|
-
]
|
|
491
|
-
)
|
|
492
|
-
else:
|
|
493
|
-
filter = dict(
|
|
494
|
-
label=[
|
|
495
|
-
"service=proximl",
|
|
496
|
-
f"type={type}",
|
|
497
|
-
]
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
containers = await docker.containers.list(
|
|
501
|
-
all=True,
|
|
502
|
-
filters=json.dumps(filter),
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
tasks = [
|
|
506
|
-
asyncio.create_task(container.delete(force=True))
|
|
507
|
-
for container in containers
|
|
508
|
-
if container.id not in containers_target
|
|
509
|
-
]
|
|
510
|
-
|
|
511
|
-
await asyncio.gather(*tasks, return_exceptions=True)
|
|
512
|
-
await docker.close()
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
def _parse_cidr(cidr):
|
|
516
|
-
res = re.match(
|
|
517
|
-
r"(?P<first_octet>[0-9]{1,3})\.(?P<second_octet>[0-9]{1,3})\.(?P<third_octet>[0-9]{1,3})\.(?P<fourth_octet>[0-9]{1,3})/(?P<mask_length>[0-9]{1,2})",
|
|
518
|
-
cidr,
|
|
519
|
-
)
|
|
520
|
-
net = res.groupdict()
|
|
521
|
-
return net
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
def _get_vpn_container_config(id, project_uuid, entity_type, cidr, data_dir):
|
|
525
|
-
config = dict(
|
|
526
|
-
Image=VPN_IMAGE,
|
|
527
|
-
Hostname=id,
|
|
528
|
-
Cmd=[],
|
|
529
|
-
AttachStdin=False,
|
|
530
|
-
AttachStdout=False,
|
|
531
|
-
AttachStderr=False,
|
|
532
|
-
Tty=False,
|
|
533
|
-
Env=[
|
|
534
|
-
f"NETWORK={id}",
|
|
535
|
-
"DEBUG=1",
|
|
536
|
-
],
|
|
537
|
-
HostConfig=dict(
|
|
538
|
-
Init=True,
|
|
539
|
-
Binds=[f"{data_dir}:/etc/tinc:rw"],
|
|
540
|
-
NetworkMode="host",
|
|
541
|
-
CapAdd=["NET_ADMIN"],
|
|
542
|
-
),
|
|
543
|
-
Labels=dict(
|
|
544
|
-
type="vpn",
|
|
545
|
-
service="proximl",
|
|
546
|
-
id=id,
|
|
547
|
-
project=project_uuid,
|
|
548
|
-
entity_type=entity_type,
|
|
549
|
-
),
|
|
550
|
-
)
|
|
551
|
-
return config
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
def _get_storage_container_config(
|
|
555
|
-
id,
|
|
556
|
-
project_uuid,
|
|
557
|
-
entity_type,
|
|
558
|
-
cidr,
|
|
559
|
-
data_dir,
|
|
560
|
-
ssh_port,
|
|
561
|
-
model_path=None,
|
|
562
|
-
input_path=None,
|
|
563
|
-
output_path=None,
|
|
564
|
-
):
|
|
565
|
-
Binds = [f"{data_dir}/.ssh:/opt/ssh"]
|
|
566
|
-
if model_path:
|
|
567
|
-
Binds.append(f"{os.path.expanduser(model_path)}:/opt/model:ro")
|
|
568
|
-
if input_path:
|
|
569
|
-
Binds.append(f"{os.path.expanduser(input_path)}:/opt/data:ro")
|
|
570
|
-
if output_path:
|
|
571
|
-
Binds.append(f"{os.path.expanduser(output_path)}:/opt/output:rw")
|
|
572
|
-
config = dict(
|
|
573
|
-
Image=STORAGE_IMAGE,
|
|
574
|
-
Hostname=id,
|
|
575
|
-
Cmd=[],
|
|
576
|
-
AttachStdin=False,
|
|
577
|
-
AttachStdout=False,
|
|
578
|
-
AttachStderr=False,
|
|
579
|
-
Tty=False,
|
|
580
|
-
Env=[
|
|
581
|
-
f"VPN_CIDR={cidr}",
|
|
582
|
-
],
|
|
583
|
-
ExposedPorts={f"22/tcp": {}},
|
|
584
|
-
HostConfig=dict(
|
|
585
|
-
Init=True,
|
|
586
|
-
Binds=Binds,
|
|
587
|
-
PortBindings={
|
|
588
|
-
f"22/tcp": [dict(HostPort=f"{ssh_port}", HostIP="0.0.0.0")],
|
|
589
|
-
},
|
|
590
|
-
),
|
|
591
|
-
Labels=dict(
|
|
592
|
-
type="storage",
|
|
593
|
-
service="proximl",
|
|
594
|
-
id=id,
|
|
595
|
-
project=project_uuid,
|
|
596
|
-
entity_type=entity_type,
|
|
597
|
-
),
|
|
598
|
-
)
|
|
599
|
-
return config
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
async def _image_exists(client, id):
|
|
603
|
-
if not id:
|
|
604
|
-
return False
|
|
605
|
-
try:
|
|
606
|
-
await client.images.inspect(id)
|
|
607
|
-
return True
|
|
608
|
-
except DockerError:
|
|
609
|
-
return False
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
async def _check_docker_status(client):
|
|
613
|
-
try:
|
|
614
|
-
await client.version()
|
|
615
|
-
except DockerError:
|
|
616
|
-
raise ProxiMLException(
|
|
617
|
-
"Docker is not installed, not running, or your user does not have docker privileges. "
|
|
618
|
-
+ "Ensure docker is installed, then add your user to the docker group. "
|
|
619
|
-
+ "Commonly, this is accomplished by entering 'sudo gpasswd -a <username> docker'. "
|
|
620
|
-
+ "You may need to logout and log back in to your computer for this to take effect"
|
|
621
|
-
)
|