fc-server 0.7.0__tar.gz → 0.7.2__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.
- {fc-server-0.7.0 → fc-server-0.7.2}/PKG-INFO +2 -3
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_server/docker-compose.yaml +1 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_client/client.py +191 -91
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_client_daemon/client_daemon.py +128 -45
- fc-server-0.7.2/fc_client_docker/fc_client_docker +3 -0
- fc-server-0.7.2/fc_common/VERSION +1 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/etcd.py +5 -1
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/version.py +5 -6
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/__init__.py +6 -3
- fc-server-0.7.2/fc_server/core/api_svr.py +494 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/config.py +18 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/coordinator.py +11 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/labgrid.py +73 -5
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/lava.py +8 -2
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/utils/labgrid.py +7 -1
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/utils/lava.py +15 -4
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/PKG-INFO +2 -3
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server_daemon/server_daemon.py +29 -18
- {fc-server-0.7.0 → fc-server-0.7.2}/setup.cfg +1 -2
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_client/test_client.py +27 -5
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/conftest.py +4 -2
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_coordinator.py +7 -4
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_plugin_labgrid.py +2 -1
- fc-server-0.7.0/fc_client_docker/fc_client_docker +0 -3
- fc-server-0.7.0/fc_common/VERSION +0 -1
- fc-server-0.7.0/fc_server/core/api_svr.py +0 -242
- {fc-server-0.7.0 → fc-server-0.7.2}/.QualityGate +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/.github/workflows/main.yml +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/.gitignore +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/.pylintrc +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/.readthedocs.yaml +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/LICENSE +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/README.rst +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/SCR-fc.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/ci/black.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/ci/docker.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/ci/package.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/ci/publish_docker.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/ci/pylint.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/ci/pytest.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/ci/setup.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/Makefile +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/conf.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/configuration.rst +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/css/css-style.css +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/architecture.svg +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/era.svg +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/fc.png +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/flowchart.svg +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/principle.svg +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/index.rst +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/make.bat +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/overview.rst +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/quickstart.rst +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/support.rst +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/doc/usage.rst +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_client/Dockerfile +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_client/fc-client +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/Dockerfile +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/docker-compose.yaml +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/entrypoint.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/sample.env +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_server/Dockerfile +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_server/entrypoint.sh +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_client/sample_client.env +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_client_docker/__init__.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/__init__.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/config.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/logger.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_guarder/guarder.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/__init__.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/config/sample_cfg.yaml +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/config/sample_lavacli.yaml +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/config/sample_resource.yaml +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/decorators.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/plugin.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/management/__init__.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/management/cmd_online_lava_devices.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/management/common.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/__init__.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/utils/__init__.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/server.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/SOURCES.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/dependency_links.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/entry_points.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/requires.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/top_level.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/fc_server_daemon/__init__.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/requirements/ci-requirements.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/requirements/client-requirements.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/requirements/doc-requirements.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/requirements/server-requirements.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/requirements.txt +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/setup.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/conftest.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_guarder/conftest.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_guarder/test_guarder.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/config/cfg.yaml +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_config.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_misc.py +0 -0
- {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_plugin_lava.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fc-server
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Home-page: https://fc.readthedocs.io/
|
|
5
5
|
Author: Larry Shen
|
|
6
6
|
Author-email: larry.shen@nxp.com
|
|
@@ -11,11 +11,10 @@ Classifier: Intended Audience :: Developers
|
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: POSIX :: Linux
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.8
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
16
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Requires-Python: >=3.
|
|
17
|
+
Requires-Python: >=3.8
|
|
19
18
|
License-File: LICENSE
|
|
20
19
|
Requires-Dist: aiohttp>=3.7.4.post0
|
|
21
20
|
Requires-Dist: async-lru>=1.0.3
|
|
@@ -11,20 +11,24 @@ import asyncio
|
|
|
11
11
|
import json
|
|
12
12
|
import os
|
|
13
13
|
import pathlib
|
|
14
|
+
import re
|
|
14
15
|
import signal
|
|
15
16
|
import socket
|
|
16
17
|
import subprocess
|
|
17
18
|
import sys
|
|
19
|
+
import textwrap
|
|
18
20
|
import time
|
|
19
21
|
import urllib.parse
|
|
20
22
|
from getpass import getuser
|
|
21
23
|
from socket import gethostname
|
|
24
|
+
from urllib.parse import urlparse, urlunparse
|
|
22
25
|
|
|
23
26
|
import aiohttp
|
|
24
27
|
import prettytable
|
|
25
28
|
import psutil
|
|
26
29
|
import requests
|
|
27
30
|
import yaml
|
|
31
|
+
from tenacity import retry, stop_after_attempt, wait_fixed
|
|
28
32
|
|
|
29
33
|
from fc_common import which
|
|
30
34
|
from fc_common.config import Config
|
|
@@ -34,6 +38,38 @@ from fc_common.version import get_runtime_version
|
|
|
34
38
|
class Client:
|
|
35
39
|
daemon_pid_file = "/tmp/fc/fc_client_daemon.pid"
|
|
36
40
|
etcd_url = None
|
|
41
|
+
version = get_runtime_version("fc-client")
|
|
42
|
+
me = "/".join(
|
|
43
|
+
(
|
|
44
|
+
os.environ.get("LG_HOSTNAME", gethostname()),
|
|
45
|
+
os.environ.get("LG_USERNAME", getuser()),
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def action(caller, *caller_args):
|
|
51
|
+
for caller_arg in caller_args:
|
|
52
|
+
if "format" in caller_arg and caller_arg.format == "json":
|
|
53
|
+
os.environ.setdefault("FC_DISABLE_INFO", "true")
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
Client.conditional_print(
|
|
57
|
+
f"FC-CLIENT VERSION: {Client.version}, "
|
|
58
|
+
"HOMEPAGE: https://fc.readthedocs.org/"
|
|
59
|
+
)
|
|
60
|
+
Client.mode_check()
|
|
61
|
+
|
|
62
|
+
caller(*caller_args)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def conditional_print(msg):
|
|
66
|
+
if not os.environ.get("FC_DISABLE_INFO", None):
|
|
67
|
+
print(msg)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def verbose_print(msg, args):
|
|
71
|
+
if args.verbose >= 10:
|
|
72
|
+
print(msg)
|
|
37
73
|
|
|
38
74
|
@staticmethod
|
|
39
75
|
def mode_check():
|
|
@@ -41,42 +77,90 @@ class Client:
|
|
|
41
77
|
lg_crossbar = os.environ.get("LG_CROSSBAR", None)
|
|
42
78
|
|
|
43
79
|
if fc_server and lg_crossbar:
|
|
44
|
-
|
|
80
|
+
Client.conditional_print("MODE: single")
|
|
45
81
|
Client.mode = "single"
|
|
46
82
|
elif fc_server:
|
|
47
|
-
|
|
83
|
+
Client.conditional_print(
|
|
84
|
+
"MODE: cluster (LG_CROSSBAR not set, fallback to cluster mode)"
|
|
85
|
+
)
|
|
48
86
|
Client.mode = "cluster"
|
|
49
87
|
elif lg_crossbar:
|
|
50
|
-
|
|
88
|
+
Client.conditional_print(
|
|
89
|
+
"MODE: cluster (FC_SERVER not set, fallback to cluster mode)"
|
|
90
|
+
)
|
|
51
91
|
Client.mode = "cluster"
|
|
52
92
|
else:
|
|
53
|
-
|
|
93
|
+
Client.conditional_print("MODE: cluster")
|
|
54
94
|
Client.mode = "cluster"
|
|
55
95
|
|
|
56
96
|
@staticmethod
|
|
57
97
|
def labgrid_call(args, extras):
|
|
98
|
+
verbose = ["-v"] * args.verbose
|
|
99
|
+
|
|
58
100
|
metadata = Client.fetch_metadata(args.resource)
|
|
59
101
|
os.environ["LG_CROSSBAR"] = metadata["lg"]
|
|
60
102
|
|
|
61
103
|
# FIXME: temp fix for bootstrap command # pylint: disable=fixme
|
|
62
|
-
if extras
|
|
104
|
+
if extras and extras[0] in ["ssh", "scp"]:
|
|
63
105
|
cmd = " ".join(extras)
|
|
64
106
|
try:
|
|
65
107
|
import fc_plugins # pylint: disable=import-outside-toplevel
|
|
66
108
|
|
|
67
|
-
cmd = fc_plugins.get_rule(metadata["lg"],
|
|
109
|
+
cmd = fc_plugins.get_rule(metadata["lg"], extras[0])
|
|
110
|
+
if len(extras) > 1:
|
|
111
|
+
cmd += " " + " ".join(extras[1:])
|
|
68
112
|
except ImportError:
|
|
69
113
|
pass
|
|
70
114
|
|
|
71
115
|
os.environ["LG_PLACE"] = args.resource
|
|
72
|
-
|
|
73
|
-
"labgrid-client",
|
|
74
|
-
["labgrid-client", "-p", args.resource] + cmd.split(" "),
|
|
116
|
+
full_cmd = (
|
|
117
|
+
["labgrid-client"] + verbose + ["-p", args.resource] + cmd.split(" ")
|
|
75
118
|
)
|
|
119
|
+
Client.verbose_print(full_cmd, args)
|
|
120
|
+
os.execvp("labgrid-client", full_cmd)
|
|
121
|
+
else:
|
|
122
|
+
full_cmd = ["labgrid-client"] + verbose + ["-p", args.resource] + extras
|
|
123
|
+
Client.verbose_print(full_cmd, args)
|
|
124
|
+
os.execvp("labgrid-client", full_cmd)
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def communicate_with_server_sync(url, aim=None):
|
|
128
|
+
if aim:
|
|
129
|
+
headers = {
|
|
130
|
+
"X-FC-Client-ID": Client.me,
|
|
131
|
+
"X-FC-Client-Version": Client.version,
|
|
132
|
+
"X-FC-Aim": aim,
|
|
133
|
+
}
|
|
76
134
|
else:
|
|
77
|
-
|
|
78
|
-
|
|
135
|
+
headers = {"X-FC-Client-ID": Client.me}
|
|
136
|
+
|
|
137
|
+
output = requests.get(url, headers=headers)
|
|
138
|
+
try:
|
|
139
|
+
output_data = json.loads(output.text)
|
|
140
|
+
except json.JSONDecodeError as exc:
|
|
141
|
+
print(exc)
|
|
142
|
+
sys.exit(
|
|
143
|
+
"Fatal: unable to parse resource data,"
|
|
144
|
+
f"status_code={output.status_code},"
|
|
145
|
+
f"text={output.content}"
|
|
79
146
|
)
|
|
147
|
+
return output_data
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
async def communicate_with_server_async(url, timeout=5):
|
|
151
|
+
timeout = aiohttp.ClientTimeout(total=timeout)
|
|
152
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
153
|
+
try:
|
|
154
|
+
async with session.get(url) as response:
|
|
155
|
+
output = await response.text()
|
|
156
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
157
|
+
parsed = urlparse(url)
|
|
158
|
+
fc_server = urlunparse((parsed.scheme, parsed.netloc, "", "", "", ""))
|
|
159
|
+
Client.conditional_print(
|
|
160
|
+
f"Warning: {fc_server} skipped due to {type(exc)}"
|
|
161
|
+
)
|
|
162
|
+
return ""
|
|
163
|
+
return output
|
|
80
164
|
|
|
81
165
|
@staticmethod
|
|
82
166
|
def communicate_with_daemon(msg_type, para=None):
|
|
@@ -172,16 +256,7 @@ class Client:
|
|
|
172
256
|
def cluster_info(args):
|
|
173
257
|
async def get_server_info(fc_server):
|
|
174
258
|
url = f"{fc_server}/server_info"
|
|
175
|
-
|
|
176
|
-
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
177
|
-
try:
|
|
178
|
-
async with session.get(url) as response:
|
|
179
|
-
output = await response.text()
|
|
180
|
-
except Exception as exc: # pylint: disable=broad-except
|
|
181
|
-
print(f"Warning: {fc_server} skipped due to {type(exc)}")
|
|
182
|
-
return ""
|
|
183
|
-
|
|
184
|
-
return output
|
|
259
|
+
return await Client.communicate_with_server_async(url)
|
|
185
260
|
|
|
186
261
|
if args.resource:
|
|
187
262
|
metadata = Client.fetch_metadata(args.resource)
|
|
@@ -212,16 +287,7 @@ class Client:
|
|
|
212
287
|
def all_locks(_):
|
|
213
288
|
async def get_all_locks(fc_server):
|
|
214
289
|
url = f"{fc_server}/all_locks"
|
|
215
|
-
|
|
216
|
-
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
217
|
-
try:
|
|
218
|
-
async with session.get(url) as response:
|
|
219
|
-
output = await response.text()
|
|
220
|
-
except Exception as exc: # pylint: disable=broad-except
|
|
221
|
-
print(f"Warning: {fc_server} skipped due to {type(exc)}")
|
|
222
|
-
return ""
|
|
223
|
-
|
|
224
|
-
return output
|
|
290
|
+
return await Client.communicate_with_server_async(url, 10)
|
|
225
291
|
|
|
226
292
|
metadata = Client.fetch_metadata("all")
|
|
227
293
|
|
|
@@ -244,7 +310,7 @@ class Client:
|
|
|
244
310
|
specified_device_type = args.device_type
|
|
245
311
|
specified_peripheral_info = args.peripheral_info
|
|
246
312
|
|
|
247
|
-
url = f"{fc_server}/
|
|
313
|
+
url = f"{fc_server}/resources"
|
|
248
314
|
|
|
249
315
|
if specified_resource:
|
|
250
316
|
url += f"/{specified_resource}"
|
|
@@ -261,14 +327,13 @@ class Client:
|
|
|
261
327
|
if query_string:
|
|
262
328
|
url += f"?{query_string}"
|
|
263
329
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return []
|
|
330
|
+
if args.verbose >= 2:
|
|
331
|
+
client_timeout = 15
|
|
332
|
+
else:
|
|
333
|
+
client_timeout = 5
|
|
334
|
+
output = await Client.communicate_with_server_async(url, client_timeout)
|
|
335
|
+
if output == "":
|
|
336
|
+
return []
|
|
272
337
|
|
|
273
338
|
try:
|
|
274
339
|
ret = json.loads(output)
|
|
@@ -285,34 +350,44 @@ class Client:
|
|
|
285
350
|
resources = sum(resource_data, [])
|
|
286
351
|
|
|
287
352
|
if args.verbose == 0:
|
|
288
|
-
field_names = ["Resource", "Farm", "Status"
|
|
353
|
+
field_names = ["Resource", "Farm", "Status"]
|
|
289
354
|
elif args.verbose == 1:
|
|
290
|
-
field_names = ["Resource", "Farm", "Status", "
|
|
355
|
+
field_names = ["Resource", "Farm", "Status", "Comment"]
|
|
356
|
+
elif args.verbose == 2:
|
|
357
|
+
field_names = ["Resource", "Farm", "Status", "Comment", "Label"]
|
|
291
358
|
else:
|
|
292
359
|
field_names = [
|
|
293
360
|
"Resource",
|
|
294
361
|
"Farm",
|
|
295
362
|
"Status",
|
|
296
|
-
"Note",
|
|
297
363
|
"Comment",
|
|
364
|
+
"Label",
|
|
298
365
|
"Info",
|
|
299
366
|
]
|
|
300
367
|
|
|
301
368
|
if args.format == "json":
|
|
302
|
-
|
|
303
|
-
for resource in resources:
|
|
304
|
-
json_data.append(
|
|
305
|
-
{prop.lower(): value for prop, value in zip(field_names, resource)}
|
|
306
|
-
)
|
|
307
|
-
print(json.dumps(json_data))
|
|
369
|
+
print(json.dumps(resources))
|
|
308
370
|
else:
|
|
309
371
|
table = prettytable.PrettyTable(align="l")
|
|
372
|
+
max_info_width = 60
|
|
310
373
|
|
|
311
374
|
for resource in resources:
|
|
312
|
-
|
|
375
|
+
if "comment" in resource:
|
|
376
|
+
resource["comment"] = textwrap.fill(resource["comment"], width=25)
|
|
377
|
+
for key in ("label", "info"):
|
|
378
|
+
if key in resource:
|
|
379
|
+
yaml_str = yaml.dump(resource[key], allow_unicode=True)
|
|
380
|
+
if key == "info" and any(
|
|
381
|
+
len(line) > max_info_width for line in yaml_str.splitlines()
|
|
382
|
+
):
|
|
383
|
+
yaml_str = "\n".join(
|
|
384
|
+
textwrap.fill(line, width=max_info_width)
|
|
385
|
+
for line in yaml_str.splitlines()
|
|
386
|
+
)
|
|
387
|
+
resource[key] = yaml_str
|
|
388
|
+
table.add_row([resource[field.lower()] for field in field_names])
|
|
313
389
|
|
|
314
390
|
table.field_names = field_names
|
|
315
|
-
table.max_width = 80
|
|
316
391
|
|
|
317
392
|
print(table.get_string(sortby="Resource"))
|
|
318
393
|
|
|
@@ -320,37 +395,55 @@ class Client:
|
|
|
320
395
|
@which(
|
|
321
396
|
"labgrid-client",
|
|
322
397
|
"Use 'pip3 install labgrid' or other customized way to install labgrid software please.",
|
|
323
|
-
)
|
|
398
|
+
) # pylint: disable=too-many-branches, too-many-statements
|
|
324
399
|
def lock(args):
|
|
400
|
+
def check_resource_ready():
|
|
401
|
+
print(f"Check if {resource} is free...")
|
|
402
|
+
url = f"{fc_server}/resources/{resource}"
|
|
403
|
+
output_data = Client.communicate_with_server_sync(url, "lock")
|
|
404
|
+
Client.verbose_print(output_data, args)
|
|
405
|
+
|
|
406
|
+
rc = output_data.get("rc") # pylint: disable=invalid-name
|
|
407
|
+
if rc != 0:
|
|
408
|
+
reason = output_data.get("msg", "Unknown error")
|
|
409
|
+
if rc == 2 and "labgrid" in reason:
|
|
410
|
+
print(reason)
|
|
411
|
+
else:
|
|
412
|
+
sys.exit(f"Fatal: {reason}")
|
|
413
|
+
|
|
414
|
+
@retry(wait=wait_fixed(2), stop=stop_after_attempt(5))
|
|
415
|
+
def check_labgrid_resource_ready(resource):
|
|
416
|
+
cmd = f"labgrid-client -p {resource} show | grep acquired:"
|
|
417
|
+
ret, place_info_text = subprocess.getstatusoutput(cmd)
|
|
418
|
+
if ret == 0:
|
|
419
|
+
acquired = place_info_text.split()[1]
|
|
420
|
+
if acquired not in ["fc/fc", "None"]:
|
|
421
|
+
sys.exit(
|
|
422
|
+
f"error: place {resource} is already acquired by {acquired}"
|
|
423
|
+
)
|
|
424
|
+
if acquired == "None":
|
|
425
|
+
raise Exception
|
|
426
|
+
|
|
325
427
|
resource = args.resource
|
|
326
428
|
metadata = Client.fetch_metadata(resource)
|
|
327
429
|
|
|
328
430
|
fc_server = metadata.get("fc", None)
|
|
329
431
|
if not fc_server:
|
|
330
432
|
sys.exit("Fatal: invalid resource")
|
|
331
|
-
else:
|
|
332
|
-
url = f"{fc_server}/resource/{resource}"
|
|
333
|
-
output = requests.get(url)
|
|
334
|
-
output_data = json.loads(output.text)
|
|
335
|
-
|
|
336
|
-
if len(output_data) == 0:
|
|
337
|
-
sys.exit("Fatal: invalid resource")
|
|
338
433
|
|
|
339
|
-
|
|
340
|
-
|
|
434
|
+
if resource:
|
|
435
|
+
os.environ["LG_CROSSBAR"] = metadata["lg"]
|
|
341
436
|
|
|
342
|
-
|
|
437
|
+
check_resource_ready()
|
|
438
|
+
try:
|
|
439
|
+
check_labgrid_resource_ready(resource)
|
|
440
|
+
except Exception: # pylint: disable=broad-except
|
|
441
|
+
sys.exit(
|
|
442
|
+
f"error: place {resource} is not ready, "
|
|
443
|
+
"contact admin to recover this device please "
|
|
444
|
+
"if this persists after a few minutes"
|
|
445
|
+
)
|
|
343
446
|
|
|
344
|
-
if resource:
|
|
345
|
-
print(f"Check if {resource} is free")
|
|
346
|
-
cmd = f"labgrid-client -p {resource} show | grep acquired:"
|
|
347
|
-
ret, place_info_text = subprocess.getstatusoutput(cmd)
|
|
348
|
-
if ret == 0:
|
|
349
|
-
acquired = place_info_text.split()[1]
|
|
350
|
-
if acquired not in ["fc/fc", "None"]:
|
|
351
|
-
sys.exit(
|
|
352
|
-
f"error: place {resource} is already acquired by {acquired}"
|
|
353
|
-
)
|
|
354
447
|
print(f"Try to acquire resource {resource}...")
|
|
355
448
|
with subprocess.Popen(
|
|
356
449
|
["labgrid-client", "reserve", "--wait", f"name={resource}"],
|
|
@@ -388,13 +481,6 @@ class Client:
|
|
|
388
481
|
metadata = Client.fetch_metadata(resource)
|
|
389
482
|
os.environ["LG_CROSSBAR"] = metadata["lg"]
|
|
390
483
|
|
|
391
|
-
me = "/".join( # pylint: disable=invalid-name
|
|
392
|
-
(
|
|
393
|
-
os.environ.get("LG_HOSTNAME", gethostname()),
|
|
394
|
-
os.environ.get("LG_USERNAME", getuser()),
|
|
395
|
-
)
|
|
396
|
-
)
|
|
397
|
-
|
|
398
484
|
if resource: # pylint: disable=too-many-nested-blocks
|
|
399
485
|
cmd = f"labgrid-client -p {resource} show"
|
|
400
486
|
ret, place_info_text = subprocess.getstatusoutput(cmd)
|
|
@@ -413,7 +499,7 @@ class Client:
|
|
|
413
499
|
for k, v in reservations.items(): # pylint: disable=invalid-name
|
|
414
500
|
if k == f"Reservation '{token}'":
|
|
415
501
|
owner = v["owner"]
|
|
416
|
-
if owner == me:
|
|
502
|
+
if owner == Client.me:
|
|
417
503
|
print("Start to free the place.")
|
|
418
504
|
signal.signal(signal.SIGINT, lambda _: "")
|
|
419
505
|
subprocess.call(
|
|
@@ -431,23 +517,37 @@ class Client:
|
|
|
431
517
|
sys.exit("No resource specified.")
|
|
432
518
|
|
|
433
519
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
520
|
+
class VerbosityAction(argparse.Action):
|
|
521
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
522
|
+
current = getattr(namespace, self.dest, 0)
|
|
523
|
+
if values is None:
|
|
524
|
+
setattr(namespace, self.dest, current + 1)
|
|
525
|
+
elif values.isdigit():
|
|
526
|
+
setattr(namespace, self.dest, current + int(values))
|
|
527
|
+
elif re.fullmatch(r"v+", values):
|
|
528
|
+
setattr(namespace, self.dest, current + len(values) + 1)
|
|
529
|
+
else:
|
|
530
|
+
parser.error(f"Invalid verbosity argument: {values}")
|
|
439
531
|
|
|
440
|
-
Client.mode_check()
|
|
441
532
|
|
|
533
|
+
def main():
|
|
442
534
|
parser = argparse.ArgumentParser()
|
|
443
|
-
parser.set_defaults(func=lambda
|
|
535
|
+
parser.set_defaults(func=lambda _: parser.print_help())
|
|
444
536
|
parser.add_argument(
|
|
445
537
|
"-r", "--resource", "-p", "--place", type=str, help="resource name"
|
|
446
538
|
)
|
|
447
539
|
parser.add_argument("-f", "--farm-type", type=str, help="farm type")
|
|
448
540
|
parser.add_argument("-d", "--device-type", type=str, help="device type")
|
|
449
541
|
parser.add_argument("-i", "--peripheral-info", type=str, help="peripheral info")
|
|
450
|
-
parser.add_argument(
|
|
542
|
+
parser.add_argument(
|
|
543
|
+
"-v",
|
|
544
|
+
"--verbose",
|
|
545
|
+
action=VerbosityAction,
|
|
546
|
+
nargs="?",
|
|
547
|
+
default=0,
|
|
548
|
+
help="increase verbosity level "
|
|
549
|
+
"(can be used multiple times or with a number: -v, -vv, -v1, -v2)",
|
|
550
|
+
)
|
|
451
551
|
|
|
452
552
|
args, extras = parser.parse_known_args()
|
|
453
553
|
|
|
@@ -514,12 +614,12 @@ def main():
|
|
|
514
614
|
|
|
515
615
|
if len(extras) > 0 and extras[0] in ["init", "i"]:
|
|
516
616
|
args, extras = parser.parse_known_args(extras, namespace=args)
|
|
517
|
-
args.func
|
|
617
|
+
Client.action(args.func, extras)
|
|
518
618
|
else:
|
|
519
619
|
args = parser.parse_args(extras, namespace=args)
|
|
520
|
-
args.func
|
|
620
|
+
Client.action(args.func, args)
|
|
521
621
|
else:
|
|
522
|
-
Client.labgrid_call
|
|
622
|
+
Client.action(Client.labgrid_call, args, extras)
|
|
523
623
|
|
|
524
624
|
|
|
525
625
|
if __name__ == "__main__":
|