ls-algorithm-plugin-sdk 0.3.0__py3-none-any.whl → 0.3.1__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.
- algorithm_plugin_sdk/cli_impl/configure.py +55 -42
- algorithm_plugin_sdk/deployment.py +2 -0
- {ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/METADATA +6 -17
- {ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/RECORD +7 -7
- {ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/WHEEL +0 -0
- {ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/entry_points.txt +0 -0
- {ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import ipaddress
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
7
|
+
import socket
|
|
6
8
|
import subprocess
|
|
7
9
|
import sys
|
|
8
10
|
from pathlib import Path
|
|
11
|
+
from urllib.parse import urlparse
|
|
9
12
|
|
|
10
13
|
from ..deployment import (
|
|
11
14
|
DEFAULT_CONFIG_NAME,
|
|
@@ -17,6 +20,11 @@ from .parsing import environment, gpu_ids
|
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
SYSTEMD_UNIT_DIR = Path("/etc/systemd/system")
|
|
23
|
+
INPUT_ROOT = Path("/mnt/ldp_uploads/ldp-uploads")
|
|
24
|
+
PRIVATE_IPV4_NETWORKS = tuple(
|
|
25
|
+
ipaddress.ip_network(network)
|
|
26
|
+
for network in ("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")
|
|
27
|
+
)
|
|
20
28
|
|
|
21
29
|
SERVICE_NAME_PATTERN = re.compile(
|
|
22
30
|
r"^[A-Za-z0-9][A-Za-z0-9_.@-]*$"
|
|
@@ -65,18 +73,6 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:
|
|
|
65
73
|
"--api-key",
|
|
66
74
|
default=os.getenv("LDP_INTERNAL_API_KEY"),
|
|
67
75
|
)
|
|
68
|
-
parser.add_argument(
|
|
69
|
-
"--public-url",
|
|
70
|
-
default=os.getenv("LDP_PLUGIN_PUBLIC_URL"),
|
|
71
|
-
)
|
|
72
|
-
parser.add_argument(
|
|
73
|
-
"--input-root",
|
|
74
|
-
default=os.getenv("LDP_JOB_INPUT_ROOT"),
|
|
75
|
-
)
|
|
76
|
-
parser.add_argument(
|
|
77
|
-
"--workspace-root",
|
|
78
|
-
default=os.getenv("LDP_JOB_WORKSPACE_ROOT"),
|
|
79
|
-
)
|
|
80
76
|
parser.add_argument(
|
|
81
77
|
"--instance-key",
|
|
82
78
|
default=os.getenv("LDP_PLUGIN_INSTANCE_KEY"),
|
|
@@ -85,14 +81,6 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:
|
|
|
85
81
|
"--cluster",
|
|
86
82
|
default=os.getenv("LDP_CLUSTER"),
|
|
87
83
|
)
|
|
88
|
-
parser.add_argument(
|
|
89
|
-
"--namespace",
|
|
90
|
-
default=os.getenv("POD_NAMESPACE"),
|
|
91
|
-
)
|
|
92
|
-
parser.add_argument(
|
|
93
|
-
"--node-name",
|
|
94
|
-
default=os.getenv("NODE_NAME"),
|
|
95
|
-
)
|
|
96
84
|
parser.add_argument(
|
|
97
85
|
"--local",
|
|
98
86
|
action="store_true",
|
|
@@ -105,10 +93,6 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:
|
|
|
105
93
|
default=[],
|
|
106
94
|
metavar="NAME=VALUE",
|
|
107
95
|
)
|
|
108
|
-
parser.add_argument(
|
|
109
|
-
"--service-name",
|
|
110
|
-
help="systemd unit name without .service; defaults to repository name",
|
|
111
|
-
)
|
|
112
96
|
parser.add_argument(
|
|
113
97
|
"--force",
|
|
114
98
|
action="store_true",
|
|
@@ -119,33 +103,35 @@ def configure_parser(parser: argparse.ArgumentParser) -> None:
|
|
|
119
103
|
|
|
120
104
|
def resolve_service_name(
|
|
121
105
|
repository: Path,
|
|
122
|
-
requested: str | None,
|
|
123
106
|
) -> str:
|
|
124
|
-
name =
|
|
107
|
+
name = repository.name
|
|
125
108
|
if name.endswith(".service"):
|
|
126
109
|
raise ValueError(
|
|
127
|
-
"
|
|
110
|
+
"repository name must not include the .service suffix"
|
|
128
111
|
)
|
|
129
112
|
if not SERVICE_NAME_PATTERN.fullmatch(name):
|
|
130
113
|
raise ValueError(
|
|
131
|
-
"
|
|
114
|
+
"repository name may contain only letters, numbers, "
|
|
132
115
|
"underscore, dot, @, and hyphen"
|
|
133
116
|
)
|
|
134
117
|
return name
|
|
135
118
|
|
|
136
119
|
|
|
137
|
-
def
|
|
120
|
+
def _escape_systemd_path(value: Path) -> str:
|
|
138
121
|
text = str(value)
|
|
139
122
|
if "\n" in text or "\r" in text or "\0" in text:
|
|
140
123
|
raise ValueError(
|
|
141
124
|
f"systemd path contains control characters: {value}"
|
|
142
125
|
)
|
|
143
|
-
|
|
126
|
+
return (
|
|
144
127
|
text.replace("\\", "\\\\")
|
|
145
128
|
.replace('"', '\\"')
|
|
146
129
|
.replace("%", "%%")
|
|
147
130
|
)
|
|
148
|
-
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _quote_systemd_path(value: Path) -> str:
|
|
134
|
+
return f'"{_escape_systemd_path(value)}"'
|
|
149
135
|
|
|
150
136
|
|
|
151
137
|
def render_systemd_unit(
|
|
@@ -167,7 +153,7 @@ def render_systemd_unit(
|
|
|
167
153
|
"[Service]",
|
|
168
154
|
"Type=simple",
|
|
169
155
|
"User=root",
|
|
170
|
-
f"WorkingDirectory={
|
|
156
|
+
f"WorkingDirectory={_escape_systemd_path(root)}",
|
|
171
157
|
(
|
|
172
158
|
f"ExecStart={_quote_systemd_path(launcher)} "
|
|
173
159
|
f"serve --config {_quote_systemd_path(config)}"
|
|
@@ -317,6 +303,35 @@ def resolve_config_path(
|
|
|
317
303
|
return path.resolve()
|
|
318
304
|
|
|
319
305
|
|
|
306
|
+
def resolve_private_ipv4(compute_url: str) -> str:
|
|
307
|
+
parsed = urlparse(compute_url)
|
|
308
|
+
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
|
|
309
|
+
raise ValueError("compute URL must be an http or https URL")
|
|
310
|
+
try:
|
|
311
|
+
port = parsed.port or (443 if parsed.scheme == "https" else 80)
|
|
312
|
+
except ValueError as exc:
|
|
313
|
+
raise ValueError("compute URL has an invalid port") from exc
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe:
|
|
317
|
+
probe.connect((parsed.hostname, port))
|
|
318
|
+
value = probe.getsockname()[0]
|
|
319
|
+
except OSError as exc:
|
|
320
|
+
raise RuntimeError(
|
|
321
|
+
f"cannot resolve the local IP used to reach {parsed.hostname}: {exc}"
|
|
322
|
+
) from exc
|
|
323
|
+
|
|
324
|
+
address = ipaddress.ip_address(value)
|
|
325
|
+
if not isinstance(address, ipaddress.IPv4Address) or not any(
|
|
326
|
+
address in network for network in PRIVATE_IPV4_NETWORKS
|
|
327
|
+
):
|
|
328
|
+
raise RuntimeError(
|
|
329
|
+
f"the local IP used to reach {parsed.hostname} is not a private IPv4: "
|
|
330
|
+
f"{address}"
|
|
331
|
+
)
|
|
332
|
+
return str(address)
|
|
333
|
+
|
|
334
|
+
|
|
320
335
|
def registration_from_args(
|
|
321
336
|
args: argparse.Namespace,
|
|
322
337
|
repository: Path,
|
|
@@ -327,12 +342,9 @@ def registration_from_args(
|
|
|
327
342
|
supplied = {
|
|
328
343
|
"computeUrl": args.compute_url,
|
|
329
344
|
"apiKey": args.api_key,
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
"workspaceRoot": args.workspace_root,
|
|
345
|
+
"inputRoot": str(INPUT_ROOT),
|
|
346
|
+
"workspaceRoot": str(repository.resolve()),
|
|
333
347
|
"cluster": args.cluster,
|
|
334
|
-
"namespace": args.namespace,
|
|
335
|
-
"nodeName": args.node_name,
|
|
336
348
|
}
|
|
337
349
|
missing = [
|
|
338
350
|
name
|
|
@@ -350,6 +362,9 @@ def registration_from_args(
|
|
|
350
362
|
name: str(value)
|
|
351
363
|
for name, value in supplied.items()
|
|
352
364
|
}
|
|
365
|
+
private_ipv4 = resolve_private_ipv4(registration["computeUrl"])
|
|
366
|
+
registration["publicUrl"] = f"http://{private_ipv4}:{args.port}"
|
|
367
|
+
registration["nodeName"] = private_ipv4
|
|
353
368
|
registration["instanceKey"] = stable_instance_key(
|
|
354
369
|
repository / ".algorithm-plugin" / "instance-key",
|
|
355
370
|
requested=args.instance_key,
|
|
@@ -383,10 +398,7 @@ def execute(args: argparse.Namespace) -> int:
|
|
|
383
398
|
require_root()
|
|
384
399
|
repository = validate_repository(args.repository)
|
|
385
400
|
validate_configure_runtime(repository)
|
|
386
|
-
service_name = resolve_service_name(
|
|
387
|
-
repository,
|
|
388
|
-
args.service_name,
|
|
389
|
-
)
|
|
401
|
+
service_name = resolve_service_name(repository)
|
|
390
402
|
config_path = resolve_config_path(
|
|
391
403
|
repository,
|
|
392
404
|
args.config_output,
|
|
@@ -418,4 +430,5 @@ def execute(args: argparse.Namespace) -> int:
|
|
|
418
430
|
print(" sudo systemctl daemon-reload")
|
|
419
431
|
print(f" sudo systemctl enable {service_name}.service")
|
|
420
432
|
print(f" sudo systemctl restart {service_name}.service")
|
|
421
|
-
|
|
433
|
+
print(f" sudo systemctl status {service_name}.service --no-pager")
|
|
434
|
+
return 0
|
{ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ls-algorithm-plugin-sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Protocol-independent runtime SDK for dataset algorithms
|
|
5
5
|
Author: Ling Robotics
|
|
6
6
|
License: Proprietary
|
|
@@ -15,10 +15,6 @@ Requires-Dist: build>=1.2; extra == "dev"
|
|
|
15
15
|
|
|
16
16
|
# Algorithm Plugin SDK
|
|
17
17
|
|
|
18
|
-
算法仓库只实现一个 `Algorithm` 类并声明 entry point。SDK 统一提供单次运行、
|
|
19
|
-
WebUI、HTTP service、release manifest、compute 注册和心跳。算法仓库不需要
|
|
20
|
-
Dockerfile 或 Web 框架代码。
|
|
21
|
-
|
|
22
18
|
## 算法侧最小接口
|
|
23
19
|
|
|
24
20
|
```python
|
|
@@ -30,8 +26,7 @@ class MyAlgorithm(Algorithm):
|
|
|
30
26
|
def execute(self, request, context): ...
|
|
31
27
|
```
|
|
32
28
|
|
|
33
|
-
算法包需要声明 `algorithm_plugin_sdk.algorithms` entry point
|
|
34
|
-
会被所有运行方式复用。
|
|
29
|
+
算法包需要声明 `algorithm_plugin_sdk.algorithms` entry point。
|
|
35
30
|
|
|
36
31
|
## 单次运行
|
|
37
32
|
|
|
@@ -49,10 +44,9 @@ algorithm-plugin run camera-space-mano \
|
|
|
49
44
|
|
|
50
45
|
目标算法仓库必须满足:
|
|
51
46
|
|
|
52
|
-
- 仓库本身是 Git
|
|
47
|
+
- 仓库本身是 Git 工作树根目录。
|
|
53
48
|
- 仓库包含自己的 `.venv`。
|
|
54
49
|
- `<repository>/.venv/bin/algorithm-plugin` 存在且可执行。
|
|
55
|
-
- 该启动器的 `run --help` 和 `serve --help` 均可正常加载。
|
|
56
50
|
|
|
57
51
|
建议将生成的 `release-manifest.json` 和 `algorithm-plugin.json` 加入算法仓库的
|
|
58
52
|
`.gitignore`,不要手动编辑或提交它们。
|
|
@@ -64,16 +58,11 @@ sudo /srv/camera-space-mano/.venv/bin/algorithm-plugin configure \
|
|
|
64
58
|
--repository /srv/camera-space-mano \
|
|
65
59
|
--compute-url http://compute:5180 \
|
|
66
60
|
--api-key "$LDP_INTERNAL_API_KEY" \
|
|
67
|
-
--
|
|
68
|
-
--input-root /data/jobs/input \
|
|
69
|
-
--workspace-root /data/jobs/workspace \
|
|
61
|
+
--port 9030 \
|
|
70
62
|
--cluster production \
|
|
71
|
-
--namespace camera-space-mano \
|
|
72
|
-
--node-name 10.0.0.4 \
|
|
73
63
|
--gpu-ids 0,1
|
|
74
64
|
```
|
|
75
65
|
|
|
76
|
-
service name 默认使用仓库目录名,也可以通过 `--service-name` 指定。
|
|
77
66
|
重复执行 configure 需要显式提供 `--force`。
|
|
78
67
|
|
|
79
68
|
确认输出后执行:
|
|
@@ -82,7 +71,7 @@ service name 默认使用仓库目录名,也可以通过 `--service-name` 指
|
|
|
82
71
|
sudo systemctl daemon-reload
|
|
83
72
|
sudo systemctl enable camera-space-mano.service
|
|
84
73
|
sudo systemctl restart camera-space-mano.service
|
|
85
|
-
sudo systemctl status camera-space-mano.service
|
|
74
|
+
sudo systemctl status camera-space-mano.service --no-pager
|
|
86
75
|
```
|
|
87
76
|
|
|
88
77
|
## serve
|
|
@@ -95,6 +84,6 @@ sudo systemctl status camera-space-mano.service
|
|
|
95
84
|
## 安装与测试
|
|
96
85
|
|
|
97
86
|
```bash
|
|
98
|
-
python -m pip install
|
|
87
|
+
python -m pip install .
|
|
99
88
|
python -m unittest discover -s tests -v
|
|
100
89
|
```
|
|
@@ -2,7 +2,7 @@ algorithm_plugin_sdk/__init__.py,sha256=OKj2VcNXTwZmRgPlECI6QUBlxW1YLtflr4RAgs6w
|
|
|
2
2
|
algorithm_plugin_sdk/algorithm.py,sha256=INjX-ygMQfHk8D1AZbEEdCw1P4VNQLGF989lP1J1H1k,1025
|
|
3
3
|
algorithm_plugin_sdk/cli.py,sha256=V9HFJ98iyhM1q6PZqd-366g1AoKuieC_Fn4u9RJcMNI,1252
|
|
4
4
|
algorithm_plugin_sdk/context.py,sha256=Kj-eDZg7PgX6xNzrWrQs37UfepTP8fXv4_8hCgsAIDA,11016
|
|
5
|
-
algorithm_plugin_sdk/deployment.py,sha256=
|
|
5
|
+
algorithm_plugin_sdk/deployment.py,sha256=yv6UCMtbX1pglfq7H7N2xI4_-bM-YLSPXFST5m_pW-g,13931
|
|
6
6
|
algorithm_plugin_sdk/errors.py,sha256=DNmI2c36Pd7K6tTaonqfEgpBzwX0o6pXzYrc8r2Z--k,730
|
|
7
7
|
algorithm_plugin_sdk/loader.py,sha256=XKmyqbJMoUMdKoo2eRQFGdbsmlz6NBgtUeIdqzumJFs,2561
|
|
8
8
|
algorithm_plugin_sdk/models.py,sha256=FIwUm8QDK8L37pV7AcCMnmYlDP-2l93zp8yc0KSu4yo,12196
|
|
@@ -12,7 +12,7 @@ algorithm_plugin_sdk/runner.py,sha256=583Y9_7efvW5rrmM2nt316NEpCRO6kd8dKYjFRjgtC
|
|
|
12
12
|
algorithm_plugin_sdk/service.py,sha256=Xj99UiCZtqtu7U4IVghAr4Mu3I9nR1AvP6vNzdD4sAk,24002
|
|
13
13
|
algorithm_plugin_sdk/webui_app.py,sha256=Gvt2FPt0xZBjYi39sqTQqcosllifYeJam49_mGfkEVw,1530
|
|
14
14
|
algorithm_plugin_sdk/cli_impl/__init__.py,sha256=uLknQVGipNd7eSJegDHZS0NVowMa0qAaOf63wFI3zHM,78
|
|
15
|
-
algorithm_plugin_sdk/cli_impl/configure.py,sha256=
|
|
15
|
+
algorithm_plugin_sdk/cli_impl/configure.py,sha256=fr_xDLh82YKV8Csuxi5M2z4dPpXWv9j7SZUHa1n14dA,12496
|
|
16
16
|
algorithm_plugin_sdk/cli_impl/parsing.py,sha256=TDX45Spu4WMrczuzTMN9ZC2SYyHE11OPBCvN45yPsxs,1399
|
|
17
17
|
algorithm_plugin_sdk/cli_impl/run.py,sha256=EnqqCdGWYK-iB5s1DMlGRXNpnCWA25xoAex0f4l36Ic,5893
|
|
18
18
|
algorithm_plugin_sdk/cli_impl/serve.py,sha256=9_nJcdzerwqczGm7xTIcMYKShpmoltYD6r_nBQjAlWU,4324
|
|
@@ -23,8 +23,8 @@ algorithm_plugin_sdk/webui/__init__.py,sha256=cvtaktJXz_DYG4QbV5ppoqY2bFaMLuiUcT
|
|
|
23
23
|
algorithm_plugin_sdk/webui/app.css,sha256=5HGDKlbUFLZMgCknlRuwCjiNcCr8poIF5YGHchyaamE,2965
|
|
24
24
|
algorithm_plugin_sdk/webui/app.js,sha256=0YsXsocEY3yDseG3jh0RhzI8xo_bYmsbSxdN3FdCswQ,7657
|
|
25
25
|
algorithm_plugin_sdk/webui/index.html,sha256=Z222OuaacWdbr0s-sjJq6wmg45u96tIneM5-LP55Z8I,2261
|
|
26
|
-
ls_algorithm_plugin_sdk-0.3.
|
|
27
|
-
ls_algorithm_plugin_sdk-0.3.
|
|
28
|
-
ls_algorithm_plugin_sdk-0.3.
|
|
29
|
-
ls_algorithm_plugin_sdk-0.3.
|
|
30
|
-
ls_algorithm_plugin_sdk-0.3.
|
|
26
|
+
ls_algorithm_plugin_sdk-0.3.1.dist-info/METADATA,sha256=m3-0HP49-i6sFKAgZjcsWLMGdUdVL-jLUag4siJ9i3Q,2317
|
|
27
|
+
ls_algorithm_plugin_sdk-0.3.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
28
|
+
ls_algorithm_plugin_sdk-0.3.1.dist-info/entry_points.txt,sha256=TU0R_TxuB5OuOiZ5BHOEmUPtHvN3v8ARUW8d3PztHcE,67
|
|
29
|
+
ls_algorithm_plugin_sdk-0.3.1.dist-info/top_level.txt,sha256=8lsgxZ8HJGLlzJ5Rt8CiVzw9Ccme286i3akbmeMjBos,21
|
|
30
|
+
ls_algorithm_plugin_sdk-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
{ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{ls_algorithm_plugin_sdk-0.3.0.dist-info → ls_algorithm_plugin_sdk-0.3.1.dist-info}/top_level.txt
RENAMED
|
File without changes
|