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.
Files changed (101) hide show
  1. {fc-server-0.7.0 → fc-server-0.7.2}/PKG-INFO +2 -3
  2. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_server/docker-compose.yaml +1 -0
  3. {fc-server-0.7.0 → fc-server-0.7.2}/fc_client/client.py +191 -91
  4. {fc-server-0.7.0 → fc-server-0.7.2}/fc_client_daemon/client_daemon.py +128 -45
  5. fc-server-0.7.2/fc_client_docker/fc_client_docker +3 -0
  6. fc-server-0.7.2/fc_common/VERSION +1 -0
  7. {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/etcd.py +5 -1
  8. {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/version.py +5 -6
  9. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/__init__.py +6 -3
  10. fc-server-0.7.2/fc_server/core/api_svr.py +494 -0
  11. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/config.py +18 -0
  12. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/coordinator.py +11 -0
  13. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/labgrid.py +73 -5
  14. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/lava.py +8 -2
  15. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/utils/labgrid.py +7 -1
  16. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/utils/lava.py +15 -4
  17. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/PKG-INFO +2 -3
  18. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server_daemon/server_daemon.py +29 -18
  19. {fc-server-0.7.0 → fc-server-0.7.2}/setup.cfg +1 -2
  20. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_client/test_client.py +27 -5
  21. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/conftest.py +4 -2
  22. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_coordinator.py +7 -4
  23. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_plugin_labgrid.py +2 -1
  24. fc-server-0.7.0/fc_client_docker/fc_client_docker +0 -3
  25. fc-server-0.7.0/fc_common/VERSION +0 -1
  26. fc-server-0.7.0/fc_server/core/api_svr.py +0 -242
  27. {fc-server-0.7.0 → fc-server-0.7.2}/.QualityGate +0 -0
  28. {fc-server-0.7.0 → fc-server-0.7.2}/.github/workflows/main.yml +0 -0
  29. {fc-server-0.7.0 → fc-server-0.7.2}/.gitignore +0 -0
  30. {fc-server-0.7.0 → fc-server-0.7.2}/.pylintrc +0 -0
  31. {fc-server-0.7.0 → fc-server-0.7.2}/.readthedocs.yaml +0 -0
  32. {fc-server-0.7.0 → fc-server-0.7.2}/LICENSE +0 -0
  33. {fc-server-0.7.0 → fc-server-0.7.2}/README.rst +0 -0
  34. {fc-server-0.7.0 → fc-server-0.7.2}/SCR-fc.txt +0 -0
  35. {fc-server-0.7.0 → fc-server-0.7.2}/ci/black.sh +0 -0
  36. {fc-server-0.7.0 → fc-server-0.7.2}/ci/docker.sh +0 -0
  37. {fc-server-0.7.0 → fc-server-0.7.2}/ci/package.sh +0 -0
  38. {fc-server-0.7.0 → fc-server-0.7.2}/ci/publish_docker.sh +0 -0
  39. {fc-server-0.7.0 → fc-server-0.7.2}/ci/pylint.sh +0 -0
  40. {fc-server-0.7.0 → fc-server-0.7.2}/ci/pytest.sh +0 -0
  41. {fc-server-0.7.0 → fc-server-0.7.2}/ci/setup.sh +0 -0
  42. {fc-server-0.7.0 → fc-server-0.7.2}/doc/Makefile +0 -0
  43. {fc-server-0.7.0 → fc-server-0.7.2}/doc/conf.py +0 -0
  44. {fc-server-0.7.0 → fc-server-0.7.2}/doc/configuration.rst +0 -0
  45. {fc-server-0.7.0 → fc-server-0.7.2}/doc/css/css-style.css +0 -0
  46. {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/architecture.svg +0 -0
  47. {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/era.svg +0 -0
  48. {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/fc.png +0 -0
  49. {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/flowchart.svg +0 -0
  50. {fc-server-0.7.0 → fc-server-0.7.2}/doc/images/principle.svg +0 -0
  51. {fc-server-0.7.0 → fc-server-0.7.2}/doc/index.rst +0 -0
  52. {fc-server-0.7.0 → fc-server-0.7.2}/doc/make.bat +0 -0
  53. {fc-server-0.7.0 → fc-server-0.7.2}/doc/overview.rst +0 -0
  54. {fc-server-0.7.0 → fc-server-0.7.2}/doc/quickstart.rst +0 -0
  55. {fc-server-0.7.0 → fc-server-0.7.2}/doc/support.rst +0 -0
  56. {fc-server-0.7.0 → fc-server-0.7.2}/doc/usage.rst +0 -0
  57. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_client/Dockerfile +0 -0
  58. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_client/fc-client +0 -0
  59. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/Dockerfile +0 -0
  60. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/docker-compose.yaml +0 -0
  61. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/entrypoint.sh +0 -0
  62. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_guarder/sample.env +0 -0
  63. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_server/Dockerfile +0 -0
  64. {fc-server-0.7.0 → fc-server-0.7.2}/docker/fc_server/entrypoint.sh +0 -0
  65. {fc-server-0.7.0 → fc-server-0.7.2}/fc_client/sample_client.env +0 -0
  66. {fc-server-0.7.0 → fc-server-0.7.2}/fc_client_docker/__init__.py +0 -0
  67. {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/__init__.py +0 -0
  68. {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/config.py +0 -0
  69. {fc-server-0.7.0 → fc-server-0.7.2}/fc_common/logger.py +0 -0
  70. {fc-server-0.7.0 → fc-server-0.7.2}/fc_guarder/guarder.py +0 -0
  71. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/__init__.py +0 -0
  72. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/config/sample_cfg.yaml +0 -0
  73. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/config/sample_lavacli.yaml +0 -0
  74. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/config/sample_resource.yaml +0 -0
  75. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/decorators.py +0 -0
  76. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/core/plugin.py +0 -0
  77. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/management/__init__.py +0 -0
  78. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/management/cmd_online_lava_devices.py +0 -0
  79. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/management/common.py +0 -0
  80. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/__init__.py +0 -0
  81. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/plugins/utils/__init__.py +0 -0
  82. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server/server.py +0 -0
  83. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/SOURCES.txt +0 -0
  84. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/dependency_links.txt +0 -0
  85. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/entry_points.txt +0 -0
  86. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/requires.txt +0 -0
  87. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server.egg-info/top_level.txt +0 -0
  88. {fc-server-0.7.0 → fc-server-0.7.2}/fc_server_daemon/__init__.py +0 -0
  89. {fc-server-0.7.0 → fc-server-0.7.2}/requirements/ci-requirements.txt +0 -0
  90. {fc-server-0.7.0 → fc-server-0.7.2}/requirements/client-requirements.txt +0 -0
  91. {fc-server-0.7.0 → fc-server-0.7.2}/requirements/doc-requirements.txt +0 -0
  92. {fc-server-0.7.0 → fc-server-0.7.2}/requirements/server-requirements.txt +0 -0
  93. {fc-server-0.7.0 → fc-server-0.7.2}/requirements.txt +0 -0
  94. {fc-server-0.7.0 → fc-server-0.7.2}/setup.py +0 -0
  95. {fc-server-0.7.0 → fc-server-0.7.2}/tests/conftest.py +0 -0
  96. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_guarder/conftest.py +0 -0
  97. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_guarder/test_guarder.py +0 -0
  98. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/config/cfg.yaml +0 -0
  99. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_config.py +0 -0
  100. {fc-server-0.7.0 → fc-server-0.7.2}/tests/fc_server/test_misc.py +0 -0
  101. {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.0
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.7
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
@@ -10,6 +10,7 @@ services:
10
10
  tty: true
11
11
  ports:
12
12
  - "8600:8600"
13
+ restart: unless-stopped
13
14
  volumes:
14
15
  - /tmp:/tmp
15
16
  - $PWD/../../fc_server/config/lavacli.yaml:/root/.config/lavacli.yaml
@@ -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
- print("MODE: single")
80
+ Client.conditional_print("MODE: single")
45
81
  Client.mode = "single"
46
82
  elif fc_server:
47
- print("MODE: cluster (LG_CROSSBAR not set, fallback to cluster mode)")
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
- print("MODE: cluster (FC_SERVER not set, fallback to cluster mode)")
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
- print("MODE: cluster")
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 == ["ssh"]:
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"], cmd)
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
- os.execvp(
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
- os.execvp(
78
- "labgrid-client", ["labgrid-client", "-p", args.resource] + extras
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
- timeout = aiohttp.ClientTimeout(total=5)
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
- timeout = aiohttp.ClientTimeout(total=5)
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}/resource"
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
- timeout = aiohttp.ClientTimeout(total=5)
265
- async with aiohttp.ClientSession(timeout=timeout) as session:
266
- try:
267
- async with session.get(url) as response:
268
- output = await response.text()
269
- except Exception as exc: # pylint: disable=broad-except
270
- print(f"Warning: {fc_server} skipped due to {type(exc)}")
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", "Note"]
353
+ field_names = ["Resource", "Farm", "Status"]
289
354
  elif args.verbose == 1:
290
- field_names = ["Resource", "Farm", "Status", "Note", "Comment"]
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
- json_data = []
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
- table.add_row(resource)
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
- if output_data[0][3] != "":
340
- sys.exit("Fatal: non-debuggable resource")
434
+ if resource:
435
+ os.environ["LG_CROSSBAR"] = metadata["lg"]
341
436
 
342
- os.environ["LG_CROSSBAR"] = metadata["lg"]
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
- def main():
435
- print(
436
- f"FC-CLIENT VERSION: {get_runtime_version('fc-client')}, "
437
- "HOMEPAGE: https://fc.readthedocs.org/"
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 args: parser.print_help())
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("-v", "--verbose", action="count", default=0)
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(extras)
617
+ Client.action(args.func, extras)
518
618
  else:
519
619
  args = parser.parse_args(extras, namespace=args)
520
- args.func(args)
620
+ Client.action(args.func, args)
521
621
  else:
522
- Client.labgrid_call(args, extras)
622
+ Client.action(Client.labgrid_call, args, extras)
523
623
 
524
624
 
525
625
  if __name__ == "__main__":