skypilot-nightly 1.0.0.dev20250903__py3-none-any.whl → 1.0.0.dev20250904__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.
Potentially problematic release.
This version of skypilot-nightly might be problematic. Click here for more details.
- sky/__init__.py +2 -2
- sky/backends/cloud_vm_ray_backend.py +7 -2
- sky/client/cli/command.py +18 -1
- sky/client/sdk.py +22 -1
- sky/clouds/nebius.py +4 -2
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/{1121-ec35954c8cbea535.js → 1121-408ed10b2f9fce17.js} +1 -1
- sky/dashboard/out/_next/static/chunks/{7205-88191679e7988c57.js → 1836-37fede578e2da5f8.js} +4 -9
- sky/dashboard/out/_next/static/chunks/3015-86cabed5d4669ad0.js +1 -0
- sky/dashboard/out/_next/static/chunks/3294.c80326aec9bfed40.js +6 -0
- sky/dashboard/out/_next/static/chunks/{3785.d5b86f6ebc88e6e6.js → 3785.4872a2f3aa489880.js} +1 -1
- sky/dashboard/out/_next/static/chunks/{4783.c485f48348349f47.js → 5339.3fda4a4010ff4e06.js} +4 -9
- sky/dashboard/out/_next/static/chunks/{9946.3b7b43c217ff70ec.js → 649.b9d7f7d10c1b8c53.js} +4 -9
- sky/dashboard/out/_next/static/chunks/6856-66e696640347e77b.js +1 -0
- sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +6 -0
- sky/dashboard/out/_next/static/chunks/9037-1c0101b86582136f.js +6 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-39c9bd4cdb7e5a57.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/{[cluster]-a0527109c2fab467.js → [cluster]-0b4b35dc1dfe046c.js} +2 -7
- sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-81351f95f3bec08e.js → [context]-6563820e094f68ca.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{infra-c320641c2bcbbea6.js → infra-aabba60d57826e0f.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs-1f70d9faa564804f.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-de06e613e20bc977.js → [name]-af76bb06dbb3954f.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{workspaces-be35b22e2046564c.js → workspaces-7598c33a746cdc91.js} +1 -1
- sky/dashboard/out/_next/static/chunks/webpack-24c4fc6d30ce0193.js +1 -0
- sky/dashboard/out/_next/static/mriHUOVL_Ht-CeW-e7saa/_buildManifest.js +1 -0
- sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
- sky/dashboard/out/clusters/[cluster].html +1 -1
- sky/dashboard/out/clusters.html +1 -1
- sky/dashboard/out/config.html +1 -1
- sky/dashboard/out/index.html +1 -1
- sky/dashboard/out/infra/[context].html +1 -1
- sky/dashboard/out/infra.html +1 -1
- sky/dashboard/out/jobs/[job].html +1 -1
- sky/dashboard/out/jobs/pools/[pool].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -1
- sky/dashboard/out/volumes.html +1 -1
- sky/dashboard/out/workspace/new.html +1 -1
- sky/dashboard/out/workspaces/[name].html +1 -1
- sky/dashboard/out/workspaces.html +1 -1
- sky/data/mounting_utils.py +29 -38
- sky/global_user_state.py +17 -5
- sky/jobs/state.py +1 -1
- sky/provision/kubernetes/instance.py +10 -3
- sky/serve/serve_state.py +1 -1
- sky/server/config.py +31 -3
- sky/server/requests/executor.py +9 -3
- sky/server/requests/requests.py +9 -0
- sky/server/server.py +24 -21
- sky/server/uvicorn.py +9 -3
- sky/skylet/constants.py +1 -1
- sky/skypilot_config.py +21 -9
- sky/ssh_node_pools/server.py +5 -5
- sky/users/server.py +18 -15
- sky/utils/db/db_utils.py +58 -1
- sky/utils/db/migration_utils.py +0 -32
- {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/METADATA +33 -33
- {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/RECORD +63 -63
- sky/dashboard/out/_next/static/chunks/3015-8089ed1e0b7e37fd.js +0 -1
- sky/dashboard/out/_next/static/chunks/6856-049014c6d43d127b.js +0 -1
- sky/dashboard/out/_next/static/chunks/9025.a1bef12d672bb66d.js +0 -6
- sky/dashboard/out/_next/static/chunks/9037-89a84fd7fa31362d.js +0 -6
- sky/dashboard/out/_next/static/chunks/9984.7eb6cc51fb460cae.js +0 -6
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-b77360a343d48902.js +0 -16
- sky/dashboard/out/_next/static/chunks/pages/jobs-7421e63ac35f8fce.js +0 -1
- sky/dashboard/out/_next/static/chunks/webpack-60556df644cd5d71.js +0 -1
- sky/dashboard/out/_next/static/yLz6EPhW_XXmnNs1I6dmS/_buildManifest.js +0 -1
- /sky/dashboard/out/_next/static/{yLz6EPhW_XXmnNs1I6dmS → mriHUOVL_Ht-CeW-e7saa}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250903.dist-info → skypilot_nightly-1.0.0.dev20250904.dist-info}/top_level.txt +0 -0
sky/server/requests/requests.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Utilities for REST API."""
|
|
2
2
|
import asyncio
|
|
3
|
+
import atexit
|
|
3
4
|
import contextlib
|
|
4
5
|
import dataclasses
|
|
5
6
|
import enum
|
|
@@ -842,3 +843,11 @@ async def requests_gc_daemon():
|
|
|
842
843
|
# Run the daemon at most once every hour to avoid too frequent
|
|
843
844
|
# cleanup.
|
|
844
845
|
await asyncio.sleep(max(retention_seconds, 3600))
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
def _cleanup():
|
|
849
|
+
if _DB is not None:
|
|
850
|
+
asyncio.run(_DB.close())
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
atexit.register(_cleanup)
|
sky/server/server.py
CHANGED
|
@@ -24,7 +24,6 @@ import aiofiles
|
|
|
24
24
|
import anyio
|
|
25
25
|
import fastapi
|
|
26
26
|
from fastapi.middleware import cors
|
|
27
|
-
from sqlalchemy import pool
|
|
28
27
|
import starlette.middleware.base
|
|
29
28
|
import uvloop
|
|
30
29
|
|
|
@@ -72,6 +71,7 @@ from sky.utils import dag_utils
|
|
|
72
71
|
from sky.utils import perf_utils
|
|
73
72
|
from sky.utils import status_lib
|
|
74
73
|
from sky.utils import subprocess_utils
|
|
74
|
+
from sky.utils.db import db_utils
|
|
75
75
|
from sky.volumes.server import server as volumes_rest
|
|
76
76
|
from sky.workspaces import server as workspaces_rest
|
|
77
77
|
|
|
@@ -1322,18 +1322,17 @@ async def download(download_body: payloads.DownloadBody,
|
|
|
1322
1322
|
detail=f'Error creating zip file: {str(e)}')
|
|
1323
1323
|
|
|
1324
1324
|
|
|
1325
|
+
# TODO(aylei): run it asynchronously after global_user_state support async op
|
|
1325
1326
|
@app.post('/provision_logs')
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1327
|
+
def provision_logs(cluster_body: payloads.ClusterNameBody,
|
|
1328
|
+
follow: bool = True,
|
|
1329
|
+
tail: int = 0) -> fastapi.responses.StreamingResponse:
|
|
1329
1330
|
"""Streams the provision.log for the latest launch request of a cluster."""
|
|
1330
1331
|
# Prefer clusters table first, then cluster_history as fallback.
|
|
1331
|
-
log_path_str =
|
|
1332
|
-
global_user_state.get_cluster_provision_log_path,
|
|
1332
|
+
log_path_str = global_user_state.get_cluster_provision_log_path(
|
|
1333
1333
|
cluster_body.cluster_name)
|
|
1334
1334
|
if not log_path_str:
|
|
1335
|
-
log_path_str =
|
|
1336
|
-
global_user_state.get_cluster_history_provision_log_path,
|
|
1335
|
+
log_path_str = global_user_state.get_cluster_history_provision_log_path(
|
|
1337
1336
|
cluster_body.cluster_name)
|
|
1338
1337
|
if not log_path_str:
|
|
1339
1338
|
raise fastapi.HTTPException(
|
|
@@ -1908,13 +1907,6 @@ if __name__ == '__main__':
|
|
|
1908
1907
|
|
|
1909
1908
|
skyuvicorn.add_timestamp_prefix_for_server_logs()
|
|
1910
1909
|
|
|
1911
|
-
# Initialize global user state db
|
|
1912
|
-
global_user_state.initialize_and_get_db(pool.QueuePool)
|
|
1913
|
-
# Initialize request db
|
|
1914
|
-
requests_lib.reset_db_and_logs()
|
|
1915
|
-
# Restore the server user hash
|
|
1916
|
-
_init_or_restore_server_user_hash()
|
|
1917
|
-
|
|
1918
1910
|
parser = argparse.ArgumentParser()
|
|
1919
1911
|
parser.add_argument('--host', default='127.0.0.1')
|
|
1920
1912
|
parser.add_argument('--port', default=46580, type=int)
|
|
@@ -1930,7 +1922,17 @@ if __name__ == '__main__':
|
|
|
1930
1922
|
# that it is shown only when the API server is started.
|
|
1931
1923
|
usage_lib.maybe_show_privacy_policy()
|
|
1932
1924
|
|
|
1933
|
-
|
|
1925
|
+
# Initialize global user state db
|
|
1926
|
+
db_utils.set_max_connections(1)
|
|
1927
|
+
global_user_state.initialize_and_get_db()
|
|
1928
|
+
# Initialize request db
|
|
1929
|
+
requests_lib.reset_db_and_logs()
|
|
1930
|
+
# Restore the server user hash
|
|
1931
|
+
_init_or_restore_server_user_hash()
|
|
1932
|
+
max_db_connections = global_user_state.get_max_db_connections()
|
|
1933
|
+
config = server_config.compute_server_config(cmd_args.deploy,
|
|
1934
|
+
max_db_connections)
|
|
1935
|
+
|
|
1934
1936
|
num_workers = config.num_server_workers
|
|
1935
1937
|
|
|
1936
1938
|
queue_server: Optional[multiprocessing.Process] = None
|
|
@@ -1955,11 +1957,12 @@ if __name__ == '__main__':
|
|
|
1955
1957
|
logger.info(f'Starting SkyPilot API server, workers={num_workers}')
|
|
1956
1958
|
# We don't support reload for now, since it may cause leakage of request
|
|
1957
1959
|
# workers or interrupt running requests.
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
skyuvicorn.run(
|
|
1960
|
+
uvicorn_config = uvicorn.Config('sky.server.server:app',
|
|
1961
|
+
host=cmd_args.host,
|
|
1962
|
+
port=cmd_args.port,
|
|
1963
|
+
workers=num_workers)
|
|
1964
|
+
skyuvicorn.run(uvicorn_config,
|
|
1965
|
+
max_db_connections=config.num_db_connections_per_worker)
|
|
1963
1966
|
except Exception as exc: # pylint: disable=broad-except
|
|
1964
1967
|
logger.error(f'Failed to start SkyPilot API server: '
|
|
1965
1968
|
f'{common_utils.format_exception(exc, use_bracket=True)}')
|
sky/server/uvicorn.py
CHANGED
|
@@ -26,6 +26,7 @@ from sky.utils import context_utils
|
|
|
26
26
|
from sky.utils import env_options
|
|
27
27
|
from sky.utils import perf_utils
|
|
28
28
|
from sky.utils import subprocess_utils
|
|
29
|
+
from sky.utils.db import db_utils
|
|
29
30
|
|
|
30
31
|
logger = sky_logging.init_logger(__name__)
|
|
31
32
|
|
|
@@ -88,9 +89,12 @@ class Server(uvicorn.Server):
|
|
|
88
89
|
- Run the server process with contextually aware.
|
|
89
90
|
"""
|
|
90
91
|
|
|
91
|
-
def __init__(self,
|
|
92
|
+
def __init__(self,
|
|
93
|
+
config: uvicorn.Config,
|
|
94
|
+
max_db_connections: Optional[int] = None):
|
|
92
95
|
super().__init__(config=config)
|
|
93
96
|
self.exiting: bool = False
|
|
97
|
+
self.max_db_connections = max_db_connections
|
|
94
98
|
|
|
95
99
|
def handle_exit(self, sig: int, frame: Union[FrameType, None]) -> None:
|
|
96
100
|
"""Handle exit signal.
|
|
@@ -196,6 +200,8 @@ class Server(uvicorn.Server):
|
|
|
196
200
|
|
|
197
201
|
def run(self, *args, **kwargs):
|
|
198
202
|
"""Run the server process."""
|
|
203
|
+
if self.max_db_connections is not None:
|
|
204
|
+
db_utils.set_max_connections(self.max_db_connections)
|
|
199
205
|
add_timestamp_prefix_for_server_logs()
|
|
200
206
|
context_utils.hijack_sys_attrs()
|
|
201
207
|
# Use default loop policy of uvicorn (use uvloop if available).
|
|
@@ -210,14 +216,14 @@ class Server(uvicorn.Server):
|
|
|
210
216
|
asyncio.run(self.serve(*args, **kwargs))
|
|
211
217
|
|
|
212
218
|
|
|
213
|
-
def run(config: uvicorn.Config):
|
|
219
|
+
def run(config: uvicorn.Config, max_db_connections: Optional[int] = None):
|
|
214
220
|
"""Run unvicorn server."""
|
|
215
221
|
if config.reload:
|
|
216
222
|
# Reload and multi-workers are mutually exclusive
|
|
217
223
|
# in uvicorn. Since we do not use reload now, simply
|
|
218
224
|
# guard by an exception.
|
|
219
225
|
raise ValueError('Reload is not supported yet.')
|
|
220
|
-
server = Server(config=config)
|
|
226
|
+
server = Server(config=config, max_db_connections=max_db_connections)
|
|
221
227
|
try:
|
|
222
228
|
if config.workers is not None and config.workers > 1:
|
|
223
229
|
sock = config.bind_socket()
|
sky/skylet/constants.py
CHANGED
|
@@ -362,7 +362,7 @@ SKY_SSH_USER_PLACEHOLDER = 'skypilot:ssh_user'
|
|
|
362
362
|
|
|
363
363
|
RCLONE_CONFIG_DIR = '~/.config/rclone'
|
|
364
364
|
RCLONE_CONFIG_PATH = f'{RCLONE_CONFIG_DIR}/rclone.conf'
|
|
365
|
-
|
|
365
|
+
RCLONE_MOUNT_CACHED_LOG_DIR = '~/.sky/rclone_log'
|
|
366
366
|
RCLONE_CACHE_DIR = '~/.cache/rclone'
|
|
367
367
|
RCLONE_CACHE_REFRESH_INTERVAL = 10
|
|
368
368
|
|
sky/skypilot_config.py
CHANGED
|
@@ -227,7 +227,7 @@ def _get_config_from_path(path: Optional[str]) -> config_utils.Config:
|
|
|
227
227
|
return parse_and_validate_config_file(path)
|
|
228
228
|
|
|
229
229
|
|
|
230
|
-
def
|
|
230
|
+
def resolve_user_config_path() -> Optional[str]:
|
|
231
231
|
# find the user config file path, None if not resolved.
|
|
232
232
|
user_config_path = _get_config_file_path(ENV_VAR_GLOBAL_CONFIG)
|
|
233
233
|
if user_config_path:
|
|
@@ -252,7 +252,7 @@ def _resolve_user_config_path() -> Optional[str]:
|
|
|
252
252
|
|
|
253
253
|
def get_user_config() -> config_utils.Config:
|
|
254
254
|
"""Returns the user config."""
|
|
255
|
-
return _get_config_from_path(
|
|
255
|
+
return _get_config_from_path(resolve_user_config_path())
|
|
256
256
|
|
|
257
257
|
|
|
258
258
|
def _resolve_project_config_path() -> Optional[str]:
|
|
@@ -574,8 +574,13 @@ def _reload_config_as_server() -> None:
|
|
|
574
574
|
'If db config is specified, no other config is allowed')
|
|
575
575
|
logger.debug('retrieving config from database')
|
|
576
576
|
with _DB_USE_LOCK:
|
|
577
|
-
|
|
578
|
-
|
|
577
|
+
dispose_engine = False
|
|
578
|
+
if db_utils.get_max_connections() == 0:
|
|
579
|
+
dispose_engine = True
|
|
580
|
+
sqlalchemy_engine = sqlalchemy.create_engine(db_url,
|
|
581
|
+
poolclass=NullPool)
|
|
582
|
+
else:
|
|
583
|
+
sqlalchemy_engine = db_utils.get_engine('config')
|
|
579
584
|
db_utils.add_all_tables_to_db_sqlalchemy(Base.metadata,
|
|
580
585
|
sqlalchemy_engine)
|
|
581
586
|
|
|
@@ -597,7 +602,8 @@ def _reload_config_as_server() -> None:
|
|
|
597
602
|
server_config = overlay_skypilot_config(server_config,
|
|
598
603
|
db_config)
|
|
599
604
|
# Close the engine to avoid connection leaks
|
|
600
|
-
|
|
605
|
+
if dispose_engine:
|
|
606
|
+
sqlalchemy_engine.dispose()
|
|
601
607
|
if sky_logging.logging_enabled(logger, sky_logging.DEBUG):
|
|
602
608
|
logger.debug(f'server config: \n'
|
|
603
609
|
f'{yaml_utils.dump_yaml_str(dict(server_config))}')
|
|
@@ -611,7 +617,7 @@ def _reload_config_as_client() -> None:
|
|
|
611
617
|
_set_loaded_config_path(None)
|
|
612
618
|
|
|
613
619
|
overrides: List[config_utils.Config] = []
|
|
614
|
-
user_config_path =
|
|
620
|
+
user_config_path = resolve_user_config_path()
|
|
615
621
|
user_config = _get_config_from_path(user_config_path)
|
|
616
622
|
if user_config:
|
|
617
623
|
overrides.append(user_config)
|
|
@@ -867,8 +873,13 @@ def update_api_server_config_no_lock(config: config_utils.Config) -> None:
|
|
|
867
873
|
raise ValueError('Cannot change db url while server is running')
|
|
868
874
|
if existing_db_url:
|
|
869
875
|
with _DB_USE_LOCK:
|
|
870
|
-
|
|
871
|
-
|
|
876
|
+
dispose_engine = False
|
|
877
|
+
if db_utils.get_max_connections() == 0:
|
|
878
|
+
dispose_engine = True
|
|
879
|
+
sqlalchemy_engine = sqlalchemy.create_engine(
|
|
880
|
+
existing_db_url, poolclass=NullPool)
|
|
881
|
+
else:
|
|
882
|
+
sqlalchemy_engine = db_utils.get_engine('config')
|
|
872
883
|
db_utils.add_all_tables_to_db_sqlalchemy(
|
|
873
884
|
Base.metadata, sqlalchemy_engine)
|
|
874
885
|
|
|
@@ -897,7 +908,8 @@ def update_api_server_config_no_lock(config: config_utils.Config) -> None:
|
|
|
897
908
|
_set_config_yaml_to_db(API_SERVER_CONFIG_KEY, config)
|
|
898
909
|
db_updated = True
|
|
899
910
|
# Close the engine to avoid connection leaks
|
|
900
|
-
|
|
911
|
+
if dispose_engine:
|
|
912
|
+
sqlalchemy_engine.dispose()
|
|
901
913
|
|
|
902
914
|
if not db_updated:
|
|
903
915
|
# save to the local file (PVC in Kubernetes, local file otherwise)
|
sky/ssh_node_pools/server.py
CHANGED
|
@@ -15,7 +15,7 @@ router = fastapi.APIRouter()
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@router.get('')
|
|
18
|
-
|
|
18
|
+
def get_ssh_node_pools() -> Dict[str, Any]:
|
|
19
19
|
"""Get all SSH Node Pool configurations."""
|
|
20
20
|
try:
|
|
21
21
|
return ssh_node_pools_core.get_all_pools()
|
|
@@ -27,7 +27,7 @@ async def get_ssh_node_pools() -> Dict[str, Any]:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
@router.post('')
|
|
30
|
-
|
|
30
|
+
def update_ssh_node_pools(pools_config: Dict[str, Any]) -> Dict[str, str]:
|
|
31
31
|
"""Update SSH Node Pool configurations."""
|
|
32
32
|
try:
|
|
33
33
|
ssh_node_pools_core.update_pools(pools_config)
|
|
@@ -39,7 +39,7 @@ async def update_ssh_node_pools(pools_config: Dict[str, Any]) -> Dict[str, str]:
|
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
@router.delete('/{pool_name}')
|
|
42
|
-
|
|
42
|
+
def delete_ssh_node_pool(pool_name: str) -> Dict[str, str]:
|
|
43
43
|
"""Delete a SSH Node Pool configuration."""
|
|
44
44
|
try:
|
|
45
45
|
if ssh_node_pools_core.delete_pool(pool_name):
|
|
@@ -83,7 +83,7 @@ async def upload_ssh_key(request: fastapi.Request) -> Dict[str, str]:
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
@router.get('/keys')
|
|
86
|
-
|
|
86
|
+
def list_ssh_keys() -> List[str]:
|
|
87
87
|
"""List available SSH keys."""
|
|
88
88
|
try:
|
|
89
89
|
return ssh_node_pools_core.list_ssh_keys()
|
|
@@ -200,7 +200,7 @@ async def down_ssh_node_pool_general(
|
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
@router.get('/{pool_name}/status')
|
|
203
|
-
|
|
203
|
+
def get_ssh_node_pool_status(pool_name: str) -> Dict[str, str]:
|
|
204
204
|
"""Get the status of a specific SSH Node Pool."""
|
|
205
205
|
try:
|
|
206
206
|
# Call ssh_status to check the context
|
sky/users/server.py
CHANGED
|
@@ -33,8 +33,12 @@ USER_LOCK_TIMEOUT_SECONDS = 20
|
|
|
33
33
|
router = fastapi.APIRouter()
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
# All handlers in user handler are sync to get fastAPI run it in a
|
|
37
|
+
# ThreadPoolExecutor to avoid blocking the async event loop.
|
|
38
|
+
# TODO(aylei): make these async once we have the global_user_state async
|
|
39
|
+
# support.
|
|
36
40
|
@router.get('')
|
|
37
|
-
|
|
41
|
+
def users() -> List[Dict[str, Any]]:
|
|
38
42
|
"""Gets all users."""
|
|
39
43
|
all_users = []
|
|
40
44
|
user_list = global_user_state.get_all_users()
|
|
@@ -54,7 +58,7 @@ async def users() -> List[Dict[str, Any]]:
|
|
|
54
58
|
|
|
55
59
|
|
|
56
60
|
@router.get('/role')
|
|
57
|
-
|
|
61
|
+
def get_current_user_role(request: fastapi.Request):
|
|
58
62
|
"""Get current user's role."""
|
|
59
63
|
# TODO(hailong): is there a reliable way to get the user
|
|
60
64
|
# hash for the request without 'X-Auth-Request-Email' header?
|
|
@@ -70,7 +74,7 @@ async def get_current_user_role(request: fastapi.Request):
|
|
|
70
74
|
|
|
71
75
|
|
|
72
76
|
@router.post('/create')
|
|
73
|
-
|
|
77
|
+
def user_create(user_create_body: payloads.UserCreateBody) -> None:
|
|
74
78
|
username = user_create_body.username
|
|
75
79
|
password = user_create_body.password
|
|
76
80
|
role = user_create_body.role
|
|
@@ -100,8 +104,8 @@ async def user_create(user_create_body: payloads.UserCreateBody) -> None:
|
|
|
100
104
|
|
|
101
105
|
|
|
102
106
|
@router.post('/update')
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
def user_update(request: fastapi.Request,
|
|
108
|
+
user_update_body: payloads.UserUpdateBody) -> None:
|
|
105
109
|
"""Updates the user role."""
|
|
106
110
|
user_id = user_update_body.user_id
|
|
107
111
|
role = user_update_body.role
|
|
@@ -181,14 +185,13 @@ def _delete_user(user_id: str) -> None:
|
|
|
181
185
|
|
|
182
186
|
|
|
183
187
|
@router.post('/delete')
|
|
184
|
-
|
|
188
|
+
def user_delete(user_delete_body: payloads.UserDeleteBody) -> None:
|
|
185
189
|
user_id = user_delete_body.user_id
|
|
186
190
|
_delete_user(user_id)
|
|
187
191
|
|
|
188
192
|
|
|
189
193
|
@router.post('/import')
|
|
190
|
-
|
|
191
|
-
user_import_body: payloads.UserImportBody) -> Dict[str, Any]:
|
|
194
|
+
def user_import(user_import_body: payloads.UserImportBody) -> Dict[str, Any]:
|
|
192
195
|
"""Import users from CSV content."""
|
|
193
196
|
csv_content = user_import_body.csv_content
|
|
194
197
|
|
|
@@ -305,7 +308,7 @@ async def user_import(
|
|
|
305
308
|
|
|
306
309
|
|
|
307
310
|
@router.get('/export')
|
|
308
|
-
|
|
311
|
+
def user_export() -> Dict[str, Any]:
|
|
309
312
|
"""Export all users as CSV content."""
|
|
310
313
|
try:
|
|
311
314
|
# Get all users
|
|
@@ -369,7 +372,7 @@ def _user_lock(user_id: str) -> Generator[None, None, None]:
|
|
|
369
372
|
|
|
370
373
|
|
|
371
374
|
@router.get('/service-account-tokens')
|
|
372
|
-
|
|
375
|
+
def get_service_account_tokens(
|
|
373
376
|
request: fastapi.Request) -> List[Dict[str, Any]]:
|
|
374
377
|
"""Get service account tokens. All users can see all tokens."""
|
|
375
378
|
auth_user = request.state.auth_user
|
|
@@ -420,7 +423,7 @@ def _generate_service_account_user_id() -> str:
|
|
|
420
423
|
|
|
421
424
|
|
|
422
425
|
@router.post('/service-account-tokens')
|
|
423
|
-
|
|
426
|
+
def create_service_account_token(
|
|
424
427
|
request: fastapi.Request,
|
|
425
428
|
token_body: payloads.ServiceAccountTokenCreateBody) -> Dict[str, Any]:
|
|
426
429
|
"""Create a new service account token."""
|
|
@@ -508,7 +511,7 @@ async def create_service_account_token(
|
|
|
508
511
|
|
|
509
512
|
|
|
510
513
|
@router.post('/service-account-tokens/delete')
|
|
511
|
-
|
|
514
|
+
def delete_service_account_token(
|
|
512
515
|
request: fastapi.Request,
|
|
513
516
|
token_body: payloads.ServiceAccountTokenDeleteBody) -> Dict[str, str]:
|
|
514
517
|
"""Delete a service account token.
|
|
@@ -549,7 +552,7 @@ async def delete_service_account_token(
|
|
|
549
552
|
|
|
550
553
|
|
|
551
554
|
@router.post('/service-account-tokens/get-role')
|
|
552
|
-
|
|
555
|
+
def get_service_account_role(
|
|
553
556
|
request: fastapi.Request,
|
|
554
557
|
role_body: payloads.ServiceAccountTokenRoleBody) -> Dict[str, Any]:
|
|
555
558
|
"""Get the role of a service account."""
|
|
@@ -585,7 +588,7 @@ async def get_service_account_role(
|
|
|
585
588
|
|
|
586
589
|
|
|
587
590
|
@router.post('/service-account-tokens/update-role')
|
|
588
|
-
|
|
591
|
+
def update_service_account_role(
|
|
589
592
|
request: fastapi.Request,
|
|
590
593
|
role_body: payloads.ServiceAccountTokenUpdateRoleBody
|
|
591
594
|
) -> Dict[str, str]:
|
|
@@ -628,7 +631,7 @@ async def update_service_account_role(
|
|
|
628
631
|
|
|
629
632
|
|
|
630
633
|
@router.post('/service-account-tokens/rotate')
|
|
631
|
-
|
|
634
|
+
def rotate_service_account_token(
|
|
632
635
|
request: fastapi.Request,
|
|
633
636
|
token_body: payloads.ServiceAccountTokenRotateBody) -> Dict[str, Any]:
|
|
634
637
|
"""Rotate a service account token.
|
sky/utils/db/db_utils.py
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import asyncio
|
|
3
3
|
import contextlib
|
|
4
4
|
import enum
|
|
5
|
+
import os
|
|
6
|
+
import pathlib
|
|
5
7
|
import sqlite3
|
|
6
8
|
import threading
|
|
7
9
|
import typing
|
|
8
|
-
from typing import Any, Callable, Iterable, Optional
|
|
10
|
+
from typing import Any, Callable, Dict, Iterable, Optional
|
|
9
11
|
|
|
10
12
|
import aiosqlite
|
|
11
13
|
import aiosqlite.context
|
|
@@ -13,6 +15,7 @@ import sqlalchemy
|
|
|
13
15
|
from sqlalchemy import exc as sqlalchemy_exc
|
|
14
16
|
|
|
15
17
|
from sky import sky_logging
|
|
18
|
+
from sky.skylet import constants
|
|
16
19
|
|
|
17
20
|
logger = sky_logging.init_logger(__name__)
|
|
18
21
|
if typing.TYPE_CHECKING:
|
|
@@ -346,3 +349,57 @@ class SQLiteConn(threading.local):
|
|
|
346
349
|
) -> Iterable[sqlite3.Row]:
|
|
347
350
|
conn = await self._get_async_conn()
|
|
348
351
|
return await conn.execute_fetchall(sql, parameters)
|
|
352
|
+
|
|
353
|
+
async def close(self):
|
|
354
|
+
if self._async_conn is not None:
|
|
355
|
+
await self._async_conn.close()
|
|
356
|
+
self.conn.close()
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
_max_connections = 0
|
|
360
|
+
_postgres_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
|
|
361
|
+
_sqlite_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
|
|
362
|
+
|
|
363
|
+
_db_creation_lock = threading.Lock()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def set_max_connections(max_connections: int):
|
|
367
|
+
global _max_connections
|
|
368
|
+
_max_connections = max_connections
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def get_max_connections():
|
|
372
|
+
return _max_connections
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def get_engine(db_name: str):
|
|
376
|
+
conn_string = None
|
|
377
|
+
if os.environ.get(constants.ENV_VAR_IS_SKYPILOT_SERVER) is not None:
|
|
378
|
+
conn_string = os.environ.get(constants.ENV_VAR_DB_CONNECTION_URI)
|
|
379
|
+
if conn_string:
|
|
380
|
+
with _db_creation_lock:
|
|
381
|
+
if conn_string not in _postgres_engine_cache:
|
|
382
|
+
if _max_connections == 0:
|
|
383
|
+
_postgres_engine_cache[conn_string] = (
|
|
384
|
+
sqlalchemy.create_engine(
|
|
385
|
+
conn_string, poolclass=sqlalchemy.pool.NullPool))
|
|
386
|
+
elif _max_connections == 1:
|
|
387
|
+
_postgres_engine_cache[conn_string] = (
|
|
388
|
+
sqlalchemy.create_engine(
|
|
389
|
+
conn_string, poolclass=sqlalchemy.pool.StaticPool))
|
|
390
|
+
else:
|
|
391
|
+
_postgres_engine_cache[conn_string] = (
|
|
392
|
+
sqlalchemy.create_engine(
|
|
393
|
+
conn_string,
|
|
394
|
+
poolclass=sqlalchemy.pool.QueuePool,
|
|
395
|
+
size=_max_connections,
|
|
396
|
+
max_overflow=0))
|
|
397
|
+
engine = _postgres_engine_cache[conn_string]
|
|
398
|
+
else:
|
|
399
|
+
db_path = os.path.expanduser(f'~/.sky/{db_name}.db')
|
|
400
|
+
pathlib.Path(db_path).parents[0].mkdir(parents=True, exist_ok=True)
|
|
401
|
+
if db_path not in _sqlite_engine_cache:
|
|
402
|
+
_sqlite_engine_cache[db_path] = sqlalchemy.create_engine(
|
|
403
|
+
'sqlite:///' + db_path)
|
|
404
|
+
engine = _sqlite_engine_cache[db_path]
|
|
405
|
+
return engine
|
sky/utils/db/migration_utils.py
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
-
import pathlib
|
|
7
|
-
import threading
|
|
8
|
-
from typing import Dict, Optional
|
|
9
6
|
|
|
10
7
|
from alembic import command as alembic_command
|
|
11
8
|
from alembic.config import Config
|
|
@@ -14,7 +11,6 @@ import filelock
|
|
|
14
11
|
import sqlalchemy
|
|
15
12
|
|
|
16
13
|
from sky import sky_logging
|
|
17
|
-
from sky.skylet import constants
|
|
18
14
|
|
|
19
15
|
logger = sky_logging.init_logger(__name__)
|
|
20
16
|
|
|
@@ -32,34 +28,6 @@ SERVE_DB_NAME = 'serve_db'
|
|
|
32
28
|
SERVE_VERSION = '001'
|
|
33
29
|
SERVE_LOCK_PATH = '~/.sky/locks/.serve_db.lock'
|
|
34
30
|
|
|
35
|
-
_postgres_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
|
|
36
|
-
_sqlite_engine_cache: Dict[str, sqlalchemy.engine.Engine] = {}
|
|
37
|
-
|
|
38
|
-
_db_creation_lock = threading.Lock()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def get_engine(db_name: str,
|
|
42
|
-
pg_pool_class: Optional[sqlalchemy.pool.Pool] = None):
|
|
43
|
-
conn_string = None
|
|
44
|
-
if os.environ.get(constants.ENV_VAR_IS_SKYPILOT_SERVER) is not None:
|
|
45
|
-
conn_string = os.environ.get(constants.ENV_VAR_DB_CONNECTION_URI)
|
|
46
|
-
if conn_string:
|
|
47
|
-
if pg_pool_class is None:
|
|
48
|
-
pg_pool_class = sqlalchemy.NullPool
|
|
49
|
-
with _db_creation_lock:
|
|
50
|
-
if conn_string not in _postgres_engine_cache:
|
|
51
|
-
_postgres_engine_cache[conn_string] = sqlalchemy.create_engine(
|
|
52
|
-
conn_string, poolclass=pg_pool_class)
|
|
53
|
-
engine = _postgres_engine_cache[conn_string]
|
|
54
|
-
else:
|
|
55
|
-
db_path = os.path.expanduser(f'~/.sky/{db_name}.db')
|
|
56
|
-
pathlib.Path(db_path).parents[0].mkdir(parents=True, exist_ok=True)
|
|
57
|
-
if db_path not in _sqlite_engine_cache:
|
|
58
|
-
_sqlite_engine_cache[db_path] = sqlalchemy.create_engine(
|
|
59
|
-
'sqlite:///' + db_path)
|
|
60
|
-
engine = _sqlite_engine_cache[db_path]
|
|
61
|
-
return engine
|
|
62
|
-
|
|
63
31
|
|
|
64
32
|
@contextlib.contextmanager
|
|
65
33
|
def db_lock(db_name: str):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skypilot-nightly
|
|
3
|
-
Version: 1.0.0.
|
|
3
|
+
Version: 1.0.0.dev20250904
|
|
4
4
|
Summary: SkyPilot: Run AI on Any Infra — Unified, Faster, Cheaper.
|
|
5
5
|
Author: SkyPilot Team
|
|
6
6
|
License: Apache 2.0
|
|
@@ -145,48 +145,48 @@ Requires-Dist: grpcio>=1.63.0; extra == "server"
|
|
|
145
145
|
Requires-Dist: protobuf<7.0.0,>=5.26.1; extra == "server"
|
|
146
146
|
Requires-Dist: aiosqlite; extra == "server"
|
|
147
147
|
Provides-Extra: all
|
|
148
|
-
Requires-Dist:
|
|
149
|
-
Requires-Dist:
|
|
150
|
-
Requires-Dist: vastai-sdk>=0.1.12; extra == "all"
|
|
151
|
-
Requires-Dist: aiohttp; extra == "all"
|
|
152
|
-
Requires-Dist: google-cloud-storage; extra == "all"
|
|
153
|
-
Requires-Dist: websockets; extra == "all"
|
|
154
|
-
Requires-Dist: azure-mgmt-compute>=33.0.0; extra == "all"
|
|
148
|
+
Requires-Dist: runpod>=1.6.1; extra == "all"
|
|
149
|
+
Requires-Dist: azure-common; extra == "all"
|
|
155
150
|
Requires-Dist: kubernetes!=32.0.0,>=20.0.0; extra == "all"
|
|
156
|
-
Requires-Dist:
|
|
157
|
-
Requires-Dist: azure-storage-blob>=12.23.1; extra == "all"
|
|
158
|
-
Requires-Dist: anyio; extra == "all"
|
|
159
|
-
Requires-Dist: boto3>=1.26.1; extra == "all"
|
|
160
|
-
Requires-Dist: google-api-python-client>=2.69.0; extra == "all"
|
|
161
|
-
Requires-Dist: pyopenssl<24.3.0,>=23.2.0; extra == "all"
|
|
162
|
-
Requires-Dist: cudo-compute>=0.1.10; extra == "all"
|
|
163
|
-
Requires-Dist: azure-mgmt-network>=27.0.0; extra == "all"
|
|
151
|
+
Requires-Dist: aiohttp; extra == "all"
|
|
164
152
|
Requires-Dist: pyvmomi==8.0.1.0.2; extra == "all"
|
|
165
|
-
Requires-Dist: passlib; extra == "all"
|
|
166
|
-
Requires-Dist: azure-identity>=1.19.0; extra == "all"
|
|
167
|
-
Requires-Dist: botocore>=1.29.10; extra == "all"
|
|
168
|
-
Requires-Dist: awscli>=1.27.10; extra == "all"
|
|
169
|
-
Requires-Dist: python-dateutil; extra == "all"
|
|
170
|
-
Requires-Dist: pyjwt; extra == "all"
|
|
171
153
|
Requires-Dist: oci; extra == "all"
|
|
172
|
-
Requires-Dist:
|
|
154
|
+
Requires-Dist: ray[default]!=2.6.0,>=2.2.0; extra == "all"
|
|
155
|
+
Requires-Dist: ibm-platform-services>=0.48.0; extra == "all"
|
|
156
|
+
Requires-Dist: pyjwt; extra == "all"
|
|
173
157
|
Requires-Dist: azure-cli>=2.65.0; extra == "all"
|
|
174
|
-
Requires-Dist:
|
|
175
|
-
Requires-Dist:
|
|
176
|
-
Requires-Dist: docker; extra == "all"
|
|
177
|
-
Requires-Dist: azure-common; extra == "all"
|
|
178
|
-
Requires-Dist: colorama<0.4.5; extra == "all"
|
|
179
|
-
Requires-Dist: msrestazure; extra == "all"
|
|
158
|
+
Requires-Dist: ibm-cos-sdk; extra == "all"
|
|
159
|
+
Requires-Dist: python-dateutil; extra == "all"
|
|
180
160
|
Requires-Dist: ibm-vpc; extra == "all"
|
|
181
|
-
Requires-Dist:
|
|
161
|
+
Requires-Dist: pyopenssl<24.3.0,>=23.2.0; extra == "all"
|
|
162
|
+
Requires-Dist: grpcio>=1.63.0; extra == "all"
|
|
163
|
+
Requires-Dist: boto3>=1.26.1; extra == "all"
|
|
164
|
+
Requires-Dist: colorama<0.4.5; extra == "all"
|
|
165
|
+
Requires-Dist: azure-storage-blob>=12.23.1; extra == "all"
|
|
182
166
|
Requires-Dist: azure-core>=1.24.0; extra == "all"
|
|
167
|
+
Requires-Dist: azure-mgmt-compute>=33.0.0; extra == "all"
|
|
168
|
+
Requires-Dist: anyio; extra == "all"
|
|
183
169
|
Requires-Dist: sqlalchemy_adapter; extra == "all"
|
|
170
|
+
Requires-Dist: pydo>=0.3.0; extra == "all"
|
|
171
|
+
Requires-Dist: azure-mgmt-network>=27.0.0; extra == "all"
|
|
172
|
+
Requires-Dist: casbin; extra == "all"
|
|
173
|
+
Requires-Dist: websockets; extra == "all"
|
|
174
|
+
Requires-Dist: aiosqlite; extra == "all"
|
|
175
|
+
Requires-Dist: botocore>=1.29.10; extra == "all"
|
|
184
176
|
Requires-Dist: azure-core>=1.31.0; extra == "all"
|
|
177
|
+
Requires-Dist: ibm-cloud-sdk-core; extra == "all"
|
|
178
|
+
Requires-Dist: vastai-sdk>=0.1.12; extra == "all"
|
|
179
|
+
Requires-Dist: docker; extra == "all"
|
|
185
180
|
Requires-Dist: nebius>=0.2.47; extra == "all"
|
|
186
|
-
Requires-Dist:
|
|
181
|
+
Requires-Dist: google-cloud-storage; extra == "all"
|
|
182
|
+
Requires-Dist: msgraph-sdk; extra == "all"
|
|
183
|
+
Requires-Dist: awscli>=1.27.10; extra == "all"
|
|
187
184
|
Requires-Dist: protobuf<7.0.0,>=5.26.1; extra == "all"
|
|
188
|
-
Requires-Dist:
|
|
189
|
-
Requires-Dist:
|
|
185
|
+
Requires-Dist: google-api-python-client>=2.69.0; extra == "all"
|
|
186
|
+
Requires-Dist: msrestazure; extra == "all"
|
|
187
|
+
Requires-Dist: cudo-compute>=0.1.10; extra == "all"
|
|
188
|
+
Requires-Dist: azure-identity>=1.19.0; extra == "all"
|
|
189
|
+
Requires-Dist: passlib; extra == "all"
|
|
190
190
|
Dynamic: author
|
|
191
191
|
Dynamic: classifier
|
|
192
192
|
Dynamic: description
|