skypilot-nightly 1.0.0.dev20250627__py3-none-any.whl → 1.0.0.dev20250630__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.
Files changed (173) hide show
  1. sky/__init__.py +2 -2
  2. sky/adaptors/kubernetes.py +14 -0
  3. sky/adaptors/nebius.py +2 -2
  4. sky/authentication.py +12 -5
  5. sky/backends/backend_utils.py +92 -26
  6. sky/check.py +5 -2
  7. sky/client/cli/command.py +39 -8
  8. sky/client/sdk.py +217 -167
  9. sky/client/service_account_auth.py +47 -0
  10. sky/clouds/aws.py +10 -4
  11. sky/clouds/azure.py +5 -2
  12. sky/clouds/cloud.py +5 -2
  13. sky/clouds/gcp.py +31 -18
  14. sky/clouds/kubernetes.py +54 -34
  15. sky/clouds/nebius.py +8 -2
  16. sky/clouds/ssh.py +5 -2
  17. sky/clouds/utils/aws_utils.py +10 -4
  18. sky/clouds/utils/gcp_utils.py +22 -7
  19. sky/clouds/utils/oci_utils.py +62 -14
  20. sky/dashboard/out/404.html +1 -1
  21. sky/dashboard/out/_next/static/NdypbqMxaYucRGfopkKXa/_buildManifest.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/1043-1b39779691bb4030.js +1 -0
  23. sky/dashboard/out/_next/static/chunks/{141-fa5a20cbf401b351.js → 1141-726e5a3f00b67185.js} +2 -2
  24. sky/dashboard/out/_next/static/chunks/1272-1ef0bf0237faccdb.js +1 -0
  25. sky/dashboard/out/_next/static/chunks/1664-d65361e92b85e786.js +1 -0
  26. sky/dashboard/out/_next/static/chunks/1691.44e378727a41f3b5.js +21 -0
  27. sky/dashboard/out/_next/static/chunks/1871-80dea41717729fa5.js +6 -0
  28. sky/dashboard/out/_next/static/chunks/2544.27f70672535675ed.js +1 -0
  29. sky/dashboard/out/_next/static/chunks/{875.52c962183328b3f2.js → 2875.c24c6d57dc82e436.js} +1 -1
  30. sky/dashboard/out/_next/static/chunks/3256.7257acd01b481bed.js +11 -0
  31. sky/dashboard/out/_next/static/chunks/3698-52ad1ca228faa776.js +1 -0
  32. sky/dashboard/out/_next/static/chunks/3785.b3cc2bc1d49d2c3c.js +1 -0
  33. sky/dashboard/out/_next/static/chunks/3937.d7f1c55d1916c7f2.js +1 -0
  34. sky/dashboard/out/_next/static/chunks/{947-6620842ef80ae879.js → 3947-b059261d6fa88a1f.js} +1 -1
  35. sky/dashboard/out/_next/static/chunks/{697.6460bf72e760addd.js → 4697.f5421144224da9fc.js} +1 -1
  36. sky/dashboard/out/_next/static/chunks/4725.4c849b1e05c8e9ad.js +1 -0
  37. sky/dashboard/out/_next/static/chunks/5230-df791914b54d91d9.js +1 -0
  38. sky/dashboard/out/_next/static/chunks/{491.b3d264269613fe09.js → 5491.918ffed0ba7a5294.js} +1 -1
  39. sky/dashboard/out/_next/static/chunks/5739-5ea3ffa10fc884f2.js +8 -0
  40. sky/dashboard/out/_next/static/chunks/616-162f3033ffcd3d31.js +39 -0
  41. sky/dashboard/out/_next/static/chunks/6601-fcfad0ddf92ec7ab.js +1 -0
  42. sky/dashboard/out/_next/static/chunks/6989-6ff4e45dfb49d11d.js +1 -0
  43. sky/dashboard/out/_next/static/chunks/6990-d0dc765474fa0eca.js +1 -0
  44. sky/dashboard/out/_next/static/chunks/8969-909d53833da080cb.js +1 -0
  45. sky/dashboard/out/_next/static/chunks/8982.a2e214068f30a857.js +1 -0
  46. sky/dashboard/out/_next/static/chunks/{25.76c246239df93d50.js → 9025.a7c44babfe56ce09.js} +2 -2
  47. sky/dashboard/out/_next/static/chunks/938-044ad21de8b4626b.js +1 -0
  48. sky/dashboard/out/_next/static/chunks/9470-21d059a1dfa03f61.js +1 -0
  49. sky/dashboard/out/_next/static/chunks/9984.739ae958a066298d.js +1 -0
  50. sky/dashboard/out/_next/static/chunks/fd9d1056-61f2257a9cd8b32b.js +1 -0
  51. sky/dashboard/out/_next/static/chunks/{framework-87d061ee6ed71b28.js → framework-efc06c2733009cd3.js} +1 -1
  52. sky/dashboard/out/_next/static/chunks/main-app-68c028b1bc5e1b72.js +1 -0
  53. sky/dashboard/out/_next/static/chunks/{main-e0e2335212e72357.js → main-c0a4f1ea606d48d2.js} +1 -1
  54. sky/dashboard/out/_next/static/chunks/pages/{_app-9a3ce3170d2edcec.js → _app-a37b06ddb64521fd.js} +2 -2
  55. sky/dashboard/out/_next/static/chunks/pages/_error-c72a1f77a3c0be1b.js +1 -0
  56. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-8135aba0712bda37.js +6 -0
  57. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-b8e1114e6d38218c.js +6 -0
  58. sky/dashboard/out/_next/static/chunks/pages/clusters-9744c271a1642f76.js +1 -0
  59. sky/dashboard/out/_next/static/chunks/pages/config-a2673b256b6d416f.js +1 -0
  60. sky/dashboard/out/_next/static/chunks/pages/index-927ddeebe57a8ac3.js +1 -0
  61. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-8b0809f59034d509.js +1 -0
  62. sky/dashboard/out/_next/static/chunks/pages/infra-ae9d2f705ce582c9.js +1 -0
  63. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-c4d5cfac7fbc0668.js +16 -0
  64. sky/dashboard/out/_next/static/chunks/pages/jobs-5bbdc71878f0a068.js +1 -0
  65. sky/dashboard/out/_next/static/chunks/pages/users-cd43fb3c122eedde.js +1 -0
  66. sky/dashboard/out/_next/static/chunks/pages/volumes-4ebf6484f7216387.js +1 -0
  67. sky/dashboard/out/_next/static/chunks/pages/workspace/new-5629d4e551dba1ee.js +1 -0
  68. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-7c0187f43757a548.js +1 -0
  69. sky/dashboard/out/_next/static/chunks/pages/workspaces-06bde99155fa6292.js +1 -0
  70. sky/dashboard/out/_next/static/chunks/webpack-d427db53e54de9ce.js +1 -0
  71. sky/dashboard/out/_next/static/css/0da6afe66176678a.css +3 -0
  72. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  73. sky/dashboard/out/clusters/[cluster].html +1 -1
  74. sky/dashboard/out/clusters.html +1 -1
  75. sky/dashboard/out/config.html +1 -1
  76. sky/dashboard/out/index.html +1 -1
  77. sky/dashboard/out/infra/[context].html +1 -1
  78. sky/dashboard/out/infra.html +1 -1
  79. sky/dashboard/out/jobs/[job].html +1 -1
  80. sky/dashboard/out/jobs.html +1 -1
  81. sky/dashboard/out/users.html +1 -1
  82. sky/dashboard/out/volumes.html +1 -1
  83. sky/dashboard/out/workspace/new.html +1 -1
  84. sky/dashboard/out/workspaces/[name].html +1 -1
  85. sky/dashboard/out/workspaces.html +1 -1
  86. sky/data/storage.py +8 -3
  87. sky/global_user_state.py +257 -9
  88. sky/jobs/client/sdk.py +20 -25
  89. sky/models.py +16 -0
  90. sky/optimizer.py +46 -0
  91. sky/provision/__init__.py +14 -6
  92. sky/provision/kubernetes/config.py +1 -1
  93. sky/provision/kubernetes/constants.py +9 -0
  94. sky/provision/kubernetes/instance.py +24 -18
  95. sky/provision/kubernetes/network.py +15 -9
  96. sky/provision/kubernetes/network_utils.py +42 -23
  97. sky/provision/kubernetes/utils.py +73 -35
  98. sky/provision/kubernetes/volume.py +77 -15
  99. sky/provision/nebius/utils.py +10 -4
  100. sky/resources.py +10 -4
  101. sky/serve/client/sdk.py +28 -34
  102. sky/server/common.py +51 -3
  103. sky/server/constants.py +3 -0
  104. sky/server/requests/executor.py +4 -0
  105. sky/server/requests/payloads.py +33 -0
  106. sky/server/requests/requests.py +19 -0
  107. sky/server/rest.py +6 -15
  108. sky/server/server.py +121 -6
  109. sky/skylet/constants.py +7 -0
  110. sky/skypilot_config.py +32 -4
  111. sky/task.py +12 -0
  112. sky/users/permission.py +29 -0
  113. sky/users/server.py +384 -5
  114. sky/users/token_service.py +196 -0
  115. sky/utils/common_utils.py +4 -5
  116. sky/utils/config_utils.py +41 -0
  117. sky/utils/controller_utils.py +5 -1
  118. sky/utils/log_utils.py +68 -0
  119. sky/utils/resource_checker.py +153 -0
  120. sky/utils/resources_utils.py +12 -4
  121. sky/utils/schemas.py +87 -60
  122. sky/utils/subprocess_utils.py +2 -6
  123. sky/volumes/server/core.py +103 -78
  124. sky/volumes/utils.py +22 -5
  125. sky/workspaces/core.py +9 -117
  126. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/METADATA +1 -1
  127. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/RECORD +133 -128
  128. sky/dashboard/out/_next/static/HudU4f4Xsy-cP51JvXSZ-/_buildManifest.js +0 -1
  129. sky/dashboard/out/_next/static/chunks/230-d6e363362017ff3a.js +0 -1
  130. sky/dashboard/out/_next/static/chunks/43-36177d00f6956ab2.js +0 -1
  131. sky/dashboard/out/_next/static/chunks/470-92dd1614396389be.js +0 -1
  132. sky/dashboard/out/_next/static/chunks/544.110e53813fb98e2e.js +0 -1
  133. sky/dashboard/out/_next/static/chunks/616-d6128fa9e7cae6e6.js +0 -39
  134. sky/dashboard/out/_next/static/chunks/645.961f08e39b8ce447.js +0 -1
  135. sky/dashboard/out/_next/static/chunks/664-047bc03493fda379.js +0 -1
  136. sky/dashboard/out/_next/static/chunks/690.55f9eed3be903f56.js +0 -16
  137. sky/dashboard/out/_next/static/chunks/785.dc2686c3c1235554.js +0 -1
  138. sky/dashboard/out/_next/static/chunks/798-c0525dc3f21e488d.js +0 -1
  139. sky/dashboard/out/_next/static/chunks/799-3625946b2ec2eb30.js +0 -8
  140. sky/dashboard/out/_next/static/chunks/871-3db673be3ee3750b.js +0 -6
  141. sky/dashboard/out/_next/static/chunks/937.3759f538f11a0953.js +0 -1
  142. sky/dashboard/out/_next/static/chunks/938-068520cc11738deb.js +0 -1
  143. sky/dashboard/out/_next/static/chunks/969-d3a0b53f728d280a.js +0 -1
  144. sky/dashboard/out/_next/static/chunks/973-81b2d057178adb76.js +0 -1
  145. sky/dashboard/out/_next/static/chunks/982.1b61658204416b0f.js +0 -1
  146. sky/dashboard/out/_next/static/chunks/984.e8bac186a24e5178.js +0 -1
  147. sky/dashboard/out/_next/static/chunks/989-db34c16ad7ea6155.js +0 -1
  148. sky/dashboard/out/_next/static/chunks/990-0ad5ea1699e03ee8.js +0 -1
  149. sky/dashboard/out/_next/static/chunks/fd9d1056-2821b0f0cabcd8bd.js +0 -1
  150. sky/dashboard/out/_next/static/chunks/main-app-241eb28595532291.js +0 -1
  151. sky/dashboard/out/_next/static/chunks/pages/_error-1be831200e60c5c0.js +0 -1
  152. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-aff040d7bc5d0086.js +0 -6
  153. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-8040f2483897ed0c.js +0 -6
  154. sky/dashboard/out/_next/static/chunks/pages/clusters-f119a5630a1efd61.js +0 -1
  155. sky/dashboard/out/_next/static/chunks/pages/config-6b255eae088da6a3.js +0 -1
  156. sky/dashboard/out/_next/static/chunks/pages/index-6b0d9e5031b70c58.js +0 -1
  157. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-b302aea4d65766bf.js +0 -1
  158. sky/dashboard/out/_next/static/chunks/pages/infra-ee8cc4d449945d19.js +0 -1
  159. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-e4b23128db0774cd.js +0 -16
  160. sky/dashboard/out/_next/static/chunks/pages/jobs-0a5695ff3075d94a.js +0 -1
  161. sky/dashboard/out/_next/static/chunks/pages/users-4978cbb093e141e7.js +0 -1
  162. sky/dashboard/out/_next/static/chunks/pages/volumes-476b670ef33d1ecd.js +0 -1
  163. sky/dashboard/out/_next/static/chunks/pages/workspace/new-5b59bce9eb208d84.js +0 -1
  164. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-cb7e720b739de53a.js +0 -1
  165. sky/dashboard/out/_next/static/chunks/pages/workspaces-50e230828730cfb3.js +0 -1
  166. sky/dashboard/out/_next/static/chunks/webpack-08fdb9e6070127fc.js +0 -1
  167. sky/dashboard/out/_next/static/css/52082cf558ec9705.css +0 -3
  168. /sky/dashboard/out/_next/static/{HudU4f4Xsy-cP51JvXSZ- → NdypbqMxaYucRGfopkKXa}/_ssgManifest.js +0 -0
  169. /sky/dashboard/out/_next/static/chunks/{804-4c9fc53aa74bc191.js → 804-9f5e98ce84d46bdd.js} +0 -0
  170. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/WHEEL +0 -0
  171. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/entry_points.txt +0 -0
  172. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/licenses/LICENSE +0 -0
  173. {skypilot_nightly-1.0.0.dev20250627.dist-info → skypilot_nightly-1.0.0.dev20250630.dist-info}/top_level.txt +0 -0
sky/utils/schemas.py CHANGED
@@ -7,6 +7,7 @@ import enum
7
7
  from typing import Any, Dict, List, Tuple
8
8
 
9
9
  from sky.skylet import constants
10
+ from sky.utils import kubernetes_enums
10
11
 
11
12
 
12
13
  def _check_not_both_fields_present(field1: str, field2: str):
@@ -1018,10 +1019,73 @@ _REMOTE_IDENTITY_SCHEMA_KUBERNETES = {
1018
1019
  },
1019
1020
  }
1020
1021
 
1022
+ _CONTEXT_CONFIG_SCHEMA_KUBERNETES = {
1023
+ 'networking': {
1024
+ 'type': 'string',
1025
+ 'case_insensitive_enum': [
1026
+ type.value for type in kubernetes_enums.KubernetesNetworkingMode
1027
+ ],
1028
+ },
1029
+ 'ports': {
1030
+ 'type': 'string',
1031
+ 'case_insensitive_enum': [
1032
+ type.value for type in kubernetes_enums.KubernetesPortMode
1033
+ ],
1034
+ },
1035
+ 'pod_config': {
1036
+ 'type': 'object',
1037
+ 'required': [],
1038
+ # Allow arbitrary keys since validating pod spec is hard
1039
+ 'additionalProperties': True,
1040
+ },
1041
+ 'custom_metadata': {
1042
+ 'type': 'object',
1043
+ 'required': [],
1044
+ # Allow arbitrary keys since validating metadata is hard
1045
+ 'additionalProperties': True,
1046
+ # Disallow 'name' and 'namespace' keys in this dict
1047
+ 'not': {
1048
+ 'anyOf': [{
1049
+ 'required': ['name']
1050
+ }, {
1051
+ 'required': ['namespace']
1052
+ }]
1053
+ },
1054
+ },
1055
+ 'provision_timeout': {
1056
+ 'type': 'integer',
1057
+ },
1058
+ 'autoscaler': {
1059
+ 'type': 'string',
1060
+ 'case_insensitive_enum': [
1061
+ type.value for type in kubernetes_enums.KubernetesAutoscalerType
1062
+ ],
1063
+ },
1064
+ 'high_availability': {
1065
+ 'type': 'object',
1066
+ 'required': [],
1067
+ 'additionalProperties': False,
1068
+ 'properties': {
1069
+ 'storage_class_name': {
1070
+ 'type': 'string',
1071
+ }
1072
+ },
1073
+ },
1074
+ 'kueue': {
1075
+ 'type': 'object',
1076
+ 'required': [],
1077
+ 'additionalProperties': False,
1078
+ 'properties': {
1079
+ 'local_queue_name': {
1080
+ 'type': 'string',
1081
+ },
1082
+ },
1083
+ },
1084
+ }
1085
+
1021
1086
 
1022
1087
  def get_config_schema():
1023
1088
  # pylint: disable=import-outside-toplevel
1024
- from sky.utils import kubernetes_enums
1025
1089
 
1026
1090
  resources_schema = {
1027
1091
  k: v
@@ -1178,70 +1242,21 @@ def get_config_schema():
1178
1242
  'type': 'string',
1179
1243
  },
1180
1244
  },
1181
- 'networking': {
1182
- 'type': 'string',
1183
- 'case_insensitive_enum': [
1184
- type.value
1185
- for type in kubernetes_enums.KubernetesNetworkingMode
1186
- ]
1187
- },
1188
- 'ports': {
1189
- 'type': 'string',
1190
- 'case_insensitive_enum': [
1191
- type.value
1192
- for type in kubernetes_enums.KubernetesPortMode
1193
- ]
1194
- },
1195
- 'pod_config': {
1245
+ 'context_configs': {
1196
1246
  'type': 'object',
1197
1247
  'required': [],
1198
- # Allow arbitrary keys since validating pod spec is hard
1199
- 'additionalProperties': True,
1200
- },
1201
- 'custom_metadata': {
1202
- 'type': 'object',
1203
- 'required': [],
1204
- # Allow arbitrary keys since validating metadata is hard
1205
- 'additionalProperties': True,
1206
- # Disallow 'name' and 'namespace' keys in this dict
1207
- 'not': {
1208
- 'anyOf': [{
1209
- 'required': ['name']
1210
- }, {
1211
- 'required': ['namespace']
1212
- }]
1213
- }
1214
- },
1215
- 'provision_timeout': {
1216
- 'type': 'integer',
1217
- },
1218
- 'autoscaler': {
1219
- 'type': 'string',
1220
- 'case_insensitive_enum': [
1221
- type.value
1222
- for type in kubernetes_enums.KubernetesAutoscalerType
1223
- ]
1224
- },
1225
- 'high_availability': {
1226
- 'type': 'object',
1227
- 'required': [],
1228
- 'additionalProperties': False,
1229
- 'properties': {
1230
- 'storage_class_name': {
1231
- 'type': 'string',
1232
- }
1233
- }
1234
- },
1235
- 'kueue': {
1236
- 'type': 'object',
1237
- 'required': [],
1238
- 'additionalProperties': False,
1239
- 'properties': {
1240
- 'local_queue_name': {
1241
- 'type': 'string',
1248
+ 'properties': {},
1249
+ # Properties are kubernetes context names.
1250
+ 'additionalProperties': {
1251
+ 'type': 'object',
1252
+ 'required': [],
1253
+ 'additionalProperties': False,
1254
+ 'properties': {
1255
+ **_CONTEXT_CONFIG_SCHEMA_KUBERNETES,
1242
1256
  },
1243
1257
  },
1244
1258
  },
1259
+ **_CONTEXT_CONFIG_SCHEMA_KUBERNETES,
1245
1260
  }
1246
1261
  },
1247
1262
  'ssh': {
@@ -1400,6 +1415,18 @@ def get_config_schema():
1400
1415
  # Apply validation for URL
1401
1416
  'pattern': r'^https?://.*$',
1402
1417
  },
1418
+ 'service_account_token': {
1419
+ 'anyOf': [
1420
+ {
1421
+ 'type': 'string',
1422
+ # Validate that token starts with sky_ prefix
1423
+ 'pattern': r'^sky_.+$',
1424
+ },
1425
+ {
1426
+ 'type': 'null',
1427
+ }
1428
+ ]
1429
+ },
1403
1430
  }
1404
1431
  }
1405
1432
 
@@ -6,6 +6,7 @@ import random
6
6
  import resource
7
7
  import shlex
8
8
  import subprocess
9
+ import sys
9
10
  import threading
10
11
  import time
11
12
  import typing
@@ -16,7 +17,6 @@ import colorama
16
17
  from sky import exceptions
17
18
  from sky import sky_logging
18
19
  from sky.adaptors import common as adaptors_common
19
- from sky.skylet import constants
20
20
  from sky.skylet import log_lib
21
21
  from sky.utils import common_utils
22
22
  from sky.utils import timeline
@@ -322,12 +322,8 @@ def kill_process_daemon(process_pid: int) -> None:
322
322
  daemon_script = os.path.join(
323
323
  os.path.dirname(os.path.abspath(log_lib.__file__)),
324
324
  'subprocess_daemon.py')
325
- python_path = subprocess.check_output(constants.SKY_GET_PYTHON_PATH_CMD,
326
- shell=True,
327
- stderr=subprocess.DEVNULL,
328
- encoding='utf-8').strip()
329
325
  daemon_cmd = [
330
- python_path,
326
+ sys.executable,
331
327
  daemon_script,
332
328
  '--parent-pid',
333
329
  str(parent_pid),
@@ -13,7 +13,9 @@ from sky import models
13
13
  from sky import provision
14
14
  from sky import sky_logging
15
15
  from sky.utils import common_utils
16
+ from sky.utils import rich_utils
16
17
  from sky.utils import status_lib
18
+ from sky.utils import ux_utils
17
19
 
18
20
  logger = sky_logging.init_logger(__name__)
19
21
 
@@ -33,14 +35,14 @@ def volume_refresh():
33
35
  'Skipping status refresh...')
34
36
  continue
35
37
  cloud = config.cloud
36
- usedby = provision.get_volume_usedby(cloud, config)
38
+ usedby_pods, _ = provision.get_volume_usedby(cloud, config)
37
39
  with _volume_lock(volume_name):
38
40
  latest_volume = global_user_state.get_volume_by_name(volume_name)
39
41
  if latest_volume is None:
40
42
  logger.warning(f'Volume {volume_name} not found.')
41
43
  continue
42
44
  status = latest_volume.get('status')
43
- if not usedby:
45
+ if not usedby_pods:
44
46
  if status != status_lib.VolumeStatus.READY:
45
47
  logger.info(f'Update volume {volume_name} '
46
48
  f'status to READY')
@@ -49,7 +51,7 @@ def volume_refresh():
49
51
  else:
50
52
  if status != status_lib.VolumeStatus.IN_USE:
51
53
  logger.info(f'Update volume {volume_name} '
52
- f'status to IN_USE, usedby: {usedby}')
54
+ f'status to IN_USE, usedby: {usedby_pods}')
53
55
  global_user_state.update_volume_status(
54
56
  volume_name, status=status_lib.VolumeStatus.IN_USE)
55
57
 
@@ -74,42 +76,52 @@ def volume_list() -> List[Dict[str, Any]]:
74
76
  'last_attached_at': int timestamp of last attachment,
75
77
  'last_use': last command,
76
78
  'status': sky.VolumeStatus,
79
+ 'usedby_pods': List[str],
80
+ 'usedby_clusters': List[str],
77
81
  }
78
82
  ]
79
83
  """
80
- volumes = global_user_state.get_volumes()
81
- all_users = global_user_state.get_all_users()
82
- user_map = {user.id: user.name for user in all_users}
83
- records = []
84
- for volume in volumes:
85
- volume_name = volume.get('name')
86
- record = {
87
- 'name': volume_name,
88
- 'launched_at': volume.get('launched_at'),
89
- 'user_hash': volume.get('user_hash'),
90
- 'user_name': user_map.get(volume.get('user_hash'), ''),
91
- 'workspace': volume.get('workspace'),
92
- 'last_attached_at': volume.get('last_attached_at'),
93
- 'last_use': volume.get('last_use'),
94
- }
95
- status = volume.get('status')
96
- if status is not None:
97
- record['status'] = status.value
98
- else:
99
- record['status'] = ''
100
- config = volume.get('handle')
101
- if config is None:
102
- logger.warning(f'Volume {volume_name} has no handle.')
103
- continue
104
- record['type'] = config.type
105
- record['cloud'] = config.cloud
106
- record['region'] = config.region
107
- record['zone'] = config.zone
108
- record['size'] = config.size
109
- record['config'] = config.config
110
- record['name_on_cloud'] = config.name_on_cloud
111
- records.append(record)
112
- return records
84
+ with rich_utils.safe_status(ux_utils.spinner_message('Listing volumes')):
85
+ volumes = global_user_state.get_volumes()
86
+ all_users = global_user_state.get_all_users()
87
+ user_map = {user.id: user.name for user in all_users}
88
+ records = []
89
+ for volume in volumes:
90
+ volume_name = volume.get('name')
91
+ record = {
92
+ 'name': volume_name,
93
+ 'launched_at': volume.get('launched_at'),
94
+ 'user_hash': volume.get('user_hash'),
95
+ 'user_name': user_map.get(volume.get('user_hash'), ''),
96
+ 'workspace': volume.get('workspace'),
97
+ 'last_attached_at': volume.get('last_attached_at'),
98
+ 'last_use': volume.get('last_use'),
99
+ 'usedby_pods': [],
100
+ 'usedby_clusters': [],
101
+ }
102
+ status = volume.get('status')
103
+ if status is not None:
104
+ record['status'] = status.value
105
+ else:
106
+ record['status'] = ''
107
+ config = volume.get('handle')
108
+ if config is None:
109
+ logger.warning(f'Volume {volume_name} has no handle.')
110
+ continue
111
+ cloud = config.cloud
112
+ usedby_pods, usedby_clusters = provision.get_volume_usedby(
113
+ cloud, config)
114
+ record['type'] = config.type
115
+ record['cloud'] = config.cloud
116
+ record['region'] = config.region
117
+ record['zone'] = config.zone
118
+ record['size'] = config.size
119
+ record['config'] = config.config
120
+ record['name_on_cloud'] = config.name_on_cloud
121
+ record['usedby_pods'] = usedby_pods
122
+ record['usedby_clusters'] = usedby_clusters
123
+ records.append(record)
124
+ return records
113
125
 
114
126
 
115
127
  def volume_delete(names: List[str]) -> None:
@@ -122,20 +134,32 @@ def volume_delete(names: List[str]) -> None:
122
134
  ValueError: If the volume does not exist
123
135
  or is in use or has no handle.
124
136
  """
125
- for name in names:
126
- volume = global_user_state.get_volume_by_name(name)
127
- if volume is None:
128
- raise ValueError(f'Volume {name} not found.')
129
- if volume.get('status') == status_lib.VolumeStatus.IN_USE:
130
- raise ValueError(f'Volume {name} is in use.')
131
- config = volume.get('handle')
132
- if config is None:
133
- raise ValueError(f'Volume {name} has no handle.')
134
- logger.debug(f'Deleting volume {name} with config {config}')
135
- cloud = config.cloud
136
- with _volume_lock(name):
137
- provision.delete_volume(cloud, config)
138
- global_user_state.delete_volume(name)
137
+ with rich_utils.safe_status(ux_utils.spinner_message('Deleting volumes')):
138
+ for name in names:
139
+ volume = global_user_state.get_volume_by_name(name)
140
+ if volume is None:
141
+ raise ValueError(f'Volume {name} not found.')
142
+ config = volume.get('handle')
143
+ if config is None:
144
+ raise ValueError(f'Volume {name} has no handle.')
145
+ cloud = config.cloud
146
+ usedby_pods, usedby_clusters = provision.get_volume_usedby(
147
+ cloud, config)
148
+ if usedby_clusters:
149
+ usedby_clusters_str = ', '.join(usedby_clusters)
150
+ cluster_str = 'clusters' if len(
151
+ usedby_clusters) > 1 else 'cluster'
152
+ raise ValueError(f'Volume {name} is used by {cluster_str}'
153
+ f' {usedby_clusters_str}.')
154
+ if usedby_pods:
155
+ usedby_pods_str = ', '.join(usedby_pods)
156
+ pod_str = 'pods' if len(usedby_pods) > 1 else 'pod'
157
+ raise ValueError(
158
+ f'Volume {name} is used by {pod_str} {usedby_pods_str}.')
159
+ logger.debug(f'Deleting volume {name} with config {config}')
160
+ with _volume_lock(name):
161
+ provision.delete_volume(cloud, config)
162
+ global_user_state.delete_volume(name)
139
163
 
140
164
 
141
165
  def volume_apply(name: str, volume_type: str, cloud: str, region: Optional[str],
@@ -153,34 +177,35 @@ def volume_apply(name: str, volume_type: str, cloud: str, region: Optional[str],
153
177
  config: The configuration of the volume.
154
178
 
155
179
  """
156
- # Reuse the method for cluster name on cloud to
157
- # generate the storage name on cloud.
158
- cloud_obj = sky.CLOUD_REGISTRY.from_str(cloud)
159
- assert cloud_obj is not None
160
- name_uuid = str(uuid.uuid4())[:6]
161
- name_on_cloud = common_utils.make_cluster_name_on_cloud(
162
- name, max_length=cloud_obj.max_cluster_name_length())
163
- name_on_cloud += '-' + name_uuid
164
- config = models.VolumeConfig(
165
- name=name,
166
- type=volume_type,
167
- cloud=str(cloud_obj),
168
- region=region,
169
- zone=zone,
170
- size=size,
171
- config=config,
172
- name_on_cloud=name_on_cloud,
173
- )
174
- logger.debug(
175
- f'Creating volume {name} on cloud {cloud} with config {config}')
176
- with _volume_lock(name):
177
- current_volume = global_user_state.get_volume_by_name(name)
178
- if current_volume is not None:
179
- logger.info(f'Volume {name} already exists.')
180
- return
181
- config = provision.apply_volume(cloud, config)
182
- global_user_state.add_volume(name, config,
183
- status_lib.VolumeStatus.READY)
180
+ with rich_utils.safe_status(ux_utils.spinner_message('Creating volume')):
181
+ # Reuse the method for cluster name on cloud to
182
+ # generate the storage name on cloud.
183
+ cloud_obj = sky.CLOUD_REGISTRY.from_str(cloud)
184
+ assert cloud_obj is not None
185
+ name_uuid = str(uuid.uuid4())[:6]
186
+ name_on_cloud = common_utils.make_cluster_name_on_cloud(
187
+ name, max_length=cloud_obj.max_cluster_name_length())
188
+ name_on_cloud += '-' + name_uuid
189
+ config = models.VolumeConfig(
190
+ name=name,
191
+ type=volume_type,
192
+ cloud=str(cloud_obj),
193
+ region=region,
194
+ zone=zone,
195
+ size=size,
196
+ config=config,
197
+ name_on_cloud=name_on_cloud,
198
+ )
199
+ logger.debug(
200
+ f'Creating volume {name} on cloud {cloud} with config {config}')
201
+ with _volume_lock(name):
202
+ current_volume = global_user_state.get_volume_by_name(name)
203
+ if current_volume is not None:
204
+ logger.info(f'Volume {name} already exists.')
205
+ return
206
+ config = provision.apply_volume(cloud, config)
207
+ global_user_state.add_volume(name, config,
208
+ status_lib.VolumeStatus.READY)
184
209
 
185
210
 
186
211
  @contextlib.contextmanager
sky/volumes/utils.py CHANGED
@@ -6,6 +6,8 @@ from typing import Any, Dict, List, Optional
6
6
  import prettytable
7
7
 
8
8
  from sky import sky_logging
9
+ from sky.skylet import constants
10
+ from sky.utils import common_utils
9
11
  from sky.utils import log_utils
10
12
  from sky.volumes import volume
11
13
 
@@ -44,12 +46,12 @@ class PVCVolumeTable(VolumeTable):
44
46
 
45
47
  def _create_table(self, show_all: bool = False) -> prettytable.PrettyTable:
46
48
  """Create the PVC volume table."""
47
- # If show_all is True, show the table with the columns:
48
- # NAME, TYPE, INFRA, SIZE, USER, WORKSPACE,
49
- # AGE, LAST_USE, STATUS
50
49
  # If show_all is False, show the table with the columns:
51
50
  # NAME, TYPE, INFRA, SIZE, USER, WORKSPACE,
52
- # AGE, LAST_USE, STATUS, NAME_ON_CLOUD,
51
+ # AGE, STATUS, LAST_USE, USED_BY
52
+ # If show_all is True, show the table with the columns:
53
+ # NAME, TYPE, INFRA, SIZE, USER, WORKSPACE,
54
+ # AGE, STATUS, LAST_USE, USED_BY, NAME_ON_CLOUD
53
55
  # STORAGE_CLASS, ACCESS_MODE
54
56
 
55
57
  if show_all:
@@ -63,6 +65,7 @@ class PVCVolumeTable(VolumeTable):
63
65
  'AGE',
64
66
  'STATUS',
65
67
  'LAST_USE',
68
+ 'USED_BY',
66
69
  'NAME_ON_CLOUD',
67
70
  'STORAGE_CLASS',
68
71
  'ACCESS_MODE',
@@ -78,6 +81,7 @@ class PVCVolumeTable(VolumeTable):
78
81
  'AGE',
79
82
  'STATUS',
80
83
  'LAST_USE',
84
+ 'USED_BY',
81
85
  ]
82
86
 
83
87
  table = log_utils.create_table(columns)
@@ -98,6 +102,18 @@ class PVCVolumeTable(VolumeTable):
98
102
  size = row.get('size', '')
99
103
  if size:
100
104
  size = f'{size}Gi'
105
+ usedby_str = '-'
106
+ usedby_clusters = row.get('usedby_clusters')
107
+ usedby_pods = row.get('usedby_pods')
108
+ if usedby_clusters:
109
+ usedby_str = f'{", ".join(usedby_clusters)}'
110
+ elif usedby_pods:
111
+ usedby_str = f'{", ".join(usedby_pods)}'
112
+ if show_all:
113
+ usedby = usedby_str
114
+ else:
115
+ usedby = common_utils.truncate_long_string(
116
+ usedby_str, constants.USED_BY_TRUNC_LENGTH)
101
117
  infra = _get_infra_str(row.get('cloud'), row.get('region'),
102
118
  row.get('zone'))
103
119
  table_row = [
@@ -107,9 +123,10 @@ class PVCVolumeTable(VolumeTable):
107
123
  size,
108
124
  row.get('user_name', '-'),
109
125
  row.get('workspace', '-'),
110
- log_utils.readable_time_duration(row.get('launched_at', 0)),
126
+ log_utils.human_duration(row.get('launched_at', 0)),
111
127
  row.get('status', ''),
112
128
  last_attached_at_str,
129
+ usedby,
113
130
  ]
114
131
  if show_all:
115
132
  table_row.append(row.get('name_on_cloud', ''))