skypilot-nightly 1.0.0.dev20241204__py3-none-any.whl → 1.0.0.dev20241206__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.
- sky/__init__.py +2 -2
- sky/backends/backend.py +42 -15
- sky/backends/backend_utils.py +138 -5
- sky/backends/cloud_vm_ray_backend.py +76 -18
- sky/backends/local_docker_backend.py +11 -7
- sky/clouds/service_catalog/common.py +2 -2
- sky/execution.py +27 -9
- sky/global_user_state.py +23 -10
- sky/templates/kubernetes-ray.yml.j2 +3 -1
- sky/utils/common_utils.py +19 -0
- {skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/METADATA +1 -1
- {skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/RECORD +16 -16
- {skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/top_level.txt +0 -0
sky/__init__.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
|
|
5
5
|
import urllib.request
|
6
6
|
|
7
7
|
# Replaced with the current commit when building the wheels.
|
8
|
-
_SKYPILOT_COMMIT_SHA = '
|
8
|
+
_SKYPILOT_COMMIT_SHA = '6e5083293f0d9a9d069d51274c57f0e59e47e5ce'
|
9
9
|
|
10
10
|
|
11
11
|
def _get_git_commit():
|
@@ -35,7 +35,7 @@ def _get_git_commit():
|
|
35
35
|
|
36
36
|
|
37
37
|
__commit__ = _get_git_commit()
|
38
|
-
__version__ = '1.0.0.
|
38
|
+
__version__ = '1.0.0.dev20241206'
|
39
39
|
__root_dir__ = os.path.dirname(os.path.abspath(__file__))
|
40
40
|
|
41
41
|
|
sky/backends/backend.py
CHANGED
@@ -45,20 +45,45 @@ class Backend(Generic[_ResourceHandleType]):
|
|
45
45
|
@timeline.event
|
46
46
|
@usage_lib.messages.usage.update_runtime('provision')
|
47
47
|
def provision(
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
self,
|
49
|
+
task: 'task_lib.Task',
|
50
|
+
to_provision: Optional['resources.Resources'],
|
51
|
+
dryrun: bool,
|
52
|
+
stream_logs: bool,
|
53
|
+
cluster_name: Optional[str] = None,
|
54
|
+
retry_until_up: bool = False,
|
55
|
+
skip_unnecessary_provisioning: bool = False,
|
56
|
+
) -> Optional[_ResourceHandleType]:
|
57
|
+
"""Provisions resources for the given task.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
task: The task to provision resources for.
|
61
|
+
to_provision: Resource config to provision. Should only be None if
|
62
|
+
cluster_name refers to an existing cluster, whose resources will
|
63
|
+
be used.
|
64
|
+
dryrun: If True, don't actually provision anything.
|
65
|
+
stream_logs: If True, stream additional logs to console.
|
66
|
+
cluster_name: Name of the cluster to provision. If None, a name will
|
67
|
+
be auto-generated. If the name refers to an existing cluster,
|
68
|
+
the existing cluster will be reused and re-provisioned.
|
69
|
+
retry_until_up: If True, retry provisioning until resources are
|
70
|
+
successfully launched.
|
71
|
+
skip_if_no_cluster_updates: If True, compare the cluster config to
|
72
|
+
the existing cluster_name's config. Skip provisioning if no
|
73
|
+
updates are needed for the existing cluster.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
A ResourceHandle object for the provisioned resources, or None if
|
77
|
+
dryrun is True.
|
78
|
+
"""
|
55
79
|
if cluster_name is None:
|
56
80
|
cluster_name = sky.backends.backend_utils.generate_cluster_name()
|
57
81
|
usage_lib.record_cluster_name_for_current_operation(cluster_name)
|
58
82
|
usage_lib.messages.usage.update_actual_task(task)
|
59
83
|
with rich_utils.safe_status(ux_utils.spinner_message('Launching')):
|
60
84
|
return self._provision(task, to_provision, dryrun, stream_logs,
|
61
|
-
cluster_name, retry_until_up
|
85
|
+
cluster_name, retry_until_up,
|
86
|
+
skip_unnecessary_provisioning)
|
62
87
|
|
63
88
|
@timeline.event
|
64
89
|
@usage_lib.messages.usage.update_runtime('sync_workdir')
|
@@ -126,13 +151,15 @@ class Backend(Generic[_ResourceHandleType]):
|
|
126
151
|
|
127
152
|
# --- Implementations of the APIs ---
|
128
153
|
def _provision(
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
154
|
+
self,
|
155
|
+
task: 'task_lib.Task',
|
156
|
+
to_provision: Optional['resources.Resources'],
|
157
|
+
dryrun: bool,
|
158
|
+
stream_logs: bool,
|
159
|
+
cluster_name: str,
|
160
|
+
retry_until_up: bool = False,
|
161
|
+
skip_unnecessary_provisioning: bool = False,
|
162
|
+
) -> Optional[_ResourceHandleType]:
|
136
163
|
raise NotImplementedError
|
137
164
|
|
138
165
|
def _sync_workdir(self, handle: _ResourceHandleType, workdir: Path) -> None:
|
sky/backends/backend_utils.py
CHANGED
@@ -3,6 +3,7 @@ from datetime import datetime
|
|
3
3
|
import enum
|
4
4
|
import fnmatch
|
5
5
|
import functools
|
6
|
+
import hashlib
|
6
7
|
import os
|
7
8
|
import pathlib
|
8
9
|
import pprint
|
@@ -644,11 +645,17 @@ def write_cluster_config(
|
|
644
645
|
keep_launch_fields_in_existing_config: bool = True) -> Dict[str, str]:
|
645
646
|
"""Fills in cluster configuration templates and writes them out.
|
646
647
|
|
647
|
-
Returns:
|
648
|
-
|
649
|
-
- 'ray'
|
650
|
-
- '
|
651
|
-
- '
|
648
|
+
Returns:
|
649
|
+
Dict with the following keys:
|
650
|
+
- 'ray': Path to the generated Ray yaml config file
|
651
|
+
- 'cluster_name': Name of the cluster
|
652
|
+
- 'cluster_name_on_cloud': Name of the cluster as it appears in the
|
653
|
+
cloud provider
|
654
|
+
- 'config_hash': Hash of the cluster config and file mounts contents.
|
655
|
+
Can be missing if we unexpectedly failed to calculate the hash for
|
656
|
+
some reason. In that case we will continue without the optimization to
|
657
|
+
skip provisioning.
|
658
|
+
|
652
659
|
Raises:
|
653
660
|
exceptions.ResourcesUnavailableError: if the region/zones requested does
|
654
661
|
not appear in the catalog, or an ssh_proxy_command is specified but
|
@@ -903,6 +910,12 @@ def write_cluster_config(
|
|
903
910
|
if dryrun:
|
904
911
|
# If dryrun, return the unfinished tmp yaml path.
|
905
912
|
config_dict['ray'] = tmp_yaml_path
|
913
|
+
try:
|
914
|
+
config_dict['config_hash'] = _deterministic_cluster_yaml_hash(
|
915
|
+
tmp_yaml_path)
|
916
|
+
except Exception as e: # pylint: disable=broad-except
|
917
|
+
logger.warning(f'Failed to calculate config_hash: {e}')
|
918
|
+
logger.debug('Full exception:', exc_info=e)
|
906
919
|
return config_dict
|
907
920
|
_add_auth_to_cluster_config(cloud, tmp_yaml_path)
|
908
921
|
|
@@ -925,6 +938,17 @@ def write_cluster_config(
|
|
925
938
|
yaml_config = common_utils.read_yaml(tmp_yaml_path)
|
926
939
|
config_dict['cluster_name_on_cloud'] = yaml_config['cluster_name']
|
927
940
|
|
941
|
+
# Make sure to do this before we optimize file mounts. Optimization is
|
942
|
+
# non-deterministic, but everything else before this point should be
|
943
|
+
# deterministic.
|
944
|
+
try:
|
945
|
+
config_dict['config_hash'] = _deterministic_cluster_yaml_hash(
|
946
|
+
tmp_yaml_path)
|
947
|
+
except Exception as e: # pylint: disable=broad-except
|
948
|
+
logger.warning('Failed to calculate config_hash: '
|
949
|
+
f'{common_utils.format_exception(e)}')
|
950
|
+
logger.debug('Full exception:', exc_info=e)
|
951
|
+
|
928
952
|
# Optimization: copy the contents of source files in file_mounts to a
|
929
953
|
# special dir, and upload that as the only file_mount instead. Delay
|
930
954
|
# calling this optimization until now, when all source files have been
|
@@ -1033,6 +1057,115 @@ def _count_healthy_nodes_from_ray(output: str,
|
|
1033
1057
|
return ready_head, ready_workers
|
1034
1058
|
|
1035
1059
|
|
1060
|
+
@timeline.event
|
1061
|
+
def _deterministic_cluster_yaml_hash(yaml_path: str) -> str:
|
1062
|
+
"""Hash the cluster yaml and contents of file mounts to a unique string.
|
1063
|
+
|
1064
|
+
Two invocations of this function should return the same string if and only
|
1065
|
+
if the contents of the yaml are the same and the file contents of all the
|
1066
|
+
file_mounts specified in the yaml are the same.
|
1067
|
+
|
1068
|
+
Limitations:
|
1069
|
+
- This function can be expensive if the file mounts are large. (E.g. a few
|
1070
|
+
seconds for ~1GB.) This should be okay since we expect that the
|
1071
|
+
file_mounts in the cluster yaml (the wheel and cloud credentials) will be
|
1072
|
+
small.
|
1073
|
+
- Symbolic links are not explicitly handled. Some symbolic link changes may
|
1074
|
+
not be detected.
|
1075
|
+
|
1076
|
+
Implementation: We create a byte sequence that captures the state of the
|
1077
|
+
yaml file and all the files in the file mounts, then hash the byte sequence.
|
1078
|
+
|
1079
|
+
The format of the byte sequence is:
|
1080
|
+
32 bytes - sha256 hash of the yaml file
|
1081
|
+
for each file mount:
|
1082
|
+
file mount remote destination (UTF-8), \0
|
1083
|
+
if the file mount source is a file:
|
1084
|
+
'file' encoded to UTF-8
|
1085
|
+
32 byte sha256 hash of the file contents
|
1086
|
+
if the file mount source is a directory:
|
1087
|
+
'dir' encoded to UTF-8
|
1088
|
+
for each directory and subdirectory withinin the file mount (starting from
|
1089
|
+
the root and descending recursively):
|
1090
|
+
name of the directory (UTF-8), \0
|
1091
|
+
name of each subdirectory within the directory (UTF-8) terminated by \0
|
1092
|
+
\0
|
1093
|
+
for each file in the directory:
|
1094
|
+
name of the file (UTF-8), \0
|
1095
|
+
32 bytes - sha256 hash of the file contents
|
1096
|
+
\0
|
1097
|
+
if the file mount source is something else or does not exist, nothing
|
1098
|
+
\0\0
|
1099
|
+
|
1100
|
+
Rather than constructing the whole byte sequence, which may be quite large,
|
1101
|
+
we construct it incrementally by using hash.update() to add new bytes.
|
1102
|
+
"""
|
1103
|
+
|
1104
|
+
def _hash_file(path: str) -> bytes:
|
1105
|
+
return common_utils.hash_file(path, 'sha256').digest()
|
1106
|
+
|
1107
|
+
config_hash = hashlib.sha256()
|
1108
|
+
|
1109
|
+
config_hash.update(_hash_file(yaml_path))
|
1110
|
+
|
1111
|
+
yaml_config = common_utils.read_yaml(yaml_path)
|
1112
|
+
file_mounts = yaml_config.get('file_mounts', {})
|
1113
|
+
# Remove the file mounts added by the newline.
|
1114
|
+
if '' in file_mounts:
|
1115
|
+
assert file_mounts[''] == '', file_mounts['']
|
1116
|
+
file_mounts.pop('')
|
1117
|
+
|
1118
|
+
for dst, src in sorted(file_mounts.items()):
|
1119
|
+
expanded_src = os.path.expanduser(src)
|
1120
|
+
config_hash.update(dst.encode('utf-8') + b'\0')
|
1121
|
+
|
1122
|
+
# If the file mount source is a symlink, this should be true. In that
|
1123
|
+
# case we hash the contents of the symlink destination.
|
1124
|
+
if os.path.isfile(expanded_src):
|
1125
|
+
config_hash.update('file'.encode('utf-8'))
|
1126
|
+
config_hash.update(_hash_file(expanded_src))
|
1127
|
+
|
1128
|
+
# This can also be a symlink to a directory. os.walk will treat it as a
|
1129
|
+
# normal directory and list the contents of the symlink destination.
|
1130
|
+
elif os.path.isdir(expanded_src):
|
1131
|
+
config_hash.update('dir'.encode('utf-8'))
|
1132
|
+
|
1133
|
+
# Aside from expanded_src, os.walk will list symlinks to directories
|
1134
|
+
# but will not recurse into them.
|
1135
|
+
for (dirpath, dirnames, filenames) in os.walk(expanded_src):
|
1136
|
+
config_hash.update(dirpath.encode('utf-8') + b'\0')
|
1137
|
+
|
1138
|
+
# Note: inplace sort will also affect the traversal order of
|
1139
|
+
# os.walk. We need it so that the os.walk order is
|
1140
|
+
# deterministic.
|
1141
|
+
dirnames.sort()
|
1142
|
+
# This includes symlinks to directories. os.walk will recurse
|
1143
|
+
# into all the directories but not the symlinks. We don't hash
|
1144
|
+
# the link destination, so if a symlink to a directory changes,
|
1145
|
+
# we won't notice.
|
1146
|
+
for dirname in dirnames:
|
1147
|
+
config_hash.update(dirname.encode('utf-8') + b'\0')
|
1148
|
+
config_hash.update(b'\0')
|
1149
|
+
|
1150
|
+
filenames.sort()
|
1151
|
+
# This includes symlinks to files. We could hash the symlink
|
1152
|
+
# destination itself but instead just hash the destination
|
1153
|
+
# contents.
|
1154
|
+
for filename in filenames:
|
1155
|
+
config_hash.update(filename.encode('utf-8') + b'\0')
|
1156
|
+
config_hash.update(
|
1157
|
+
_hash_file(os.path.join(dirpath, filename)))
|
1158
|
+
config_hash.update(b'\0')
|
1159
|
+
|
1160
|
+
else:
|
1161
|
+
logger.debug(
|
1162
|
+
f'Unexpected file_mount that is not a file or dir: {src}')
|
1163
|
+
|
1164
|
+
config_hash.update(b'\0\0')
|
1165
|
+
|
1166
|
+
return config_hash.hexdigest()
|
1167
|
+
|
1168
|
+
|
1036
1169
|
def get_docker_user(ip: str, cluster_config_file: str) -> str:
|
1037
1170
|
"""Find docker container username."""
|
1038
1171
|
ssh_credentials = ssh_credential_from_yaml(cluster_config_file)
|
@@ -1155,6 +1155,7 @@ class RetryingVmProvisioner(object):
|
|
1155
1155
|
prev_cluster_status: Optional[status_lib.ClusterStatus],
|
1156
1156
|
prev_handle: Optional['CloudVmRayResourceHandle'],
|
1157
1157
|
prev_cluster_ever_up: bool,
|
1158
|
+
prev_config_hash: Optional[str],
|
1158
1159
|
) -> None:
|
1159
1160
|
assert cluster_name is not None, 'cluster_name must be specified.'
|
1160
1161
|
self.cluster_name = cluster_name
|
@@ -1163,6 +1164,7 @@ class RetryingVmProvisioner(object):
|
|
1163
1164
|
self.prev_cluster_status = prev_cluster_status
|
1164
1165
|
self.prev_handle = prev_handle
|
1165
1166
|
self.prev_cluster_ever_up = prev_cluster_ever_up
|
1167
|
+
self.prev_config_hash = prev_config_hash
|
1166
1168
|
|
1167
1169
|
def __init__(self,
|
1168
1170
|
log_dir: str,
|
@@ -1324,8 +1326,21 @@ class RetryingVmProvisioner(object):
|
|
1324
1326
|
prev_cluster_status: Optional[status_lib.ClusterStatus],
|
1325
1327
|
prev_handle: Optional['CloudVmRayResourceHandle'],
|
1326
1328
|
prev_cluster_ever_up: bool,
|
1329
|
+
skip_if_config_hash_matches: Optional[str],
|
1327
1330
|
) -> Dict[str, Any]:
|
1328
|
-
"""The provision retry loop.
|
1331
|
+
"""The provision retry loop.
|
1332
|
+
|
1333
|
+
Returns a config_dict with the following fields:
|
1334
|
+
All fields from backend_utils.write_cluster_config(). See its
|
1335
|
+
docstring.
|
1336
|
+
- 'provisioning_skipped': True if provisioning was short-circuited
|
1337
|
+
by skip_if_config_hash_matches, False otherwise.
|
1338
|
+
- 'handle': The provisioned cluster handle.
|
1339
|
+
- 'provision_record': (Only if using the new skypilot provisioner) The
|
1340
|
+
record returned by provisioner.bulk_provision().
|
1341
|
+
- 'resources_vars': (Only if using the new skypilot provisioner) The
|
1342
|
+
resources variables given by make_deploy_resources_variables().
|
1343
|
+
"""
|
1329
1344
|
# Get log_path name
|
1330
1345
|
log_path = os.path.join(self.log_dir, 'provision.log')
|
1331
1346
|
log_abs_path = os.path.abspath(log_path)
|
@@ -1434,8 +1449,18 @@ class RetryingVmProvisioner(object):
|
|
1434
1449
|
raise exceptions.ResourcesUnavailableError(
|
1435
1450
|
f'Failed to provision on cloud {to_provision.cloud} due to '
|
1436
1451
|
f'invalid cloud config: {common_utils.format_exception(e)}')
|
1452
|
+
|
1453
|
+
if ('config_hash' in config_dict and
|
1454
|
+
skip_if_config_hash_matches == config_dict['config_hash']):
|
1455
|
+
logger.debug('Skipping provisioning of cluster with matching '
|
1456
|
+
'config hash.')
|
1457
|
+
config_dict['provisioning_skipped'] = True
|
1458
|
+
return config_dict
|
1459
|
+
config_dict['provisioning_skipped'] = False
|
1460
|
+
|
1437
1461
|
if dryrun:
|
1438
1462
|
return config_dict
|
1463
|
+
|
1439
1464
|
cluster_config_file = config_dict['ray']
|
1440
1465
|
|
1441
1466
|
launched_resources = to_provision.copy(region=region.name)
|
@@ -1947,8 +1972,13 @@ class RetryingVmProvisioner(object):
|
|
1947
1972
|
to_provision_config: ToProvisionConfig,
|
1948
1973
|
dryrun: bool,
|
1949
1974
|
stream_logs: bool,
|
1975
|
+
skip_unnecessary_provisioning: bool,
|
1950
1976
|
) -> Dict[str, Any]:
|
1951
|
-
"""Provision with retries for all launchable resources.
|
1977
|
+
"""Provision with retries for all launchable resources.
|
1978
|
+
|
1979
|
+
Returns the config_dict from _retry_zones() - see its docstring for
|
1980
|
+
details.
|
1981
|
+
"""
|
1952
1982
|
cluster_name = to_provision_config.cluster_name
|
1953
1983
|
to_provision = to_provision_config.resources
|
1954
1984
|
num_nodes = to_provision_config.num_nodes
|
@@ -1957,6 +1987,8 @@ class RetryingVmProvisioner(object):
|
|
1957
1987
|
prev_cluster_ever_up = to_provision_config.prev_cluster_ever_up
|
1958
1988
|
launchable_retries_disabled = (self._dag is None or
|
1959
1989
|
self._optimize_target is None)
|
1990
|
+
skip_if_config_hash_matches = (to_provision_config.prev_config_hash if
|
1991
|
+
skip_unnecessary_provisioning else None)
|
1960
1992
|
|
1961
1993
|
failover_history: List[Exception] = list()
|
1962
1994
|
|
@@ -1996,7 +2028,8 @@ class RetryingVmProvisioner(object):
|
|
1996
2028
|
cloud_user_identity=cloud_user,
|
1997
2029
|
prev_cluster_status=prev_cluster_status,
|
1998
2030
|
prev_handle=prev_handle,
|
1999
|
-
prev_cluster_ever_up=prev_cluster_ever_up
|
2031
|
+
prev_cluster_ever_up=prev_cluster_ever_up,
|
2032
|
+
skip_if_config_hash_matches=skip_if_config_hash_matches)
|
2000
2033
|
if dryrun:
|
2001
2034
|
return config_dict
|
2002
2035
|
except (exceptions.InvalidClusterNameError,
|
@@ -2697,14 +2730,21 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
2697
2730
|
return valid_resource
|
2698
2731
|
|
2699
2732
|
def _provision(
|
2700
|
-
|
2701
|
-
|
2702
|
-
|
2703
|
-
|
2704
|
-
|
2705
|
-
|
2706
|
-
|
2707
|
-
|
2733
|
+
self,
|
2734
|
+
task: task_lib.Task,
|
2735
|
+
to_provision: Optional[resources_lib.Resources],
|
2736
|
+
dryrun: bool,
|
2737
|
+
stream_logs: bool,
|
2738
|
+
cluster_name: str,
|
2739
|
+
retry_until_up: bool = False,
|
2740
|
+
skip_unnecessary_provisioning: bool = False,
|
2741
|
+
) -> Optional[CloudVmRayResourceHandle]:
|
2742
|
+
"""Provisions the cluster, or re-provisions an existing cluster.
|
2743
|
+
|
2744
|
+
Use the SKYPILOT provisioner if it's supported by the cloud, otherwise
|
2745
|
+
use 'ray up'.
|
2746
|
+
|
2747
|
+
See also docstring for Backend.provision().
|
2708
2748
|
|
2709
2749
|
Raises:
|
2710
2750
|
exceptions.ClusterOwnerIdentityMismatchError: if the cluster
|
@@ -2789,7 +2829,8 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
2789
2829
|
rich_utils.force_update_status(
|
2790
2830
|
ux_utils.spinner_message('Launching', log_path))
|
2791
2831
|
config_dict = retry_provisioner.provision_with_retries(
|
2792
|
-
task, to_provision_config, dryrun, stream_logs
|
2832
|
+
task, to_provision_config, dryrun, stream_logs,
|
2833
|
+
skip_unnecessary_provisioning)
|
2793
2834
|
break
|
2794
2835
|
except exceptions.ResourcesUnavailableError as e:
|
2795
2836
|
# Do not remove the stopped cluster from the global state
|
@@ -2839,11 +2880,23 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
2839
2880
|
record = global_user_state.get_cluster_from_name(cluster_name)
|
2840
2881
|
return record['handle'] if record is not None else None
|
2841
2882
|
|
2883
|
+
if config_dict['provisioning_skipped']:
|
2884
|
+
# Skip further provisioning.
|
2885
|
+
# In this case, we won't have certain fields in the config_dict
|
2886
|
+
# ('handle', 'provision_record', 'resources_vars')
|
2887
|
+
# We need to return the handle - but it should be the existing
|
2888
|
+
# handle for the cluster.
|
2889
|
+
record = global_user_state.get_cluster_from_name(cluster_name)
|
2890
|
+
assert record is not None and record['handle'] is not None, (
|
2891
|
+
cluster_name, record)
|
2892
|
+
return record['handle']
|
2893
|
+
|
2842
2894
|
if 'provision_record' in config_dict:
|
2843
2895
|
# New provisioner is used here.
|
2844
2896
|
handle = config_dict['handle']
|
2845
2897
|
provision_record = config_dict['provision_record']
|
2846
2898
|
resources_vars = config_dict['resources_vars']
|
2899
|
+
config_hash = config_dict.get('config_hash', None)
|
2847
2900
|
|
2848
2901
|
# Setup SkyPilot runtime after the cluster is provisioned
|
2849
2902
|
# 1. Wait for SSH to be ready.
|
@@ -2878,7 +2931,7 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
2878
2931
|
self._update_after_cluster_provisioned(
|
2879
2932
|
handle, to_provision_config.prev_handle, task,
|
2880
2933
|
prev_cluster_status, handle.external_ips(),
|
2881
|
-
handle.external_ssh_ports(), lock_path)
|
2934
|
+
handle.external_ssh_ports(), lock_path, config_hash)
|
2882
2935
|
return handle
|
2883
2936
|
|
2884
2937
|
cluster_config_file = config_dict['ray']
|
@@ -2950,7 +3003,8 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
2950
3003
|
|
2951
3004
|
self._update_after_cluster_provisioned(
|
2952
3005
|
handle, to_provision_config.prev_handle, task,
|
2953
|
-
prev_cluster_status, ip_list, ssh_port_list, lock_path
|
3006
|
+
prev_cluster_status, ip_list, ssh_port_list, lock_path,
|
3007
|
+
config_hash)
|
2954
3008
|
return handle
|
2955
3009
|
|
2956
3010
|
def _open_ports(self, handle: CloudVmRayResourceHandle) -> None:
|
@@ -2968,8 +3022,8 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
2968
3022
|
prev_handle: Optional[CloudVmRayResourceHandle],
|
2969
3023
|
task: task_lib.Task,
|
2970
3024
|
prev_cluster_status: Optional[status_lib.ClusterStatus],
|
2971
|
-
ip_list: List[str], ssh_port_list: List[int],
|
2972
|
-
|
3025
|
+
ip_list: List[str], ssh_port_list: List[int], lock_path: str,
|
3026
|
+
config_hash: str) -> None:
|
2973
3027
|
usage_lib.messages.usage.update_cluster_resources(
|
2974
3028
|
handle.launched_nodes, handle.launched_resources)
|
2975
3029
|
usage_lib.messages.usage.update_final_cluster_status(
|
@@ -3029,6 +3083,7 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
3029
3083
|
handle,
|
3030
3084
|
set(task.resources),
|
3031
3085
|
ready=True,
|
3086
|
+
config_hash=config_hash,
|
3032
3087
|
)
|
3033
3088
|
usage_lib.messages.usage.update_final_cluster_status(
|
3034
3089
|
status_lib.ClusterStatus.UP)
|
@@ -4348,6 +4403,7 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
4348
4403
|
# cluster is terminated (through console or auto-dwon), the record will
|
4349
4404
|
# become None and the cluster_ever_up should be considered as False.
|
4350
4405
|
cluster_ever_up = record is not None and record['cluster_ever_up']
|
4406
|
+
prev_config_hash = record['config_hash'] if record is not None else None
|
4351
4407
|
logger.debug(f'cluster_ever_up: {cluster_ever_up}')
|
4352
4408
|
logger.debug(f'record: {record}')
|
4353
4409
|
|
@@ -4386,7 +4442,8 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
4386
4442
|
handle.launched_nodes,
|
4387
4443
|
prev_cluster_status=prev_cluster_status,
|
4388
4444
|
prev_handle=handle,
|
4389
|
-
prev_cluster_ever_up=cluster_ever_up
|
4445
|
+
prev_cluster_ever_up=cluster_ever_up,
|
4446
|
+
prev_config_hash=prev_config_hash)
|
4390
4447
|
usage_lib.messages.usage.set_new_cluster()
|
4391
4448
|
# Use the task_cloud, because the cloud in `to_provision` can be changed
|
4392
4449
|
# later during the retry.
|
@@ -4427,7 +4484,8 @@ class CloudVmRayBackend(backends.Backend['CloudVmRayResourceHandle']):
|
|
4427
4484
|
task.num_nodes,
|
4428
4485
|
prev_cluster_status=None,
|
4429
4486
|
prev_handle=None,
|
4430
|
-
prev_cluster_ever_up=False
|
4487
|
+
prev_cluster_ever_up=False,
|
4488
|
+
prev_config_hash=prev_config_hash)
|
4431
4489
|
|
4432
4490
|
def _execute_file_mounts(self, handle: CloudVmRayResourceHandle,
|
4433
4491
|
file_mounts: Optional[Dict[Path, Path]]):
|
@@ -131,13 +131,14 @@ class LocalDockerBackend(backends.Backend['LocalDockerResourceHandle']):
|
|
131
131
|
pass
|
132
132
|
|
133
133
|
def _provision(
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
134
|
+
self,
|
135
|
+
task: 'task_lib.Task',
|
136
|
+
to_provision: Optional['resources.Resources'],
|
137
|
+
dryrun: bool,
|
138
|
+
stream_logs: bool,
|
139
|
+
cluster_name: str,
|
140
|
+
retry_until_up: bool = False,
|
141
|
+
skip_unnecessary_provisioning: bool = False,
|
141
142
|
) -> Optional[LocalDockerResourceHandle]:
|
142
143
|
"""Builds docker image for the task and returns cluster name as handle.
|
143
144
|
|
@@ -153,6 +154,9 @@ class LocalDockerBackend(backends.Backend['LocalDockerResourceHandle']):
|
|
153
154
|
logger.warning(
|
154
155
|
f'Retrying until up is not supported in backend: {self.NAME}. '
|
155
156
|
'Ignored the flag.')
|
157
|
+
if skip_unnecessary_provisioning:
|
158
|
+
logger.warning(f'skip_unnecessary_provisioning is not supported in '
|
159
|
+
f'backend: {self.NAME}. Ignored the flag.')
|
156
160
|
if stream_logs:
|
157
161
|
logger.info(
|
158
162
|
'Streaming build logs is not supported in LocalDockerBackend. '
|
@@ -15,6 +15,7 @@ from sky.adaptors import common as adaptors_common
|
|
15
15
|
from sky.clouds import cloud as cloud_lib
|
16
16
|
from sky.clouds import cloud_registry
|
17
17
|
from sky.clouds.service_catalog import constants
|
18
|
+
from sky.utils import common_utils
|
18
19
|
from sky.utils import rich_utils
|
19
20
|
from sky.utils import ux_utils
|
20
21
|
|
@@ -69,8 +70,7 @@ def is_catalog_modified(filename: str) -> bool:
|
|
69
70
|
meta_path = os.path.join(_ABSOLUTE_VERSIONED_CATALOG_DIR, '.meta', filename)
|
70
71
|
md5_filepath = meta_path + '.md5'
|
71
72
|
if os.path.exists(md5_filepath):
|
72
|
-
|
73
|
-
file_md5 = hashlib.md5(f.read()).hexdigest()
|
73
|
+
file_md5 = common_utils.hash_file(catalog_path, 'md5').hexdigest()
|
74
74
|
with open(md5_filepath, 'r', encoding='utf-8') as f:
|
75
75
|
last_md5 = f.read()
|
76
76
|
return file_md5 != last_md5
|
sky/execution.py
CHANGED
@@ -108,6 +108,7 @@ def _execute(
|
|
108
108
|
idle_minutes_to_autostop: Optional[int] = None,
|
109
109
|
no_setup: bool = False,
|
110
110
|
clone_disk_from: Optional[str] = None,
|
111
|
+
skip_unnecessary_provisioning: bool = False,
|
111
112
|
# Internal only:
|
112
113
|
# pylint: disable=invalid-name
|
113
114
|
_is_launched_by_jobs_controller: bool = False,
|
@@ -128,8 +129,9 @@ def _execute(
|
|
128
129
|
Note that if errors occur during provisioning/data syncing/setting up,
|
129
130
|
the cluster will not be torn down for debugging purposes.
|
130
131
|
stream_logs: bool; whether to stream all tasks' outputs to the client.
|
131
|
-
handle: Optional[backends.ResourceHandle]; if provided, execution will
|
132
|
-
an existing backend cluster handle instead of
|
132
|
+
handle: Optional[backends.ResourceHandle]; if provided, execution will
|
133
|
+
attempt to use an existing backend cluster handle instead of
|
134
|
+
provisioning a new one.
|
133
135
|
backend: Backend; backend to use for executing the tasks. Defaults to
|
134
136
|
CloudVmRayBackend()
|
135
137
|
retry_until_up: bool; whether to retry the provisioning until the cluster
|
@@ -150,6 +152,11 @@ def _execute(
|
|
150
152
|
idle_minutes_to_autostop: int; if provided, the cluster will be set to
|
151
153
|
autostop after this many minutes of idleness.
|
152
154
|
no_setup: bool; whether to skip setup commands or not when (re-)launching.
|
155
|
+
clone_disk_from: Optional[str]; if set, clone the disk from the specified
|
156
|
+
cluster.
|
157
|
+
skip_unecessary_provisioning: bool; if True, compare the calculated
|
158
|
+
cluster config to the current cluster's config. If they match, shortcut
|
159
|
+
provisioning even if we have Stage.PROVISION.
|
153
160
|
|
154
161
|
Returns:
|
155
162
|
job_id: Optional[int]; the job ID of the submitted job. None if the
|
@@ -288,13 +295,18 @@ def _execute(
|
|
288
295
|
|
289
296
|
try:
|
290
297
|
if Stage.PROVISION in stages:
|
291
|
-
|
292
|
-
handle
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
+
assert handle is None or skip_unnecessary_provisioning, (
|
299
|
+
'Provisioning requested, but handle is already set. PROVISION '
|
300
|
+
'should be excluded from stages or '
|
301
|
+
'skip_unecessary_provisioning should be set. ')
|
302
|
+
handle = backend.provision(
|
303
|
+
task,
|
304
|
+
task.best_resources,
|
305
|
+
dryrun=dryrun,
|
306
|
+
stream_logs=stream_logs,
|
307
|
+
cluster_name=cluster_name,
|
308
|
+
retry_until_up=retry_until_up,
|
309
|
+
skip_unnecessary_provisioning=skip_unnecessary_provisioning)
|
298
310
|
|
299
311
|
if handle is None:
|
300
312
|
assert dryrun, ('If not dryrun, handle must be set or '
|
@@ -469,6 +481,7 @@ def launch(
|
|
469
481
|
|
470
482
|
handle = None
|
471
483
|
stages = None
|
484
|
+
skip_unnecessary_provisioning = False
|
472
485
|
# Check if cluster exists and we are doing fast provisioning
|
473
486
|
if fast and cluster_name is not None:
|
474
487
|
cluster_status, maybe_handle = (
|
@@ -502,12 +515,16 @@ def launch(
|
|
502
515
|
if cluster_status == status_lib.ClusterStatus.UP:
|
503
516
|
handle = maybe_handle
|
504
517
|
stages = [
|
518
|
+
# Provisioning will be short-circuited if the existing
|
519
|
+
# cluster config hash matches the calculated one.
|
520
|
+
Stage.PROVISION,
|
505
521
|
Stage.SYNC_WORKDIR,
|
506
522
|
Stage.SYNC_FILE_MOUNTS,
|
507
523
|
Stage.PRE_EXEC,
|
508
524
|
Stage.EXEC,
|
509
525
|
Stage.DOWN,
|
510
526
|
]
|
527
|
+
skip_unnecessary_provisioning = True
|
511
528
|
|
512
529
|
return _execute(
|
513
530
|
entrypoint=entrypoint,
|
@@ -525,6 +542,7 @@ def launch(
|
|
525
542
|
idle_minutes_to_autostop=idle_minutes_to_autostop,
|
526
543
|
no_setup=no_setup,
|
527
544
|
clone_disk_from=clone_disk_from,
|
545
|
+
skip_unnecessary_provisioning=skip_unnecessary_provisioning,
|
528
546
|
_is_launched_by_jobs_controller=_is_launched_by_jobs_controller,
|
529
547
|
_is_launched_by_sky_serve_controller=
|
530
548
|
_is_launched_by_sky_serve_controller,
|
sky/global_user_state.py
CHANGED
@@ -61,7 +61,8 @@ def create_table(cursor, conn):
|
|
61
61
|
cluster_hash TEXT DEFAULT null,
|
62
62
|
storage_mounts_metadata BLOB DEFAULT null,
|
63
63
|
cluster_ever_up INTEGER DEFAULT 0,
|
64
|
-
status_updated_at INTEGER DEFAULT null
|
64
|
+
status_updated_at INTEGER DEFAULT null,
|
65
|
+
config_hash TEXT DEFAULT null)""")
|
65
66
|
|
66
67
|
# Table for Cluster History
|
67
68
|
# usage_intervals: List[Tuple[int, int]]
|
@@ -135,6 +136,9 @@ def create_table(cursor, conn):
|
|
135
136
|
db_utils.add_column_to_table(cursor, conn, 'clusters', 'status_updated_at',
|
136
137
|
'INTEGER DEFAULT null')
|
137
138
|
|
139
|
+
db_utils.add_column_to_table(cursor, conn, 'clusters', 'config_hash',
|
140
|
+
'TEXT DEFAULT null')
|
141
|
+
|
138
142
|
conn.commit()
|
139
143
|
|
140
144
|
|
@@ -145,7 +149,8 @@ def add_or_update_cluster(cluster_name: str,
|
|
145
149
|
cluster_handle: 'backends.ResourceHandle',
|
146
150
|
requested_resources: Optional[Set[Any]],
|
147
151
|
ready: bool,
|
148
|
-
is_launch: bool = True
|
152
|
+
is_launch: bool = True,
|
153
|
+
config_hash: Optional[str] = None):
|
149
154
|
"""Adds or updates cluster_name -> cluster_handle mapping.
|
150
155
|
|
151
156
|
Args:
|
@@ -197,7 +202,8 @@ def add_or_update_cluster(cluster_name: str,
|
|
197
202
|
# specified.
|
198
203
|
'(name, launched_at, handle, last_use, status, '
|
199
204
|
'autostop, to_down, metadata, owner, cluster_hash, '
|
200
|
-
'storage_mounts_metadata, cluster_ever_up, status_updated_at
|
205
|
+
'storage_mounts_metadata, cluster_ever_up, status_updated_at, '
|
206
|
+
'config_hash) '
|
201
207
|
'VALUES ('
|
202
208
|
# name
|
203
209
|
'?, '
|
@@ -236,7 +242,9 @@ def add_or_update_cluster(cluster_name: str,
|
|
236
242
|
# cluster_ever_up
|
237
243
|
'((SELECT cluster_ever_up FROM clusters WHERE name=?) OR ?),'
|
238
244
|
# status_updated_at
|
239
|
-
'
|
245
|
+
'?,'
|
246
|
+
# config_hash
|
247
|
+
'COALESCE(?, (SELECT config_hash FROM clusters WHERE name=?))'
|
240
248
|
')',
|
241
249
|
(
|
242
250
|
# name
|
@@ -270,6 +278,9 @@ def add_or_update_cluster(cluster_name: str,
|
|
270
278
|
int(ready),
|
271
279
|
# status_updated_at
|
272
280
|
status_updated_at,
|
281
|
+
# config_hash
|
282
|
+
config_hash,
|
283
|
+
cluster_name,
|
273
284
|
))
|
274
285
|
|
275
286
|
launched_nodes = getattr(cluster_handle, 'launched_nodes', None)
|
@@ -585,15 +596,15 @@ def get_cluster_from_name(
|
|
585
596
|
rows = _DB.cursor.execute(
|
586
597
|
'SELECT name, launched_at, handle, last_use, status, autostop, '
|
587
598
|
'metadata, to_down, owner, cluster_hash, storage_mounts_metadata, '
|
588
|
-
'cluster_ever_up, status_updated_at
|
589
|
-
(cluster_name,)).fetchall()
|
599
|
+
'cluster_ever_up, status_updated_at, config_hash '
|
600
|
+
'FROM clusters WHERE name=(?)', (cluster_name,)).fetchall()
|
590
601
|
for row in rows:
|
591
602
|
# Explicitly specify the number of fields to unpack, so that
|
592
603
|
# we can add new fields to the database in the future without
|
593
604
|
# breaking the previous code.
|
594
605
|
(name, launched_at, handle, last_use, status, autostop, metadata,
|
595
606
|
to_down, owner, cluster_hash, storage_mounts_metadata, cluster_ever_up,
|
596
|
-
status_updated_at) = row[:
|
607
|
+
status_updated_at, config_hash) = row[:14]
|
597
608
|
# TODO: use namedtuple instead of dict
|
598
609
|
record = {
|
599
610
|
'name': name,
|
@@ -610,6 +621,7 @@ def get_cluster_from_name(
|
|
610
621
|
_load_storage_mounts_metadata(storage_mounts_metadata),
|
611
622
|
'cluster_ever_up': bool(cluster_ever_up),
|
612
623
|
'status_updated_at': status_updated_at,
|
624
|
+
'config_hash': config_hash,
|
613
625
|
}
|
614
626
|
return record
|
615
627
|
return None
|
@@ -619,13 +631,13 @@ def get_clusters() -> List[Dict[str, Any]]:
|
|
619
631
|
rows = _DB.cursor.execute(
|
620
632
|
'select name, launched_at, handle, last_use, status, autostop, '
|
621
633
|
'metadata, to_down, owner, cluster_hash, storage_mounts_metadata, '
|
622
|
-
'cluster_ever_up, status_updated_at
|
623
|
-
'order by launched_at desc').fetchall()
|
634
|
+
'cluster_ever_up, status_updated_at, config_hash '
|
635
|
+
'from clusters order by launched_at desc').fetchall()
|
624
636
|
records = []
|
625
637
|
for row in rows:
|
626
638
|
(name, launched_at, handle, last_use, status, autostop, metadata,
|
627
639
|
to_down, owner, cluster_hash, storage_mounts_metadata, cluster_ever_up,
|
628
|
-
status_updated_at) = row[:
|
640
|
+
status_updated_at, config_hash) = row[:14]
|
629
641
|
# TODO: use namedtuple instead of dict
|
630
642
|
record = {
|
631
643
|
'name': name,
|
@@ -642,6 +654,7 @@ def get_clusters() -> List[Dict[str, Any]]:
|
|
642
654
|
_load_storage_mounts_metadata(storage_mounts_metadata),
|
643
655
|
'cluster_ever_up': bool(cluster_ever_up),
|
644
656
|
'status_updated_at': status_updated_at,
|
657
|
+
'config_hash': config_hash,
|
645
658
|
}
|
646
659
|
|
647
660
|
records.append(record)
|
@@ -560,6 +560,7 @@ available_node_types:
|
|
560
560
|
# https://gitlab.com/arm-research/smarter/smarter-device-manager
|
561
561
|
smarter-devices/fuse: "1"
|
562
562
|
{% endif %}
|
563
|
+
{% if k8s_resource_key is not none or k8s_fuse_device_required %}
|
563
564
|
limits:
|
564
565
|
# Limits need to be defined for GPU/TPU requests
|
565
566
|
{% if k8s_resource_key is not none %}
|
@@ -568,7 +569,8 @@ available_node_types:
|
|
568
569
|
{% if k8s_fuse_device_required %}
|
569
570
|
smarter-devices/fuse: "1"
|
570
571
|
{% endif %}
|
571
|
-
|
572
|
+
{% endif %}
|
573
|
+
|
572
574
|
setup_commands:
|
573
575
|
# Disable `unattended-upgrades` to prevent apt-get from hanging. It should be called at the beginning before the process started to avoid being blocked. (This is a temporary fix.)
|
574
576
|
# Create ~/.ssh/config file in case the file does not exist in the image.
|
sky/utils/common_utils.py
CHANGED
@@ -697,3 +697,22 @@ def truncate_long_string(s: str, max_length: int = 35) -> str:
|
|
697
697
|
if len(prefix) < max_length:
|
698
698
|
prefix += s[len(prefix):max_length]
|
699
699
|
return prefix + '...'
|
700
|
+
|
701
|
+
|
702
|
+
def hash_file(path: str, hash_alg: str) -> 'hashlib._Hash':
|
703
|
+
# In python 3.11, hashlib.file_digest is available, but for <3.11 we have to
|
704
|
+
# do it manually.
|
705
|
+
# This implementation is simplified from the implementation in CPython.
|
706
|
+
# TODO(cooperc): Use hashlib.file_digest once we move to 3.11+.
|
707
|
+
# Beware of f.read() as some files may be larger than memory.
|
708
|
+
with open(path, 'rb') as f:
|
709
|
+
file_hash = hashlib.new(hash_alg)
|
710
|
+
buf = bytearray(2**18)
|
711
|
+
view = memoryview(buf)
|
712
|
+
while True:
|
713
|
+
size = f.readinto(buf)
|
714
|
+
if size == 0:
|
715
|
+
# EOF
|
716
|
+
break
|
717
|
+
file_hash.update(view[:size])
|
718
|
+
return file_hash
|
{skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/RECORD
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
sky/__init__.py,sha256=
|
1
|
+
sky/__init__.py,sha256=ALidpTzKOu5NkCpzBWS1bj2NJln5WMQFVixsQZFO_wM,5944
|
2
2
|
sky/admin_policy.py,sha256=hPo02f_A32gCqhUueF0QYy1fMSSKqRwYEg_9FxScN_s,3248
|
3
3
|
sky/authentication.py,sha256=kACHmiZgWgRpYd1wx1ofbXRMErfMcFmWrkw4a9NxYrY,20988
|
4
4
|
sky/check.py,sha256=D3Y3saIFAYVvPxuBHnVgJEO0fUVDxgjwuMBaO-D778k,9472
|
@@ -7,8 +7,8 @@ sky/cloud_stores.py,sha256=RjFgmRhUh1Kk__f6g3KxzLp9s7dA0pFK4W1AukEuUaw,21153
|
|
7
7
|
sky/core.py,sha256=TQnSe7-bod1qmOwBArk9a2PVCWFlJZX9Ahn2FUHt7M8,38615
|
8
8
|
sky/dag.py,sha256=f3sJlkH4bE6Uuz3ozNtsMhcBpRx7KmC9Sa4seDKt4hU,3104
|
9
9
|
sky/exceptions.py,sha256=rUi_au7QBNn3_wvwa8Y_MSHN3QDRpVLry8Mfa56LyGk,9197
|
10
|
-
sky/execution.py,sha256=
|
11
|
-
sky/global_user_state.py,sha256=
|
10
|
+
sky/execution.py,sha256=dpbk1kGRkGHT0FCJKGvjqeV3qIGEN2K20NDZbVrcAvI,28483
|
11
|
+
sky/global_user_state.py,sha256=m2LJsXkh8eAvvz0ADnSP6idfYWZTA_Xi3uxwR3DrJxo,30241
|
12
12
|
sky/optimizer.py,sha256=GjvKQIBtY3NlULzau_9tfa7V2KYVJRrmNrjKVIWCPIQ,59753
|
13
13
|
sky/resources.py,sha256=4T-zQK0OSLC1z5-sLFluEQJ_Y8CAJX3jb4ZSPedwy1s,70352
|
14
14
|
sky/sky_logging.py,sha256=oLmTmwkuucIto3LHXLJfMcyRpYSkmZAZa5XzQPA5IHk,4434
|
@@ -29,11 +29,11 @@ sky/adaptors/oci.py,sha256=n_zcrippTZRbTIhN3euD5sqNYn43G397zMavaJyEYbk,1480
|
|
29
29
|
sky/adaptors/runpod.py,sha256=4Nt_BfZhJAKQNA3wO8cxvvNI8x4NsDGHu_4EhRDlGYQ,225
|
30
30
|
sky/adaptors/vsphere.py,sha256=zJP9SeObEoLrpgHW2VHvZE48EhgVf8GfAEIwBeaDMfM,2129
|
31
31
|
sky/backends/__init__.py,sha256=UDjwbUgpTRApbPJnNfR786GadUuwgRk3vsWoVu5RB_c,536
|
32
|
-
sky/backends/backend.py,sha256=
|
33
|
-
sky/backends/backend_utils.py,sha256=
|
34
|
-
sky/backends/cloud_vm_ray_backend.py,sha256=
|
32
|
+
sky/backends/backend.py,sha256=iBs5gnMaaUoH2OIQ3xhAjWdrJWqj8T61Za9TGsBFpvQ,7515
|
33
|
+
sky/backends/backend_utils.py,sha256=BbtkHwp__jGCN0u3gO_pKVITJidQEreVNE6ciTCxy6g,132186
|
34
|
+
sky/backends/cloud_vm_ray_backend.py,sha256=yu61XKC2VMvUY7w53bRqofAPGnVvsaHKyHh_SaN1J-s,237304
|
35
35
|
sky/backends/docker_utils.py,sha256=Hyw1YY20EyghhEbYx6O2FIMDcGkNzBzV9TM7LFynei8,8358
|
36
|
-
sky/backends/local_docker_backend.py,sha256=
|
36
|
+
sky/backends/local_docker_backend.py,sha256=nSYCjms3HOPjPNOrcCqsUKm1WV3AAovRFjEQ7hcEXW4,17021
|
37
37
|
sky/backends/wheel_utils.py,sha256=CUVOwlBtQjOMv-RSDGx2jMQ0M1D0w9ZPm0TDafJwBDI,8180
|
38
38
|
sky/backends/monkey_patches/monkey_patch_ray_up.py,sha256=76-y2fCaE3JINj8lEwHT1eirYzCbpD8O1ySsysuGu8o,3450
|
39
39
|
sky/benchmark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -58,7 +58,7 @@ sky/clouds/vsphere.py,sha256=LzO-Mc-zDgpaDYZxNKGdEFa0eR5DHpTgKsPX60mPi10,12280
|
|
58
58
|
sky/clouds/service_catalog/__init__.py,sha256=p4V0GGeumT8yt01emqDM7Au45H5jvPfGNqdI6L2W3uM,14750
|
59
59
|
sky/clouds/service_catalog/aws_catalog.py,sha256=j33lNC5GXWK6CiGWZORCnumGlRODmCAT2_lfWp0YtBc,13106
|
60
60
|
sky/clouds/service_catalog/azure_catalog.py,sha256=5Q51x_WEKvQ2YSgJvZHRH3URlbwIstYuwpjaWW_wJlw,8149
|
61
|
-
sky/clouds/service_catalog/common.py,sha256=
|
61
|
+
sky/clouds/service_catalog/common.py,sha256=fCGabkqCMmzNaeT4_--V4K5GsGdf8v_pzYHGYNRDot4,27709
|
62
62
|
sky/clouds/service_catalog/config.py,sha256=ylzqewdEBjDg4awvFek6ldYmFrnvD2bVGLZuLPvEVYA,1793
|
63
63
|
sky/clouds/service_catalog/constants.py,sha256=ai2yOlsVqBnEpbxaEHXt61COsHBLwOfw6GZXntEPj7k,411
|
64
64
|
sky/clouds/service_catalog/cudo_catalog.py,sha256=V_takvL6dWTGQaTLCEvjKIotCDPnMujiNUZ87kZKGVI,4673
|
@@ -229,7 +229,7 @@ sky/templates/jobs-controller.yaml.j2,sha256=Gu3ogFxFYr09VEXP-6zEbrCUOFo1aYxWEjA
|
|
229
229
|
sky/templates/kubernetes-ingress.yml.j2,sha256=73iDklVDWBMbItg0IexCa6_ClXPJOxw7PWz3leku4nE,1340
|
230
230
|
sky/templates/kubernetes-loadbalancer.yml.j2,sha256=IxrNYM366N01bbkJEbZ_UPYxUP8wyVEbRNFHRsBuLsw,626
|
231
231
|
sky/templates/kubernetes-port-forward-proxy-command.sh,sha256=iw7mypHszg6Ggq9MbyiYMFOkSlXaQZulaxqC5IWYGCc,3381
|
232
|
-
sky/templates/kubernetes-ray.yml.j2,sha256=
|
232
|
+
sky/templates/kubernetes-ray.yml.j2,sha256=EHUDvALvhaPB44U7cdgXStV6v8Qh8yn5J4T6XFnmZoM,28856
|
233
233
|
sky/templates/kubernetes-ssh-jump.yml.j2,sha256=k5W5sOIMppU7dDkJMwPlqsUcb92y7L5_TVG3hkgMy8M,2747
|
234
234
|
sky/templates/lambda-ray.yml.j2,sha256=HyvO_tX2vxwSsc4IFVSqGuIbjLMk0bevP9bcxb8ZQII,4498
|
235
235
|
sky/templates/local-ray.yml.j2,sha256=FNHeyHF6nW9nU9QLIZceUWfvrFTTcO51KqhTnYCEFaA,1185
|
@@ -248,7 +248,7 @@ sky/utils/admin_policy_utils.py,sha256=_Vt_jTTYCXmMdryj0vrrumFPewa93qHnzUqBDXjAh
|
|
248
248
|
sky/utils/cluster_yaml_utils.py,sha256=1wRRYqI1kI-eFs1pMW4r_FFjHJ0zamq6v2RRI-Gtx5E,849
|
249
249
|
sky/utils/command_runner.py,sha256=ewDjFxcCOv0OeG2aUOIfVWmTls65up9DvSnAXURvGfM,36696
|
250
250
|
sky/utils/command_runner.pyi,sha256=mJOzCgcYZAfHwnY_6Wf1YwlTEJGb9ihzc2f0rE0Kw98,7751
|
251
|
-
sky/utils/common_utils.py,sha256=
|
251
|
+
sky/utils/common_utils.py,sha256=wSna2SAWaKOTm1c5BhUl_4b8ev-MkMm9MF_rTAxW2zc,25360
|
252
252
|
sky/utils/control_master_utils.py,sha256=90hnxiAUP20gbJ9e3MERh7rb04ZO_I3LsljNjR26H5I,1416
|
253
253
|
sky/utils/controller_utils.py,sha256=u-dSFwjP8AR941AbGfLSROuwsAYOUch3hoPPeIEdBq0,40683
|
254
254
|
sky/utils/dag_utils.py,sha256=pVX3lGDDcYTcGoH_1jEWzl9767Y4mwlIEYIzoyHO6gM,6105
|
@@ -276,9 +276,9 @@ sky/utils/kubernetes/k8s_gpu_labeler_job.yaml,sha256=k0TBoQ4zgf79-sVkixKSGYFHQ7Z
|
|
276
276
|
sky/utils/kubernetes/k8s_gpu_labeler_setup.yaml,sha256=VLKT2KKimZu1GDg_4AIlIt488oMQvhRZWwsj9vBbPUg,3812
|
277
277
|
sky/utils/kubernetes/rsync_helper.sh,sha256=h4YwrPFf9727CACnMJvF3EyK_0OeOYKKt4su_daKekw,1256
|
278
278
|
sky/utils/kubernetes/ssh_jump_lifecycle_manager.py,sha256=RFLJ3k7MR5UN4SKHykQ0lV9SgXumoULpKYIAt1vh-HU,6560
|
279
|
-
skypilot_nightly-1.0.0.
|
280
|
-
skypilot_nightly-1.0.0.
|
281
|
-
skypilot_nightly-1.0.0.
|
282
|
-
skypilot_nightly-1.0.0.
|
283
|
-
skypilot_nightly-1.0.0.
|
284
|
-
skypilot_nightly-1.0.0.
|
279
|
+
skypilot_nightly-1.0.0.dev20241206.dist-info/LICENSE,sha256=emRJAvE7ngL6x0RhQvlns5wJzGI3NEQ_WMjNmd9TZc4,12170
|
280
|
+
skypilot_nightly-1.0.0.dev20241206.dist-info/METADATA,sha256=4nRw07zllEeEc44b7dRedR2MeR4ZlPhUwtsRawjGyEI,20319
|
281
|
+
skypilot_nightly-1.0.0.dev20241206.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
282
|
+
skypilot_nightly-1.0.0.dev20241206.dist-info/entry_points.txt,sha256=StA6HYpuHj-Y61L2Ze-hK2IcLWgLZcML5gJu8cs6nU4,36
|
283
|
+
skypilot_nightly-1.0.0.dev20241206.dist-info/top_level.txt,sha256=qA8QuiNNb6Y1OF-pCUtPEr6sLEwy2xJX06Bd_CrtrHY,4
|
284
|
+
skypilot_nightly-1.0.0.dev20241206.dist-info/RECORD,,
|
File without changes
|
{skypilot_nightly-1.0.0.dev20241204.dist-info → skypilot_nightly-1.0.0.dev20241206.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|