gpustack-runtime 0.1.39.post1__py3-none-any.whl → 0.1.39.post3__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.
- gpustack_runtime/__main__.py +6 -0
- gpustack_runtime/_version.py +2 -2
- gpustack_runtime/_version_appendix.py +1 -1
- gpustack_runtime/cmds/__init__.py +6 -0
- gpustack_runtime/cmds/deployer.py +170 -40
- gpustack_runtime/deployer/__init__.py +197 -0
- gpustack_runtime/deployer/__types__.py +382 -17
- gpustack_runtime/deployer/__utils__.py +34 -0
- gpustack_runtime/deployer/docker.py +280 -66
- gpustack_runtime/deployer/kuberentes.py +288 -45
- gpustack_runtime/deployer/podman.py +290 -66
- gpustack_runtime/detector/__utils__.py +23 -0
- gpustack_runtime/detector/amd.py +18 -10
- gpustack_runtime/detector/hygon.py +7 -2
- gpustack_runtime/detector/iluvatar.py +10 -2
- gpustack_runtime/detector/mthreads.py +8 -12
- gpustack_runtime/detector/nvidia.py +194 -86
- gpustack_runtime/detector/pyhsa/__init__.py +7 -7
- gpustack_runtime/detector/pyrocmsmi/__init__.py +3 -9
- gpustack_runtime/envs.py +30 -18
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/METADATA +3 -2
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/RECORD +25 -26
- gpustack_runtime/detector/pymtml/__init__.py +0 -770
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/WHEEL +0 -0
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/entry_points.txt +0 -0
- {gpustack_runtime-0.1.39.post1.dist-info → gpustack_runtime-0.1.39.post3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1020,11 +1020,13 @@ class WorkloadPlan(WorkloadSecurity):
|
|
|
1020
1020
|
c.execution.command_script = None
|
|
1021
1021
|
# Add default registry if needed.
|
|
1022
1022
|
if (
|
|
1023
|
-
envs.
|
|
1024
|
-
and envs.
|
|
1023
|
+
envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY
|
|
1024
|
+
and envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_REGISTRY
|
|
1025
1025
|
not in ["docker.io", "index.docker.io"]
|
|
1026
1026
|
):
|
|
1027
|
-
image_registry =
|
|
1027
|
+
image_registry = (
|
|
1028
|
+
envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_CONTAINER_NAMESPACE
|
|
1029
|
+
)
|
|
1028
1030
|
image_split = c.image.split("/")
|
|
1029
1031
|
if len(image_split) == 1:
|
|
1030
1032
|
c.image = f"{image_registry}/library/{c.image}"
|
|
@@ -1245,6 +1247,25 @@ class WorkloadExecStream(ABC):
|
|
|
1245
1247
|
raise NotImplementedError
|
|
1246
1248
|
|
|
1247
1249
|
|
|
1250
|
+
def _default_args(func):
|
|
1251
|
+
"""
|
|
1252
|
+
Decorator to set default args for deployer methods.
|
|
1253
|
+
|
|
1254
|
+
"""
|
|
1255
|
+
|
|
1256
|
+
def wrapper(self, *args, async_mode=None, **kwargs):
|
|
1257
|
+
if async_mode is None:
|
|
1258
|
+
async_mode = envs.GPUSTACK_RUNTIME_DEPLOY_ASYNC
|
|
1259
|
+
return func(
|
|
1260
|
+
self,
|
|
1261
|
+
*args,
|
|
1262
|
+
async_mode=async_mode,
|
|
1263
|
+
**kwargs,
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
return wrapper
|
|
1267
|
+
|
|
1268
|
+
|
|
1248
1269
|
class Deployer(ABC):
|
|
1249
1270
|
"""
|
|
1250
1271
|
Base class for all deployers.
|
|
@@ -1326,20 +1347,6 @@ class Deployer(ABC):
|
|
|
1326
1347
|
"""
|
|
1327
1348
|
raise NotImplementedError
|
|
1328
1349
|
|
|
1329
|
-
@staticmethod
|
|
1330
|
-
def _default_args(func):
|
|
1331
|
-
def wrapper(self, *args, async_mode=None, **kwargs):
|
|
1332
|
-
if async_mode is None:
|
|
1333
|
-
async_mode = envs.GPUSTACK_RUNTIME_DEPLOY_ASYNC
|
|
1334
|
-
return func(
|
|
1335
|
-
self,
|
|
1336
|
-
*args,
|
|
1337
|
-
async_mode=async_mode,
|
|
1338
|
-
**kwargs,
|
|
1339
|
-
)
|
|
1340
|
-
|
|
1341
|
-
return wrapper
|
|
1342
|
-
|
|
1343
1350
|
def __init__(self, name: str):
|
|
1344
1351
|
self._name = name
|
|
1345
1352
|
|
|
@@ -2130,3 +2137,361 @@ class Deployer(ABC):
|
|
|
2130
2137
|
|
|
2131
2138
|
"""
|
|
2132
2139
|
raise NotImplementedError
|
|
2140
|
+
|
|
2141
|
+
@_default_args
|
|
2142
|
+
def inspect(
|
|
2143
|
+
self,
|
|
2144
|
+
name: WorkloadName,
|
|
2145
|
+
namespace: WorkloadNamespace | None = None,
|
|
2146
|
+
async_mode: bool | None = None,
|
|
2147
|
+
) -> str | None:
|
|
2148
|
+
"""
|
|
2149
|
+
Inspect a workload.
|
|
2150
|
+
|
|
2151
|
+
Args:
|
|
2152
|
+
name:
|
|
2153
|
+
The name of the workload.
|
|
2154
|
+
namespace:
|
|
2155
|
+
The namespace of the workload.
|
|
2156
|
+
async_mode:
|
|
2157
|
+
Whether to execute in a separate thread.
|
|
2158
|
+
|
|
2159
|
+
Returns:
|
|
2160
|
+
The inspection result. None if not found.
|
|
2161
|
+
|
|
2162
|
+
Raises:
|
|
2163
|
+
UnsupportedError:
|
|
2164
|
+
If the deployer is not supported in the current environment.
|
|
2165
|
+
OperationError:
|
|
2166
|
+
If the workload fails to inspect.
|
|
2167
|
+
|
|
2168
|
+
"""
|
|
2169
|
+
if async_mode:
|
|
2170
|
+
try:
|
|
2171
|
+
future = self.pool.submit(
|
|
2172
|
+
self._inspect,
|
|
2173
|
+
name,
|
|
2174
|
+
namespace,
|
|
2175
|
+
)
|
|
2176
|
+
return future.result()
|
|
2177
|
+
except OperationError:
|
|
2178
|
+
raise
|
|
2179
|
+
except Exception as e:
|
|
2180
|
+
msg = "Asynchronous workload inspect failed."
|
|
2181
|
+
raise OperationError(msg) from e
|
|
2182
|
+
else:
|
|
2183
|
+
return self._inspect(name, namespace)
|
|
2184
|
+
|
|
2185
|
+
@abstractmethod
|
|
2186
|
+
def _inspect(
|
|
2187
|
+
self,
|
|
2188
|
+
name: WorkloadName,
|
|
2189
|
+
namespace: WorkloadNamespace | None = None,
|
|
2190
|
+
) -> str | None:
|
|
2191
|
+
"""
|
|
2192
|
+
Inspect a workload.
|
|
2193
|
+
|
|
2194
|
+
Args:
|
|
2195
|
+
name:
|
|
2196
|
+
The name of the workload.
|
|
2197
|
+
namespace:
|
|
2198
|
+
The namespace of the workload.
|
|
2199
|
+
|
|
2200
|
+
Returns:
|
|
2201
|
+
The inspection result. None if not found.
|
|
2202
|
+
|
|
2203
|
+
Raises:
|
|
2204
|
+
UnsupportedError:
|
|
2205
|
+
If the deployer is not supported in the current environment.
|
|
2206
|
+
OperationError:
|
|
2207
|
+
If the workload fails to inspect.
|
|
2208
|
+
|
|
2209
|
+
"""
|
|
2210
|
+
raise NotImplementedError
|
|
2211
|
+
|
|
2212
|
+
|
|
2213
|
+
class EndoscopicDeployer(Deployer):
|
|
2214
|
+
"""
|
|
2215
|
+
Base class for endoscopic deployers.
|
|
2216
|
+
"""
|
|
2217
|
+
|
|
2218
|
+
async def async_endoscopic_logs(
|
|
2219
|
+
self,
|
|
2220
|
+
timestamps: bool = False,
|
|
2221
|
+
tail: int | None = None,
|
|
2222
|
+
since: int | None = None,
|
|
2223
|
+
follow: bool = False,
|
|
2224
|
+
async_mode: bool | None = None,
|
|
2225
|
+
) -> AsyncGenerator[bytes | str, None, None] | bytes | str:
|
|
2226
|
+
"""
|
|
2227
|
+
Asynchronously get the logs of the deployer itself.
|
|
2228
|
+
Only works in mirrored deployment mode.
|
|
2229
|
+
|
|
2230
|
+
Args:
|
|
2231
|
+
timestamps:
|
|
2232
|
+
Show timestamps in the logs.
|
|
2233
|
+
tail:
|
|
2234
|
+
Number of lines to show from the end of the logs.
|
|
2235
|
+
since:
|
|
2236
|
+
Show logs since the given epoch in seconds.
|
|
2237
|
+
follow:
|
|
2238
|
+
Whether to follow the logs.
|
|
2239
|
+
async_mode:
|
|
2240
|
+
Whether to execute in a separate thread.
|
|
2241
|
+
|
|
2242
|
+
Returns:
|
|
2243
|
+
The logs as a byte string or a generator yielding byte strings if follow is True.
|
|
2244
|
+
|
|
2245
|
+
Raises:
|
|
2246
|
+
UnsupportedError:
|
|
2247
|
+
If the deployer is not supported in the current environment.
|
|
2248
|
+
OperationError:
|
|
2249
|
+
If the deployer fails to get logs.
|
|
2250
|
+
|
|
2251
|
+
"""
|
|
2252
|
+
|
|
2253
|
+
def run_sync_gen(f: bool) -> Generator[bytes | str, None, None] | bytes | str:
|
|
2254
|
+
return self.endoscopic_logs(
|
|
2255
|
+
timestamps=timestamps,
|
|
2256
|
+
tail=tail,
|
|
2257
|
+
since=since,
|
|
2258
|
+
follow=f,
|
|
2259
|
+
async_mode=async_mode,
|
|
2260
|
+
)
|
|
2261
|
+
|
|
2262
|
+
if not follow:
|
|
2263
|
+
return run_sync_gen(False)
|
|
2264
|
+
|
|
2265
|
+
async def async_gen() -> AsyncGenerator[bytes | str, None, None]:
|
|
2266
|
+
loop = asyncio.get_event_loop()
|
|
2267
|
+
sync_gen = await loop.run_in_executor(self.pool, run_sync_gen, True)
|
|
2268
|
+
with contextlib.closing(sync_gen) as gen:
|
|
2269
|
+
for chunk in gen:
|
|
2270
|
+
yield chunk
|
|
2271
|
+
|
|
2272
|
+
return async_gen()
|
|
2273
|
+
|
|
2274
|
+
@_default_args
|
|
2275
|
+
def endoscopic_logs(
|
|
2276
|
+
self,
|
|
2277
|
+
timestamps: bool = False,
|
|
2278
|
+
tail: int | None = None,
|
|
2279
|
+
since: int | None = None,
|
|
2280
|
+
follow: bool = False,
|
|
2281
|
+
async_mode: bool | None = None,
|
|
2282
|
+
) -> Generator[bytes | str, None, None] | bytes | str:
|
|
2283
|
+
"""
|
|
2284
|
+
Get the logs of the deployer itself.
|
|
2285
|
+
Only works in mirrored deployment mode.
|
|
2286
|
+
|
|
2287
|
+
Args:
|
|
2288
|
+
timestamps:
|
|
2289
|
+
Show timestamps in the logs.
|
|
2290
|
+
tail:
|
|
2291
|
+
Number of lines to show from the end of the logs.
|
|
2292
|
+
since:
|
|
2293
|
+
Show logs since the given epoch in seconds.
|
|
2294
|
+
follow:
|
|
2295
|
+
Whether to follow the logs.
|
|
2296
|
+
async_mode:
|
|
2297
|
+
Whether to execute in a separate thread.
|
|
2298
|
+
|
|
2299
|
+
Returns:
|
|
2300
|
+
The logs as a byte string or a generator yielding byte strings if follow is True.
|
|
2301
|
+
|
|
2302
|
+
Raises:
|
|
2303
|
+
UnsupportedError:
|
|
2304
|
+
If the deployer is not supported in the current environment.
|
|
2305
|
+
OperationError:
|
|
2306
|
+
If the deployer fails to get logs.
|
|
2307
|
+
|
|
2308
|
+
"""
|
|
2309
|
+
if async_mode:
|
|
2310
|
+
try:
|
|
2311
|
+
future = self.pool.submit(
|
|
2312
|
+
self._endoscopic_logs,
|
|
2313
|
+
timestamps,
|
|
2314
|
+
tail,
|
|
2315
|
+
since,
|
|
2316
|
+
follow,
|
|
2317
|
+
)
|
|
2318
|
+
return future.result()
|
|
2319
|
+
except OperationError:
|
|
2320
|
+
raise
|
|
2321
|
+
except Exception as e:
|
|
2322
|
+
msg = "Asynchronous endoscopic logs failed."
|
|
2323
|
+
raise OperationError(msg) from e
|
|
2324
|
+
else:
|
|
2325
|
+
return self._endoscopic_logs(timestamps, tail, since, follow)
|
|
2326
|
+
|
|
2327
|
+
@abstractmethod
|
|
2328
|
+
def _endoscopic_logs(
|
|
2329
|
+
self,
|
|
2330
|
+
timestamps: bool = False,
|
|
2331
|
+
tail: int | None = None,
|
|
2332
|
+
since: int | None = None,
|
|
2333
|
+
follow: bool = False,
|
|
2334
|
+
) -> Generator[bytes | str, None, None] | bytes | str:
|
|
2335
|
+
"""
|
|
2336
|
+
Get the logs of the deployer itself.
|
|
2337
|
+
Only works in mirrored deployment mode.
|
|
2338
|
+
|
|
2339
|
+
Args:
|
|
2340
|
+
timestamps:
|
|
2341
|
+
Show timestamps in the logs.
|
|
2342
|
+
tail:
|
|
2343
|
+
Number of lines to show from the end of the logs.
|
|
2344
|
+
since:
|
|
2345
|
+
Show logs since the given epoch in seconds.
|
|
2346
|
+
follow:
|
|
2347
|
+
Whether to follow the logs.
|
|
2348
|
+
|
|
2349
|
+
Returns:
|
|
2350
|
+
The logs as a byte string or a generator yielding byte strings if follow is True.
|
|
2351
|
+
|
|
2352
|
+
Raises:
|
|
2353
|
+
UnsupportedError:
|
|
2354
|
+
If the deployer is not supported in the current environment.
|
|
2355
|
+
OperationError:
|
|
2356
|
+
If the deployer fails to get logs.
|
|
2357
|
+
|
|
2358
|
+
"""
|
|
2359
|
+
raise NotImplementedError
|
|
2360
|
+
|
|
2361
|
+
@_default_args
|
|
2362
|
+
def endoscopic_exec(
|
|
2363
|
+
self,
|
|
2364
|
+
detach: bool = True,
|
|
2365
|
+
command: list[str] | None = None,
|
|
2366
|
+
args: list[str] | None = None,
|
|
2367
|
+
async_mode: bool | None = None,
|
|
2368
|
+
) -> WorkloadExecStream | bytes | str:
|
|
2369
|
+
"""
|
|
2370
|
+
Execute a command in the deployer itself.
|
|
2371
|
+
Only works in mirrored deployment mode.
|
|
2372
|
+
|
|
2373
|
+
Args:
|
|
2374
|
+
detach:
|
|
2375
|
+
Whether to detach from the command.
|
|
2376
|
+
command:
|
|
2377
|
+
The command to execute.
|
|
2378
|
+
If not specified, use /bin/sh and implicitly attach.
|
|
2379
|
+
args:
|
|
2380
|
+
The arguments to pass to the command.
|
|
2381
|
+
async_mode:
|
|
2382
|
+
Whether to execute in a separate thread.
|
|
2383
|
+
|
|
2384
|
+
Returns:
|
|
2385
|
+
If detach is False, return a WorkloadExecStream.
|
|
2386
|
+
otherwise, return the output of the command as a byte string or string.
|
|
2387
|
+
|
|
2388
|
+
Raises:
|
|
2389
|
+
UnsupportedError:
|
|
2390
|
+
If the deployer is not supported in the current environment.
|
|
2391
|
+
OperationError:
|
|
2392
|
+
If the deployer fails to execute the command.
|
|
2393
|
+
|
|
2394
|
+
"""
|
|
2395
|
+
if async_mode:
|
|
2396
|
+
try:
|
|
2397
|
+
future = self.pool.submit(
|
|
2398
|
+
self._endoscopic_exec,
|
|
2399
|
+
detach,
|
|
2400
|
+
command,
|
|
2401
|
+
args,
|
|
2402
|
+
)
|
|
2403
|
+
return future.result()
|
|
2404
|
+
except OperationError:
|
|
2405
|
+
raise
|
|
2406
|
+
except Exception as e:
|
|
2407
|
+
msg = "Asynchronous endoscopic exec failed."
|
|
2408
|
+
raise OperationError(msg) from e
|
|
2409
|
+
else:
|
|
2410
|
+
return self._endoscopic_exec(detach, command, args)
|
|
2411
|
+
|
|
2412
|
+
@abstractmethod
|
|
2413
|
+
def _endoscopic_exec(
|
|
2414
|
+
self,
|
|
2415
|
+
detach: bool = True,
|
|
2416
|
+
command: list[str] | None = None,
|
|
2417
|
+
args: list[str] | None = None,
|
|
2418
|
+
) -> WorkloadExecStream | bytes | str:
|
|
2419
|
+
"""
|
|
2420
|
+
Execute a command in the deployer itself.
|
|
2421
|
+
Only works in mirrored deployment mode.
|
|
2422
|
+
|
|
2423
|
+
Args:
|
|
2424
|
+
detach:
|
|
2425
|
+
Whether to detach from the command.
|
|
2426
|
+
command:
|
|
2427
|
+
The command to execute.
|
|
2428
|
+
If not specified, use /bin/sh and implicitly attach.
|
|
2429
|
+
args:
|
|
2430
|
+
The arguments to pass to the command.
|
|
2431
|
+
|
|
2432
|
+
Returns:
|
|
2433
|
+
If detach is False, return a WorkloadExecStream.
|
|
2434
|
+
otherwise, return the output of the command as a byte string or string.
|
|
2435
|
+
|
|
2436
|
+
Raises:
|
|
2437
|
+
UnsupportedError:
|
|
2438
|
+
If the deployer is not supported in the current environment.
|
|
2439
|
+
OperationError:
|
|
2440
|
+
If the deployer fails to execute the command.
|
|
2441
|
+
|
|
2442
|
+
"""
|
|
2443
|
+
raise NotImplementedError
|
|
2444
|
+
|
|
2445
|
+
def endoscopic_inspect(
|
|
2446
|
+
self,
|
|
2447
|
+
async_mode: bool | None = None,
|
|
2448
|
+
) -> str:
|
|
2449
|
+
"""
|
|
2450
|
+
Inspect the deployer itself.
|
|
2451
|
+
Only works in mirrored deployment mode.
|
|
2452
|
+
|
|
2453
|
+
Args:
|
|
2454
|
+
async_mode:
|
|
2455
|
+
Whether to execute in a separate thread.
|
|
2456
|
+
|
|
2457
|
+
Returns:
|
|
2458
|
+
The inspection result.
|
|
2459
|
+
|
|
2460
|
+
Raises:
|
|
2461
|
+
UnsupportedError:
|
|
2462
|
+
If the deployer is not supported in the current environment.
|
|
2463
|
+
OperationError:
|
|
2464
|
+
If the deployer fails to execute the command.
|
|
2465
|
+
|
|
2466
|
+
"""
|
|
2467
|
+
if async_mode:
|
|
2468
|
+
try:
|
|
2469
|
+
future = self.pool.submit(
|
|
2470
|
+
self._endoscopic_inspect,
|
|
2471
|
+
)
|
|
2472
|
+
return future.result()
|
|
2473
|
+
except OperationError:
|
|
2474
|
+
raise
|
|
2475
|
+
except Exception as e:
|
|
2476
|
+
msg = "Asynchronous endoscopic inspect failed."
|
|
2477
|
+
raise OperationError(msg) from e
|
|
2478
|
+
else:
|
|
2479
|
+
return self._endoscopic_inspect()
|
|
2480
|
+
|
|
2481
|
+
@abstractmethod
|
|
2482
|
+
def _endoscopic_inspect(self) -> str:
|
|
2483
|
+
"""
|
|
2484
|
+
Inspect the deployer itself.
|
|
2485
|
+
Only works in mirrored deployment mode.
|
|
2486
|
+
|
|
2487
|
+
Returns:
|
|
2488
|
+
The inspection result.
|
|
2489
|
+
|
|
2490
|
+
Raises:
|
|
2491
|
+
UnsupportedError:
|
|
2492
|
+
If the deployer is not supported in the current environment.
|
|
2493
|
+
OperationError:
|
|
2494
|
+
If the deployer fails to execute the command.
|
|
2495
|
+
|
|
2496
|
+
"""
|
|
2497
|
+
raise NotImplementedError
|
|
@@ -42,6 +42,25 @@ Regex for RFC1035 domain name, which must:
|
|
|
42
42
|
- start with an alphabetic character
|
|
43
43
|
- end with an alphanumeric character
|
|
44
44
|
"""
|
|
45
|
+
_SENSITIVE_ENVS_SUFFIX = (
|
|
46
|
+
"key",
|
|
47
|
+
"keys",
|
|
48
|
+
"token",
|
|
49
|
+
"tokens",
|
|
50
|
+
"secret",
|
|
51
|
+
"secrets",
|
|
52
|
+
"password",
|
|
53
|
+
"passwords",
|
|
54
|
+
"passwd",
|
|
55
|
+
"pass",
|
|
56
|
+
"credential",
|
|
57
|
+
"credentials",
|
|
58
|
+
"cred",
|
|
59
|
+
"auth",
|
|
60
|
+
)
|
|
61
|
+
"""
|
|
62
|
+
Suffixes of environment variable names that are considered sensitive.
|
|
63
|
+
"""
|
|
45
64
|
|
|
46
65
|
|
|
47
66
|
@lru_cache
|
|
@@ -775,3 +794,18 @@ def make_image_with(
|
|
|
775
794
|
else:
|
|
776
795
|
image += f":{tag}"
|
|
777
796
|
return image
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def sensitive_env_var(name: str) -> bool:
|
|
800
|
+
"""
|
|
801
|
+
Check if the given environment variable name is considered sensitive.
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
name:
|
|
805
|
+
The environment variable name to check.
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
True if the name is considered sensitive, False otherwise.
|
|
809
|
+
|
|
810
|
+
"""
|
|
811
|
+
return name.lower().endswith(_SENSITIVE_ENVS_SUFFIX)
|