proximl 0.5.0__py3-none-any.whl → 0.5.2__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.
- proximl/cli/cloudbender/device.py +157 -0
- proximl/cloudbender/devices.py +190 -0
- {proximl-0.5.0.dist-info → proximl-0.5.2.dist-info}/METADATA +1 -1
- {proximl-0.5.0.dist-info → proximl-0.5.2.dist-info}/RECORD +10 -6
- tests/unit/cli/cloudbender/test_cli_device_unit.py +38 -0
- tests/unit/cloudbender/test_devices_unit.py +270 -0
- {proximl-0.5.0.dist-info → proximl-0.5.2.dist-info}/LICENSE +0 -0
- {proximl-0.5.0.dist-info → proximl-0.5.2.dist-info}/WHEEL +0 -0
- {proximl-0.5.0.dist-info → proximl-0.5.2.dist-info}/entry_points.txt +0 -0
- {proximl-0.5.0.dist-info → proximl-0.5.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from proximl.cli import cli, pass_config, search_by_id_name
|
|
3
|
+
from proximl.cli.cloudbender import cloudbender
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@cloudbender.group()
|
|
7
|
+
@pass_config
|
|
8
|
+
def device(config):
|
|
9
|
+
"""proxiML CloudBender device commands."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@device.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 devices for.",
|
|
27
|
+
)
|
|
28
|
+
@pass_config
|
|
29
|
+
def list(config, provider, region):
|
|
30
|
+
"""List devices."""
|
|
31
|
+
data = [
|
|
32
|
+
[
|
|
33
|
+
"ID",
|
|
34
|
+
"NAME",
|
|
35
|
+
"STATUS",
|
|
36
|
+
"JOB STATUS",
|
|
37
|
+
"ONLINE",
|
|
38
|
+
"MAINTENANCE",
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"-" * 80,
|
|
42
|
+
"-" * 80,
|
|
43
|
+
"-" * 80,
|
|
44
|
+
"-" * 80,
|
|
45
|
+
"-" * 80,
|
|
46
|
+
"-" * 80,
|
|
47
|
+
"-" * 80,
|
|
48
|
+
],
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
devices = config.proximl.run(
|
|
52
|
+
config.proximl.client.cloudbender.devices.list(
|
|
53
|
+
provider_uuid=provider, region_uuid=region
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
for device in devices:
|
|
58
|
+
data.append(
|
|
59
|
+
[
|
|
60
|
+
device.id,
|
|
61
|
+
device.name,
|
|
62
|
+
device.status,
|
|
63
|
+
device.job_status,
|
|
64
|
+
"X" if device.online else "",
|
|
65
|
+
"X" if device.maintenance_mode else "",
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
for row in data:
|
|
70
|
+
click.echo(
|
|
71
|
+
"{: >37.36} {: >29.28} {: >9.8} {: >11.10} {: >7.6} {: >12.11}"
|
|
72
|
+
"".format(*row),
|
|
73
|
+
file=config.stdout,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@device.command()
|
|
78
|
+
@click.option(
|
|
79
|
+
"--provider",
|
|
80
|
+
"-p",
|
|
81
|
+
type=click.STRING,
|
|
82
|
+
required=True,
|
|
83
|
+
help="The provider ID of the region.",
|
|
84
|
+
)
|
|
85
|
+
@click.option(
|
|
86
|
+
"--region",
|
|
87
|
+
"-r",
|
|
88
|
+
type=click.STRING,
|
|
89
|
+
required=True,
|
|
90
|
+
help="The region ID to create the region in.",
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--minion-id",
|
|
94
|
+
"-m",
|
|
95
|
+
type=click.STRING,
|
|
96
|
+
required=True,
|
|
97
|
+
help="The minion_id of the new node.",
|
|
98
|
+
)
|
|
99
|
+
@click.option(
|
|
100
|
+
"--hostname",
|
|
101
|
+
"-h",
|
|
102
|
+
type=click.STRING,
|
|
103
|
+
help="The hostname (if different from name)",
|
|
104
|
+
)
|
|
105
|
+
@click.argument("name", type=click.STRING, required=True)
|
|
106
|
+
@pass_config
|
|
107
|
+
def create(config, provider, region, minion_id, hostname, name):
|
|
108
|
+
"""
|
|
109
|
+
Creates a node.
|
|
110
|
+
"""
|
|
111
|
+
if not hostname:
|
|
112
|
+
hostname = name
|
|
113
|
+
return config.proximl.run(
|
|
114
|
+
config.proximl.client.cloudbender.devices.create(
|
|
115
|
+
provider_uuid=provider,
|
|
116
|
+
region_uuid=region,
|
|
117
|
+
friendly_name=name,
|
|
118
|
+
hostname=hostname,
|
|
119
|
+
minion_id=minion_id,
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@device.command()
|
|
125
|
+
@click.option(
|
|
126
|
+
"--provider",
|
|
127
|
+
"-p",
|
|
128
|
+
type=click.STRING,
|
|
129
|
+
required=True,
|
|
130
|
+
help="The provider ID of the region.",
|
|
131
|
+
)
|
|
132
|
+
@click.option(
|
|
133
|
+
"--region",
|
|
134
|
+
"-r",
|
|
135
|
+
type=click.STRING,
|
|
136
|
+
required=True,
|
|
137
|
+
help="The region ID to delete the node from.",
|
|
138
|
+
)
|
|
139
|
+
@click.argument("device", type=click.STRING)
|
|
140
|
+
@pass_config
|
|
141
|
+
def remove(config, provider, region, node):
|
|
142
|
+
"""
|
|
143
|
+
Remove a device.
|
|
144
|
+
|
|
145
|
+
DEVICE may be specified by name or ID, but ID is preferred.
|
|
146
|
+
"""
|
|
147
|
+
devices = config.proximl.run(
|
|
148
|
+
config.proximl.client.cloudbender.devices.list(
|
|
149
|
+
provider_uuid=provider, region_uuid=region
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
found = search_by_id_name(device, devices)
|
|
154
|
+
if None is found:
|
|
155
|
+
raise click.UsageError("Cannot find specified device.")
|
|
156
|
+
|
|
157
|
+
return config.proximl.run(found.remove())
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Devices(object):
|
|
6
|
+
def __init__(self, proximl):
|
|
7
|
+
self.proximl = proximl
|
|
8
|
+
|
|
9
|
+
async def get(self, provider_uuid, region_uuid, id, **kwargs):
|
|
10
|
+
resp = await self.proximl._query(
|
|
11
|
+
f"/provider/{provider_uuid}/region/{region_uuid}/device/{id}",
|
|
12
|
+
"GET",
|
|
13
|
+
kwargs,
|
|
14
|
+
)
|
|
15
|
+
return Device(self.proximl, **resp)
|
|
16
|
+
|
|
17
|
+
async def list(self, provider_uuid, region_uuid, **kwargs):
|
|
18
|
+
resp = await self.proximl._query(
|
|
19
|
+
f"/provider/{provider_uuid}/region/{region_uuid}/device",
|
|
20
|
+
"GET",
|
|
21
|
+
kwargs,
|
|
22
|
+
)
|
|
23
|
+
devices = [Device(self.proximl, **device) for device in resp]
|
|
24
|
+
return devices
|
|
25
|
+
|
|
26
|
+
async def create(
|
|
27
|
+
self,
|
|
28
|
+
provider_uuid,
|
|
29
|
+
region_uuid,
|
|
30
|
+
friendly_name,
|
|
31
|
+
hostname,
|
|
32
|
+
minion_id,
|
|
33
|
+
**kwargs,
|
|
34
|
+
):
|
|
35
|
+
logging.info(f"Creating Device {friendly_name}")
|
|
36
|
+
data = dict(
|
|
37
|
+
friendly_name=friendly_name,
|
|
38
|
+
hostname=hostname,
|
|
39
|
+
minion_id=minion_id,
|
|
40
|
+
type="device",
|
|
41
|
+
service="compute",
|
|
42
|
+
**kwargs,
|
|
43
|
+
)
|
|
44
|
+
payload = {k: v for k, v in data.items() if v is not None}
|
|
45
|
+
resp = await self.proximl._query(
|
|
46
|
+
f"/provider/{provider_uuid}/region/{region_uuid}/device",
|
|
47
|
+
"POST",
|
|
48
|
+
None,
|
|
49
|
+
payload,
|
|
50
|
+
)
|
|
51
|
+
device = Device(self.proximl, **resp)
|
|
52
|
+
logging.info(f"Created Device {friendly_name} with id {device.id}")
|
|
53
|
+
return device
|
|
54
|
+
|
|
55
|
+
async def remove(self, provider_uuid, region_uuid, id, **kwargs):
|
|
56
|
+
await self.proximl._query(
|
|
57
|
+
f"/provider/{provider_uuid}/region/{region_uuid}/device/{id}",
|
|
58
|
+
"DELETE",
|
|
59
|
+
kwargs,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Device:
|
|
64
|
+
def __init__(self, proximl, **kwargs):
|
|
65
|
+
self.proximl = proximl
|
|
66
|
+
self._device = kwargs
|
|
67
|
+
self._id = self._device.get("device_id")
|
|
68
|
+
self._provider_uuid = self._device.get("provider_uuid")
|
|
69
|
+
self._region_uuid = self._device.get("region_uuid")
|
|
70
|
+
self._name = self._device.get("friendly_name")
|
|
71
|
+
self._hostname = self._device.get("hostname")
|
|
72
|
+
self._status = self._device.get("status")
|
|
73
|
+
self._online = self._device.get("online")
|
|
74
|
+
self._maintenance_mode = self._device.get("maintenance_mode")
|
|
75
|
+
self._device_config_id = self._device.get("device_config_id")
|
|
76
|
+
self._job_status = self._device.get("job_status")
|
|
77
|
+
self._job_last_deployed = self._device.get("job_last_deployed")
|
|
78
|
+
self._job_config_id = self._device.get("job_config_id")
|
|
79
|
+
self._job_config_revision = self._device.get("job_config_revision")
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def id(self) -> str:
|
|
83
|
+
return self._id
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def provider_uuid(self) -> str:
|
|
87
|
+
return self._provider_uuid
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def region_uuid(self) -> str:
|
|
91
|
+
return self._region_uuid
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def name(self) -> str:
|
|
95
|
+
return self._name
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def hostname(self) -> str:
|
|
99
|
+
return self._hostname
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def status(self) -> str:
|
|
103
|
+
return self._status
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def online(self) -> bool:
|
|
107
|
+
return self._online
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def maintenance_mode(self) -> bool:
|
|
111
|
+
return self._maintenance_mode
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def device_config_id(self) -> str:
|
|
115
|
+
return self._device_config_id
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def job_status(self) -> str:
|
|
119
|
+
return self._job_status
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def job_last_deployed(self) -> str:
|
|
123
|
+
return self._job_last_deployed
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def job_config_id(self) -> str:
|
|
127
|
+
return self._job_config_id
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def job_config_revision(self) -> str:
|
|
131
|
+
return self._job_config_revision
|
|
132
|
+
|
|
133
|
+
def __str__(self):
|
|
134
|
+
return json.dumps({k: v for k, v in self._device.items()})
|
|
135
|
+
|
|
136
|
+
def __repr__(self):
|
|
137
|
+
return f"Device( proximl , **{self._device.__repr__()})"
|
|
138
|
+
|
|
139
|
+
def __bool__(self):
|
|
140
|
+
return bool(self._id)
|
|
141
|
+
|
|
142
|
+
async def remove(self):
|
|
143
|
+
await self.proximl._query(
|
|
144
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}",
|
|
145
|
+
"DELETE",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
async def refresh(self):
|
|
149
|
+
resp = await self.proximl._query(
|
|
150
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}",
|
|
151
|
+
"GET",
|
|
152
|
+
)
|
|
153
|
+
self.__init__(self.proximl, **resp)
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
async def toggle_maintenance(self):
|
|
157
|
+
await self.proximl._query(
|
|
158
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/maintenance",
|
|
159
|
+
"PATCH",
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def run_action(self, command):
|
|
163
|
+
await self.proximl._query(
|
|
164
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/action",
|
|
165
|
+
"POST",
|
|
166
|
+
None,
|
|
167
|
+
dict(command=command),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
async def set_config(self, device_config_id):
|
|
171
|
+
resp = await self.proximl._query(
|
|
172
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}",
|
|
173
|
+
"PATCH",
|
|
174
|
+
None,
|
|
175
|
+
dict(device_config_id=device_config_id),
|
|
176
|
+
)
|
|
177
|
+
self.__init__(self.proximl, **resp)
|
|
178
|
+
return self
|
|
179
|
+
|
|
180
|
+
async def deploy_endpoint(self):
|
|
181
|
+
await self.proximl._query(
|
|
182
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/deploy",
|
|
183
|
+
"PUT",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
async def stop_endpoint(self):
|
|
187
|
+
await self.proximl._query(
|
|
188
|
+
f"/provider/{self._provider_uuid}/region/{self._region_uuid}/device/{self._id}/stop",
|
|
189
|
+
"PUT",
|
|
190
|
+
)
|
|
@@ -25,6 +25,7 @@ proximl/cli/model.py,sha256=x1iG41tcYZuQdhA1qa_BQqLmpp0uteW6ieUCKlgzGRc,6071
|
|
|
25
25
|
proximl/cli/project.py,sha256=Er1twSiWQSAKir-hBIT9fRo2fc_UGqFoIJOwwjQGmlo,3522
|
|
26
26
|
proximl/cli/cloudbender/__init__.py,sha256=qWLg19Gq-2WHnBZZ4Un10QyY8ipNOA0_LK-Lm9yeM4w,484
|
|
27
27
|
proximl/cli/cloudbender/datastore.py,sha256=_vQOj-NfrL_nj4HfxNJL63TJZjLgfDyztRLyaRU58v8,3478
|
|
28
|
+
proximl/cli/cloudbender/device.py,sha256=FdQZPESP6YBfUSzXq1Byu7eNMKi59qSOICONK-TEljI,3453
|
|
28
29
|
proximl/cli/cloudbender/node.py,sha256=xxzj68YvpRey2vZQasgYTnwv3x7TnwpuPSSf8Ma5a54,3843
|
|
29
30
|
proximl/cli/cloudbender/provider.py,sha256=qhWbDK1tWi00wQWEYqGw7yGoZx0nEjV40GLHRuuE86c,1726
|
|
30
31
|
proximl/cli/cloudbender/region.py,sha256=WnSkY4dXKRJ-FNaoxMfmoh6iuUx5dXCNJmEFT34Xtao,2892
|
|
@@ -35,6 +36,7 @@ proximl/cloudbender/__init__.py,sha256=iE29obtC0_9f0IhRvHQcG5aY58fVhVYipTakpjAhd
|
|
|
35
36
|
proximl/cloudbender/cloudbender.py,sha256=Bn4l7ypAi4HzQbhrL_Ss0MY6S775CNaZ87YUxp9rD8Y,565
|
|
36
37
|
proximl/cloudbender/datastores.py,sha256=rE_6A2rH-muET3mFus9oXU8jPc2y5poZFs_O7cuWx4M,3245
|
|
37
38
|
proximl/cloudbender/device_configs.py,sha256=7kvnSZ68kZEkUymPMNlCIJUTg5fMIJCNrODbPZAkLdc,3134
|
|
39
|
+
proximl/cloudbender/devices.py,sha256=vHooaOw2k2Tf99FJHnVZTgggqCTYJg7rq46aUPW0k8M,5660
|
|
38
40
|
proximl/cloudbender/nodes.py,sha256=85mC7H8MkU4tGiyk51GjKgK7O9XBuKQu94Rg7ECqFnc,3608
|
|
39
41
|
proximl/cloudbender/providers.py,sha256=-DxBpWgKteBacwNluUxrKST4HuyneNg57bz1y9CcUko,1834
|
|
40
42
|
proximl/cloudbender/regions.py,sha256=7mkjWjrpJjRjlkzOWZlxzGo4HgrfPvhITwAeBZfXnDo,3339
|
|
@@ -74,6 +76,7 @@ tests/unit/cli/test_cli_model_unit.py,sha256=AucngxvYjW6GidDGBPHnKyYOb82ff7xMX5m
|
|
|
74
76
|
tests/unit/cli/test_cli_project_unit.py,sha256=ms9gJ8pgMNGeIMdFcvBcwSPmb0i2qo9-rk9CCF53-9M,1756
|
|
75
77
|
tests/unit/cli/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
78
|
tests/unit/cli/cloudbender/test_cli_datastore_unit.py,sha256=sUZWnzzCCG7NdgzEHsQ2zHpEb-ZD6FDIazca6FqMOc0,1381
|
|
79
|
+
tests/unit/cli/cloudbender/test_cli_device_unit.py,sha256=o1vrPlbaanYK1iJG5pE6tDwgmOXDuLUY0VH8DxtaPYI,1342
|
|
77
80
|
tests/unit/cli/cloudbender/test_cli_node_unit.py,sha256=uh1Nt0ewk0v81iN5wCyBPzSXAhD8clW98HAORsCjqrg,1316
|
|
78
81
|
tests/unit/cli/cloudbender/test_cli_provider_unit.py,sha256=jCnFnqZuLzuDx9u3kLyjT83nBDWKn7LDCz6ErzCce1g,781
|
|
79
82
|
tests/unit/cli/cloudbender/test_cli_region_unit.py,sha256=mEAU0z_gKDM-e5J_V8igXmiU4qjrOfzJJRtKRNWdeBs,1262
|
|
@@ -81,13 +84,14 @@ tests/unit/cli/cloudbender/test_cli_reservation_unit.py,sha256=Lcr-xeNVAtVLHllQn
|
|
|
81
84
|
tests/unit/cloudbender/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
85
|
tests/unit/cloudbender/test_datastores_unit.py,sha256=Sk-qg4fpBSkW8D3cnnfjAQdWjADtV9uHfLpqexl0loo,4644
|
|
83
86
|
tests/unit/cloudbender/test_device_configs_unit.py,sha256=Dp8GaeD0lEt9giVAX6f3r_G0ELLgIZux_887gR32M3M,4858
|
|
87
|
+
tests/unit/cloudbender/test_devices_unit.py,sha256=C2YTnfIpHlxdidgfbTnlzl72r5O7kqKStQUhWcTTXDg,9103
|
|
84
88
|
tests/unit/cloudbender/test_nodes_unit.py,sha256=vk6lWGr1PBWeJZNIVywPN_UzwuZdRW3xZuh_iT0gCeA,4686
|
|
85
89
|
tests/unit/cloudbender/test_providers_unit.py,sha256=8KURiz5zz2saBcAYIPPvLBx3wVOUhN5kK36MD64-oyw,3730
|
|
86
90
|
tests/unit/cloudbender/test_regions_unit.py,sha256=4QivFfejiI8cI47DsmJGgDw-tY9ENol3dKRwcVEDpr0,5635
|
|
87
91
|
tests/unit/cloudbender/test_reservations_unit.py,sha256=MlUWr8cXM7n-CTqd4sfFaZMEV6jka802Hnhmg6dAeGU,4827
|
|
88
|
-
proximl-0.5.
|
|
89
|
-
proximl-0.5.
|
|
90
|
-
proximl-0.5.
|
|
91
|
-
proximl-0.5.
|
|
92
|
-
proximl-0.5.
|
|
93
|
-
proximl-0.5.
|
|
92
|
+
proximl-0.5.2.dist-info/LICENSE,sha256=ADFxLEZDxKY0j4MdyUd5GNuhQ18rnWH5rOz1ZG7yiOA,1069
|
|
93
|
+
proximl-0.5.2.dist-info/METADATA,sha256=fc1Q36A0tQBo_Gb3KoPlCqfOOopX0WvMOtjXqyAiRC8,7344
|
|
94
|
+
proximl-0.5.2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
|
95
|
+
proximl-0.5.2.dist-info/entry_points.txt,sha256=HmI311IIabkZReMCXu-nGbvIEW-KfaduAOyfiSqt5SY,63
|
|
96
|
+
proximl-0.5.2.dist-info/top_level.txt,sha256=-TWqc9tAaxmWmW4c7uYsmzPEYUIoh6z02xxqPbv7Kys,23
|
|
97
|
+
proximl-0.5.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,38 @@
|
|
|
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.devices]
|
|
8
|
+
|
|
9
|
+
from proximl.cli.cloudbender import device as specimen
|
|
10
|
+
from proximl.cloudbender.devices import Device
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_list(runner, mock_devices):
|
|
14
|
+
with patch("proximl.cli.ProxiML", new=AsyncMock) as mock_proximl:
|
|
15
|
+
mock_proximl.cloudbender = AsyncMock()
|
|
16
|
+
mock_proximl.cloudbender.devices = AsyncMock()
|
|
17
|
+
mock_proximl.cloudbender.devices.list = AsyncMock(
|
|
18
|
+
return_value=mock_devices
|
|
19
|
+
)
|
|
20
|
+
result = runner.invoke(
|
|
21
|
+
specimen,
|
|
22
|
+
args=["list", "--provider=prov-id-1", "--region=reg-id-1"],
|
|
23
|
+
)
|
|
24
|
+
assert result.exit_code == 0
|
|
25
|
+
mock_proximl.cloudbender.devices.list.assert_called_once_with(
|
|
26
|
+
provider_uuid="prov-id-1", region_uuid="reg-id-1"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_list_no_provider(runner, mock_devices):
|
|
31
|
+
with patch("proximl.cli.ProxiML", new=AsyncMock) as mock_proximl:
|
|
32
|
+
mock_proximl.cloudbender = AsyncMock()
|
|
33
|
+
mock_proximl.cloudbender.devices = AsyncMock()
|
|
34
|
+
mock_proximl.cloudbender.devices.list = AsyncMock(
|
|
35
|
+
return_value=mock_devices
|
|
36
|
+
)
|
|
37
|
+
result = runner.invoke(specimen, ["list"])
|
|
38
|
+
assert result.exit_code != 0
|
|
@@ -0,0 +1,270 @@
|
|
|
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.cloudbender.devices as specimen
|
|
9
|
+
from proximl.exceptions import (
|
|
10
|
+
ApiError,
|
|
11
|
+
SpecificationError,
|
|
12
|
+
ProxiMLException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
pytestmark = [mark.sdk, mark.unit, mark.cloudbender, mark.devices]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@fixture
|
|
19
|
+
def devices(mock_proximl):
|
|
20
|
+
yield specimen.Devices(mock_proximl)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@fixture
|
|
24
|
+
def device(mock_proximl):
|
|
25
|
+
yield specimen.Device(
|
|
26
|
+
mock_proximl,
|
|
27
|
+
provider_uuid="1",
|
|
28
|
+
region_uuid="a",
|
|
29
|
+
device_id="x",
|
|
30
|
+
type="device",
|
|
31
|
+
service="compute",
|
|
32
|
+
friendly_name="hq-orin-01",
|
|
33
|
+
hostname="hq-orin-01",
|
|
34
|
+
status="active",
|
|
35
|
+
online=True,
|
|
36
|
+
maintenance_mode=False,
|
|
37
|
+
job_status="stopped",
|
|
38
|
+
job_last_deployed="2023-06-02T21:22:40.084Z",
|
|
39
|
+
job_config_id="job-id-1",
|
|
40
|
+
job_config_revision="1685740490096",
|
|
41
|
+
device_config_id="conf-id-2",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class RegionsTests:
|
|
46
|
+
@mark.asyncio
|
|
47
|
+
async def test_get_device(
|
|
48
|
+
self,
|
|
49
|
+
devices,
|
|
50
|
+
mock_proximl,
|
|
51
|
+
):
|
|
52
|
+
api_response = dict()
|
|
53
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
54
|
+
await devices.get("1234", "5687", "91011")
|
|
55
|
+
mock_proximl._query.assert_called_once_with(
|
|
56
|
+
"/provider/1234/region/5687/device/91011", "GET", {}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@mark.asyncio
|
|
60
|
+
async def test_list_devices(
|
|
61
|
+
self,
|
|
62
|
+
devices,
|
|
63
|
+
mock_proximl,
|
|
64
|
+
):
|
|
65
|
+
api_response = dict()
|
|
66
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
67
|
+
await devices.list("1234", "5687")
|
|
68
|
+
mock_proximl._query.assert_called_once_with(
|
|
69
|
+
"/provider/1234/region/5687/device", "GET", {}
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@mark.asyncio
|
|
73
|
+
async def test_remove_device(
|
|
74
|
+
self,
|
|
75
|
+
devices,
|
|
76
|
+
mock_proximl,
|
|
77
|
+
):
|
|
78
|
+
api_response = dict()
|
|
79
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
80
|
+
await devices.remove("1234", "4567", "8910")
|
|
81
|
+
mock_proximl._query.assert_called_once_with(
|
|
82
|
+
"/provider/1234/region/4567/device/8910", "DELETE", {}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@mark.asyncio
|
|
86
|
+
async def test_create_device(self, devices, mock_proximl):
|
|
87
|
+
requested_config = dict(
|
|
88
|
+
provider_uuid="provider-id-1",
|
|
89
|
+
region_uuid="region-id-1",
|
|
90
|
+
friendly_name="phys-device",
|
|
91
|
+
hostname="phys-device",
|
|
92
|
+
minion_id="asdf",
|
|
93
|
+
)
|
|
94
|
+
expected_payload = dict(
|
|
95
|
+
friendly_name="phys-device",
|
|
96
|
+
hostname="phys-device",
|
|
97
|
+
minion_id="asdf",
|
|
98
|
+
type="device",
|
|
99
|
+
service="compute",
|
|
100
|
+
)
|
|
101
|
+
api_response = {
|
|
102
|
+
"provider_uuid": "provider-id-1",
|
|
103
|
+
"region_uuid": "region-id-1",
|
|
104
|
+
"device_id": "rig-id-1",
|
|
105
|
+
"name": "phys-device",
|
|
106
|
+
"type": "device",
|
|
107
|
+
"service": "compute",
|
|
108
|
+
"status": "new",
|
|
109
|
+
"online": False,
|
|
110
|
+
"maintenance_mode": True,
|
|
111
|
+
"job_status": "stopped",
|
|
112
|
+
"job_last_deployed": "2023-06-02T21:22:40.084Z",
|
|
113
|
+
"job_config_id": "job-id-1",
|
|
114
|
+
"job_config_revision": "1685740490096",
|
|
115
|
+
"device_config_id": "conf-id-1",
|
|
116
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
120
|
+
response = await devices.create(**requested_config)
|
|
121
|
+
mock_proximl._query.assert_called_once_with(
|
|
122
|
+
"/provider/provider-id-1/region/region-id-1/device",
|
|
123
|
+
"POST",
|
|
124
|
+
None,
|
|
125
|
+
expected_payload,
|
|
126
|
+
)
|
|
127
|
+
assert response.id == "rig-id-1"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class deviceTests:
|
|
131
|
+
def test_device_properties(self, device):
|
|
132
|
+
assert isinstance(device.id, str)
|
|
133
|
+
assert isinstance(device.provider_uuid, str)
|
|
134
|
+
assert isinstance(device.region_uuid, str)
|
|
135
|
+
assert isinstance(device.name, str)
|
|
136
|
+
assert isinstance(device.hostname, str)
|
|
137
|
+
assert isinstance(device.status, str)
|
|
138
|
+
assert isinstance(device.online, bool)
|
|
139
|
+
assert isinstance(device.maintenance_mode, bool)
|
|
140
|
+
assert isinstance(device.device_config_id, str)
|
|
141
|
+
assert isinstance(device.job_status, str)
|
|
142
|
+
assert isinstance(device.job_last_deployed, str)
|
|
143
|
+
assert isinstance(device.job_config_id, str)
|
|
144
|
+
assert isinstance(device.job_config_revision, str)
|
|
145
|
+
|
|
146
|
+
def test_device_str(self, device):
|
|
147
|
+
string = str(device)
|
|
148
|
+
regex = r"^{.*\"device_id\": \"" + device.id + r"\".*}$"
|
|
149
|
+
assert isinstance(string, str)
|
|
150
|
+
assert re.match(regex, string)
|
|
151
|
+
|
|
152
|
+
def test_device_repr(self, device):
|
|
153
|
+
string = repr(device)
|
|
154
|
+
regex = (
|
|
155
|
+
r"^Device\( proximl , \*\*{.*'device_id': '"
|
|
156
|
+
+ device.id
|
|
157
|
+
+ r"'.*}\)$"
|
|
158
|
+
)
|
|
159
|
+
assert isinstance(string, str)
|
|
160
|
+
assert re.match(regex, string)
|
|
161
|
+
|
|
162
|
+
def test_device_bool(self, device, mock_proximl):
|
|
163
|
+
empty_device = specimen.Device(mock_proximl)
|
|
164
|
+
assert bool(device)
|
|
165
|
+
assert not bool(empty_device)
|
|
166
|
+
|
|
167
|
+
@mark.asyncio
|
|
168
|
+
async def test_device_remove(self, device, mock_proximl):
|
|
169
|
+
api_response = dict()
|
|
170
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
171
|
+
await device.remove()
|
|
172
|
+
mock_proximl._query.assert_called_once_with(
|
|
173
|
+
"/provider/1/region/a/device/x", "DELETE"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@mark.asyncio
|
|
177
|
+
async def test_device_refresh(self, device, mock_proximl):
|
|
178
|
+
api_response = {
|
|
179
|
+
"provider_uuid": "provider-id-1",
|
|
180
|
+
"region_uuid": "region-id-1",
|
|
181
|
+
"device_id": "device-id-1",
|
|
182
|
+
"name": "phys-device",
|
|
183
|
+
"type": "device",
|
|
184
|
+
"service": "compute",
|
|
185
|
+
"status": "new",
|
|
186
|
+
"online": False,
|
|
187
|
+
"maintenance_mode": True,
|
|
188
|
+
"job_status": "stopped",
|
|
189
|
+
"job_last_deployed": "2023-06-02T21:22:40.084Z",
|
|
190
|
+
"job_config_id": "job-id-1",
|
|
191
|
+
"job_config_revision": "1685740490096",
|
|
192
|
+
"device_config_id": "conf-id-1",
|
|
193
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
194
|
+
}
|
|
195
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
196
|
+
response = await device.refresh()
|
|
197
|
+
mock_proximl._query.assert_called_once_with(
|
|
198
|
+
f"/provider/1/region/a/device/x", "GET"
|
|
199
|
+
)
|
|
200
|
+
assert device.id == "device-id-1"
|
|
201
|
+
assert response.id == "device-id-1"
|
|
202
|
+
|
|
203
|
+
@mark.asyncio
|
|
204
|
+
async def test_device_toggle_maintenance(self, device, mock_proximl):
|
|
205
|
+
api_response = None
|
|
206
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
207
|
+
await device.toggle_maintenance()
|
|
208
|
+
mock_proximl._query.assert_called_once_with(
|
|
209
|
+
"/provider/1/region/a/device/x/maintenance", "PATCH"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
@mark.asyncio
|
|
213
|
+
async def test_device_run_action(self, device, mock_proximl):
|
|
214
|
+
api_response = None
|
|
215
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
216
|
+
await device.run_action(command="report")
|
|
217
|
+
mock_proximl._query.assert_called_once_with(
|
|
218
|
+
"/provider/1/region/a/device/x/action",
|
|
219
|
+
"POST",
|
|
220
|
+
None,
|
|
221
|
+
dict(command="report"),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
@mark.asyncio
|
|
225
|
+
async def test_device_set_config(self, device, mock_proximl):
|
|
226
|
+
api_response = {
|
|
227
|
+
"provider_uuid": "provider-id-1",
|
|
228
|
+
"region_uuid": "region-id-1",
|
|
229
|
+
"device_id": "device-id-1",
|
|
230
|
+
"name": "phys-device",
|
|
231
|
+
"type": "device",
|
|
232
|
+
"service": "compute",
|
|
233
|
+
"status": "new",
|
|
234
|
+
"online": False,
|
|
235
|
+
"maintenance_mode": True,
|
|
236
|
+
"job_status": "stopped",
|
|
237
|
+
"job_last_deployed": "2023-06-02T21:22:40.084Z",
|
|
238
|
+
"job_config_id": "job-id-1",
|
|
239
|
+
"job_config_revision": "1685740490096",
|
|
240
|
+
"device_config_id": "config-id-1",
|
|
241
|
+
"createdAt": "2020-12-31T23:59:59.000Z",
|
|
242
|
+
}
|
|
243
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
244
|
+
response = await device.set_config(device_config_id="config-id-1")
|
|
245
|
+
mock_proximl._query.assert_called_once_with(
|
|
246
|
+
"/provider/1/region/a/device/x",
|
|
247
|
+
"PATCH",
|
|
248
|
+
None,
|
|
249
|
+
dict(device_config_id="config-id-1"),
|
|
250
|
+
)
|
|
251
|
+
assert device.id == "device-id-1"
|
|
252
|
+
assert response.id == "device-id-1"
|
|
253
|
+
|
|
254
|
+
@mark.asyncio
|
|
255
|
+
async def test_device_deploy_endpoint(self, device, mock_proximl):
|
|
256
|
+
api_response = None
|
|
257
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
258
|
+
await device.deploy_endpoint()
|
|
259
|
+
mock_proximl._query.assert_called_once_with(
|
|
260
|
+
"/provider/1/region/a/device/x/deploy", "PUT"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
@mark.asyncio
|
|
264
|
+
async def test_device_stop_endpoint(self, device, mock_proximl):
|
|
265
|
+
api_response = None
|
|
266
|
+
mock_proximl._query = AsyncMock(return_value=api_response)
|
|
267
|
+
await device.stop_endpoint()
|
|
268
|
+
mock_proximl._query.assert_called_once_with(
|
|
269
|
+
"/provider/1/region/a/device/x/stop", "PUT"
|
|
270
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|