skypilot-nightly 1.0.0.dev20250912__py3-none-any.whl → 1.0.0.dev20250913__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.

Files changed (72) hide show
  1. sky/__init__.py +4 -2
  2. sky/adaptors/seeweb.py +103 -0
  3. sky/authentication.py +38 -0
  4. sky/backends/backend_utils.py +24 -9
  5. sky/backends/cloud_vm_ray_backend.py +382 -151
  6. sky/catalog/data_fetchers/fetch_aws.py +0 -36
  7. sky/catalog/data_fetchers/fetch_seeweb.py +329 -0
  8. sky/catalog/seeweb_catalog.py +184 -0
  9. sky/clouds/__init__.py +2 -0
  10. sky/clouds/kubernetes.py +2 -0
  11. sky/clouds/seeweb.py +463 -0
  12. sky/core.py +46 -12
  13. sky/dashboard/out/404.html +1 -1
  14. sky/dashboard/out/_next/static/{DAiq7V2xJnO1LSfmunZl6 → Y0Q7LyrxiFoWWbTdwb5nh}/_buildManifest.js +1 -1
  15. sky/dashboard/out/_next/static/chunks/1141-159df2d4c441a9d1.js +1 -0
  16. sky/dashboard/out/_next/static/chunks/3015-2ea98b57e318bd6e.js +1 -0
  17. sky/dashboard/out/_next/static/chunks/3294.03e02ae73455f48e.js +6 -0
  18. sky/dashboard/out/_next/static/chunks/3785.0fa442e16dd3f00e.js +1 -0
  19. sky/dashboard/out/_next/static/chunks/5339.c033b29835da0f35.js +51 -0
  20. sky/dashboard/out/_next/static/chunks/6856-e0754534b3015377.js +1 -0
  21. sky/dashboard/out/_next/static/chunks/6990-11c8e9b982e8ffec.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/9037-f9800e64eb05dd1c.js +6 -0
  23. sky/dashboard/out/_next/static/chunks/{webpack-e8a0c4c3c6f408fb.js → webpack-d1e29b3aa66bf4cf.js} +1 -1
  24. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  25. sky/dashboard/out/clusters/[cluster].html +1 -1
  26. sky/dashboard/out/clusters.html +1 -1
  27. sky/dashboard/out/config.html +1 -1
  28. sky/dashboard/out/index.html +1 -1
  29. sky/dashboard/out/infra/[context].html +1 -1
  30. sky/dashboard/out/infra.html +1 -1
  31. sky/dashboard/out/jobs/[job].html +1 -1
  32. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  33. sky/dashboard/out/jobs.html +1 -1
  34. sky/dashboard/out/users.html +1 -1
  35. sky/dashboard/out/volumes.html +1 -1
  36. sky/dashboard/out/workspace/new.html +1 -1
  37. sky/dashboard/out/workspaces/[name].html +1 -1
  38. sky/dashboard/out/workspaces.html +1 -1
  39. sky/exceptions.py +5 -0
  40. sky/global_user_state.py +41 -26
  41. sky/jobs/utils.py +61 -13
  42. sky/provision/__init__.py +1 -0
  43. sky/provision/kubernetes/utils.py +14 -3
  44. sky/provision/seeweb/__init__.py +11 -0
  45. sky/provision/seeweb/config.py +13 -0
  46. sky/provision/seeweb/instance.py +806 -0
  47. sky/schemas/generated/jobsv1_pb2.py +86 -0
  48. sky/schemas/generated/jobsv1_pb2.pyi +252 -0
  49. sky/schemas/generated/jobsv1_pb2_grpc.py +542 -0
  50. sky/setup_files/dependencies.py +8 -1
  51. sky/skylet/constants.py +2 -1
  52. sky/skylet/job_lib.py +128 -10
  53. sky/skylet/log_lib.py +3 -3
  54. sky/skylet/services.py +203 -0
  55. sky/skylet/skylet.py +4 -0
  56. sky/templates/seeweb-ray.yml.j2 +108 -0
  57. sky/utils/controller_utils.py +11 -5
  58. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/METADATA +39 -34
  59. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/RECORD +64 -53
  60. sky/dashboard/out/_next/static/chunks/1141-943efc7aff0f0c06.js +0 -1
  61. sky/dashboard/out/_next/static/chunks/3015-86cabed5d4669ad0.js +0 -1
  62. sky/dashboard/out/_next/static/chunks/3294.ba6586f9755b0edb.js +0 -6
  63. sky/dashboard/out/_next/static/chunks/3785.4872a2f3aa489880.js +0 -1
  64. sky/dashboard/out/_next/static/chunks/5339.3fda4a4010ff4e06.js +0 -51
  65. sky/dashboard/out/_next/static/chunks/6856-6e2bc8a6fd0867af.js +0 -1
  66. sky/dashboard/out/_next/static/chunks/6990-08b2a1cae076a943.js +0 -1
  67. sky/dashboard/out/_next/static/chunks/9037-fa1737818d0a0969.js +0 -6
  68. /sky/dashboard/out/_next/static/{DAiq7V2xJnO1LSfmunZl6 → Y0Q7LyrxiFoWWbTdwb5nh}/_ssgManifest.js +0 -0
  69. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/WHEEL +0 -0
  70. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/entry_points.txt +0 -0
  71. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/licenses/LICENSE +0 -0
  72. {skypilot_nightly-1.0.0.dev20250912.dist-info → skypilot_nightly-1.0.0.dev20250913.dist-info}/top_level.txt +0 -0
sky/__init__.py CHANGED
@@ -7,7 +7,7 @@ import urllib.request
7
7
  from sky.utils import directory_utils
8
8
 
9
9
  # Replaced with the current commit when building the wheels.
10
- _SKYPILOT_COMMIT_SHA = '16e9ad33e647ab3859a2b5624be7386721c9ef8b'
10
+ _SKYPILOT_COMMIT_SHA = 'bf9b3c4e09e97cf2dafed9f351d0b36438adf4ec'
11
11
 
12
12
 
13
13
  def _get_git_commit():
@@ -37,7 +37,7 @@ def _get_git_commit():
37
37
 
38
38
 
39
39
  __commit__ = _get_git_commit()
40
- __version__ = '1.0.0.dev20250912'
40
+ __version__ = '1.0.0.dev20250913'
41
41
  __root_dir__ = directory_utils.get_sky_dir()
42
42
 
43
43
 
@@ -149,6 +149,7 @@ Vsphere = clouds.Vsphere
149
149
  Fluidstack = clouds.Fluidstack
150
150
  Nebius = clouds.Nebius
151
151
  Hyperbolic = clouds.Hyperbolic
152
+ Seeweb = clouds.Seeweb
152
153
 
153
154
  __all__ = [
154
155
  '__version__',
@@ -169,6 +170,7 @@ __all__ = [
169
170
  'Fluidstack',
170
171
  'Nebius',
171
172
  'Hyperbolic',
173
+ 'Seeweb',
172
174
  'Optimizer',
173
175
  'OptimizeTarget',
174
176
  'backends',
sky/adaptors/seeweb.py ADDED
@@ -0,0 +1,103 @@
1
+ """ Seeweb Adaptor """
2
+ import configparser
3
+ from pathlib import Path
4
+
5
+ from sky.adaptors import common
6
+ from sky.utils import annotations
7
+
8
+
9
+ class SeewebError(Exception):
10
+ """Base exception for Seeweb adaptor errors."""
11
+
12
+
13
+ class SeewebCredentialsFileNotFound(SeewebError):
14
+ """Raised when the Seeweb credentials file is missing."""
15
+
16
+
17
+ class SeewebApiKeyMissing(SeewebError):
18
+ """Raised when the Seeweb API key is missing or empty."""
19
+
20
+
21
+ class SeewebAuthenticationError(SeewebError):
22
+ """Raised when authenticating with Seeweb API fails."""
23
+
24
+
25
+ _IMPORT_ERROR_MESSAGE = ('Failed to import dependencies for Seeweb.'
26
+ 'Try pip install "skypilot[seeweb]"')
27
+
28
+ ecsapi = common.LazyImport(
29
+ 'ecsapi',
30
+ import_error_message=_IMPORT_ERROR_MESSAGE,
31
+ )
32
+ boto3 = common.LazyImport('boto3', import_error_message=_IMPORT_ERROR_MESSAGE)
33
+ botocore = common.LazyImport('botocore',
34
+ import_error_message=_IMPORT_ERROR_MESSAGE)
35
+
36
+ _LAZY_MODULES = (ecsapi, boto3, botocore)
37
+
38
+
39
+ @common.load_lazy_modules(_LAZY_MODULES)
40
+ def check_compute_credentials() -> bool:
41
+ """Checks if the user has access credentials to Seeweb's compute service.
42
+
43
+ Returns True if credentials are valid; otherwise raises a SeewebError.
44
+ """
45
+ # Read API key from standard Seeweb configuration file
46
+ key_path = Path('~/.seeweb_cloud/seeweb_keys').expanduser()
47
+ if not key_path.exists():
48
+ raise SeewebCredentialsFileNotFound(
49
+ 'Missing Seeweb API key file ~/.seeweb_cloud/seeweb_keys')
50
+
51
+ parser = configparser.ConfigParser()
52
+ parser.read(key_path)
53
+ try:
54
+ api_key = parser['DEFAULT']['api_key'].strip()
55
+ except KeyError as e:
56
+ raise SeewebApiKeyMissing(
57
+ 'Missing api_key in ~/.seeweb_cloud/seeweb_keys') from e
58
+ if not api_key:
59
+ raise SeewebApiKeyMissing(
60
+ 'Empty api_key in ~/.seeweb_cloud/seeweb_keys')
61
+
62
+ # Test connection by fetching servers list to validate the key
63
+ try:
64
+ seeweb_client = ecsapi.Api(token=api_key)
65
+ seeweb_client.fetch_servers()
66
+ except Exception as e: # pylint: disable=broad-except
67
+ raise SeewebAuthenticationError(
68
+ f'Unable to authenticate with Seeweb API: {e}') from e
69
+
70
+ return True
71
+
72
+
73
+ @common.load_lazy_modules(_LAZY_MODULES)
74
+ def check_storage_credentials() -> bool:
75
+ """Checks if the user has access credentials to Seeweb's storage service.
76
+
77
+ Mirrors compute credentials validation.
78
+ """
79
+ return check_compute_credentials()
80
+
81
+
82
+ @common.load_lazy_modules(_LAZY_MODULES)
83
+ @annotations.lru_cache(scope='global', maxsize=1)
84
+ def client():
85
+ """Returns an authenticated ecsapi.Api object."""
86
+ # Create authenticated client using the same credential pattern
87
+ key_path = Path('~/.seeweb_cloud/seeweb_keys').expanduser()
88
+ if not key_path.exists():
89
+ raise SeewebCredentialsFileNotFound(
90
+ 'Missing Seeweb API key file ~/.seeweb_cloud/seeweb_keys')
91
+
92
+ parser = configparser.ConfigParser()
93
+ parser.read(key_path)
94
+ try:
95
+ api_key = parser['DEFAULT']['api_key'].strip()
96
+ except KeyError as e:
97
+ raise SeewebApiKeyMissing(
98
+ 'Missing api_key in ~/.seeweb_cloud/seeweb_keys') from e
99
+ if not api_key:
100
+ raise SeewebApiKeyMissing(
101
+ 'Empty api_key in ~/.seeweb_cloud/seeweb_keys')
102
+
103
+ return ecsapi.Api(token=api_key)
sky/authentication.py CHANGED
@@ -40,6 +40,7 @@ from sky.adaptors import gcp
40
40
  from sky.adaptors import ibm
41
41
  from sky.adaptors import kubernetes
42
42
  from sky.adaptors import runpod
43
+ from sky.adaptors import seeweb as seeweb_adaptor
43
44
  from sky.adaptors import vast
44
45
  from sky.provision.fluidstack import fluidstack_utils
45
46
  from sky.provision.kubernetes import utils as kubernetes_utils
@@ -601,3 +602,40 @@ def setup_hyperbolic_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
601
602
  config['auth']['ssh_public_key'] = public_key_path
602
603
 
603
604
  return configure_ssh_info(config)
605
+
606
+
607
+ def setup_seeweb_authentication(config: Dict[str, Any]) -> Dict[str, Any]:
608
+ """Registers the public key with Seeweb and notes the remote name."""
609
+ # 1. local key pair
610
+ get_or_generate_keys()
611
+
612
+ # 2. public key
613
+ _, public_key_path = get_or_generate_keys()
614
+ with open(public_key_path, 'r', encoding='utf-8') as f:
615
+ public_key = f.read().strip()
616
+
617
+ # 3. Seeweb API client
618
+ client = seeweb_adaptor.client()
619
+
620
+ # 4. Check if key is already registered
621
+ prefix = f'sky-key-{common_utils.get_user_hash()}'
622
+ remote_name = None
623
+ for k in client.fetch_ssh_keys():
624
+ if k.key.strip() == public_key:
625
+ remote_name = k.label # already present
626
+ break
627
+
628
+ # 5. doesn't exist, choose a unique name and create it
629
+ if remote_name is None:
630
+ suffix = 1
631
+ remote_name = prefix
632
+ existing_names = {k.label for k in client.fetch_ssh_keys()}
633
+ while remote_name in existing_names:
634
+ suffix += 1
635
+ remote_name = f'{prefix}-{suffix}'
636
+ client.create_ssh_key(label=remote_name, key=public_key)
637
+
638
+ # 6. Put the remote name in cluster-config (like for Lambda)
639
+ config['auth']['remote_key_name'] = remote_name
640
+
641
+ return config
@@ -1116,6 +1116,8 @@ def _add_auth_to_cluster_config(cloud: clouds.Cloud, tmp_yaml_path: str):
1116
1116
  config = auth.setup_fluidstack_authentication(config)
1117
1117
  elif isinstance(cloud, clouds.Hyperbolic):
1118
1118
  config = auth.setup_hyperbolic_authentication(config)
1119
+ elif isinstance(cloud, clouds.Seeweb):
1120
+ config = auth.setup_seeweb_authentication(config)
1119
1121
  else:
1120
1122
  assert False, cloud
1121
1123
  yaml_utils.dump_yaml(tmp_yaml_path, config)
@@ -2333,7 +2335,8 @@ def _update_cluster_status(cluster_name: str) -> Optional[Dict[str, Any]]:
2333
2335
  handle,
2334
2336
  requested_resources=None,
2335
2337
  ready=True,
2336
- is_launch=False)
2338
+ is_launch=False,
2339
+ update_only=True)
2337
2340
  return global_user_state.get_cluster_from_name(cluster_name)
2338
2341
 
2339
2342
  # All cases below are transitioning the cluster to non-UP states.
@@ -2543,7 +2546,8 @@ def _update_cluster_status(cluster_name: str) -> Optional[Dict[str, Any]]:
2543
2546
  handle,
2544
2547
  requested_resources=None,
2545
2548
  ready=False,
2546
- is_launch=False)
2549
+ is_launch=False,
2550
+ update_only=True)
2547
2551
  return global_user_state.get_cluster_from_name(cluster_name)
2548
2552
  # Now is_abnormal is False: either node_statuses is empty or all nodes are
2549
2553
  # STOPPED.
@@ -3719,13 +3723,24 @@ def invoke_skylet_with_retries(func: Callable[..., T]) -> T:
3719
3723
  return func()
3720
3724
  except grpc.RpcError as e:
3721
3725
  last_exception = e
3722
- if e.code() == grpc.StatusCode.INTERNAL:
3723
- with ux_utils.print_exception_no_traceback():
3724
- raise exceptions.SkyletInternalError(e.details())
3725
- elif e.code() == grpc.StatusCode.UNAVAILABLE:
3726
- time.sleep(backoff.current_backoff())
3727
- else:
3728
- raise e
3726
+ _handle_grpc_error(e, backoff.current_backoff())
3727
+
3729
3728
  raise RuntimeError(
3730
3729
  f'Failed to invoke Skylet after {max_attempts} attempts: {last_exception}'
3731
3730
  ) from last_exception
3731
+
3732
+
3733
+ def _handle_grpc_error(e: 'grpc.RpcError', current_backoff: float) -> None:
3734
+ if e.code() == grpc.StatusCode.INTERNAL:
3735
+ with ux_utils.print_exception_no_traceback():
3736
+ raise exceptions.SkyletInternalError(e.details())
3737
+ elif e.code() == grpc.StatusCode.UNAVAILABLE:
3738
+ time.sleep(current_backoff)
3739
+ elif e.code() == grpc.StatusCode.UNIMPLEMENTED:
3740
+ # Handle backwards compatibility: old server doesn't implement this RPC.
3741
+ # Let the caller fall back to legacy execution.
3742
+ raise exceptions.SkyletMethodNotImplementedError(
3743
+ f'gRPC method not implemented on server, falling back to legacy execution: {e.details()}'
3744
+ )
3745
+ else:
3746
+ raise e