skypilot-nightly 1.0.0.dev20250320__py3-none-any.whl → 1.0.0.dev20250321__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/adaptors/cloudflare.py +16 -4
- sky/adaptors/kubernetes.py +2 -1
- sky/adaptors/nebius.py +128 -6
- sky/backends/cloud_vm_ray_backend.py +3 -1
- sky/benchmark/benchmark_utils.py +3 -2
- sky/check.py +18 -87
- sky/cloud_stores.py +66 -0
- sky/clouds/aws.py +14 -7
- sky/clouds/azure.py +13 -6
- sky/clouds/cloud.py +33 -10
- sky/clouds/cudo.py +3 -2
- sky/clouds/do.py +3 -2
- sky/clouds/fluidstack.py +3 -2
- sky/clouds/gcp.py +8 -9
- sky/clouds/ibm.py +15 -6
- sky/clouds/kubernetes.py +3 -1
- sky/clouds/lambda_cloud.py +3 -1
- sky/clouds/nebius.py +7 -3
- sky/clouds/oci.py +15 -6
- sky/clouds/paperspace.py +3 -2
- sky/clouds/runpod.py +7 -1
- sky/clouds/scp.py +3 -1
- sky/clouds/service_catalog/kubernetes_catalog.py +3 -1
- sky/clouds/vast.py +3 -2
- sky/clouds/vsphere.py +3 -2
- sky/core.py +4 -2
- sky/data/data_transfer.py +75 -0
- sky/data/data_utils.py +34 -0
- sky/data/mounting_utils.py +18 -0
- sky/data/storage.py +537 -9
- sky/data/storage_utils.py +102 -84
- sky/exceptions.py +2 -0
- sky/global_user_state.py +12 -33
- sky/jobs/server/core.py +1 -1
- sky/jobs/utils.py +5 -0
- sky/optimizer.py +7 -2
- sky/resources.py +6 -1
- sky/setup_files/dependencies.py +3 -1
- sky/task.py +16 -5
- sky/utils/command_runner.py +2 -0
- sky/utils/controller_utils.py +8 -5
- sky/utils/kubernetes/kubernetes_deploy_utils.py +2 -1
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/METADATA +11 -1
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/RECORD +49 -49
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/WHEEL +1 -1
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250320.dist-info → skypilot_nightly-1.0.0.dev20250321.dist-info}/top_level.txt +0 -0
sky/clouds/cloud.py
CHANGED
@@ -13,6 +13,8 @@ import math
|
|
13
13
|
import typing
|
14
14
|
from typing import Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union
|
15
15
|
|
16
|
+
from typing_extensions import assert_never
|
17
|
+
|
16
18
|
from sky import exceptions
|
17
19
|
from sky import skypilot_config
|
18
20
|
from sky.clouds import service_catalog
|
@@ -48,6 +50,16 @@ class CloudImplementationFeatures(enum.Enum):
|
|
48
50
|
AUTO_TERMINATE = 'auto_terminate' # Pod/VM can stop or down itself
|
49
51
|
|
50
52
|
|
53
|
+
class CloudCapability(enum.Enum):
|
54
|
+
# Compute capability.
|
55
|
+
COMPUTE = 'compute'
|
56
|
+
# Storage capability.
|
57
|
+
STORAGE = 'storage'
|
58
|
+
|
59
|
+
|
60
|
+
ALL_CAPABILITIES = [CloudCapability.COMPUTE, CloudCapability.STORAGE]
|
61
|
+
|
62
|
+
|
51
63
|
class Region(collections.namedtuple('Region', ['name'])):
|
52
64
|
"""A region."""
|
53
65
|
name: str
|
@@ -435,25 +447,36 @@ class Cloud:
|
|
435
447
|
return {reservation: 0 for reservation in specific_reservations}
|
436
448
|
|
437
449
|
@classmethod
|
438
|
-
def check_credentials(
|
450
|
+
def check_credentials(
|
451
|
+
cls,
|
452
|
+
cloud_capability: CloudCapability) -> Tuple[bool, Optional[str]]:
|
439
453
|
"""Checks if the user has access credentials to this cloud.
|
440
454
|
|
441
455
|
Returns a boolean of whether the user can access this cloud, and a
|
442
456
|
string describing the reason if the user cannot access.
|
457
|
+
|
458
|
+
Raises NotSupportedError if the capability is
|
459
|
+
not supported by this cloud.
|
443
460
|
"""
|
444
|
-
|
461
|
+
if cloud_capability == CloudCapability.COMPUTE:
|
462
|
+
return cls._check_compute_credentials()
|
463
|
+
elif cloud_capability == CloudCapability.STORAGE:
|
464
|
+
return cls._check_storage_credentials()
|
465
|
+
assert_never(cloud_capability)
|
445
466
|
|
446
467
|
@classmethod
|
447
|
-
def
|
448
|
-
"""Checks if the user has access credentials to
|
468
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
469
|
+
"""Checks if the user has access credentials to
|
470
|
+
this cloud's compute service."""
|
471
|
+
raise exceptions.NotSupportedError(
|
472
|
+
f'{cls._REPR} does not support {CloudCapability.COMPUTE.value}.')
|
449
473
|
|
450
|
-
|
451
|
-
|
452
|
-
"""
|
453
|
-
|
454
|
-
# unless it overrides this method.
|
474
|
+
@classmethod
|
475
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
476
|
+
"""Checks if the user has access credentials to
|
477
|
+
this cloud's storage service."""
|
455
478
|
raise exceptions.NotSupportedError(
|
456
|
-
f'{cls._REPR} does not support
|
479
|
+
f'{cls._REPR} does not support {CloudCapability.STORAGE.value}.')
|
457
480
|
|
458
481
|
# TODO(zhwu): Make the return type immutable.
|
459
482
|
@classmethod
|
sky/clouds/cudo.py
CHANGED
@@ -267,8 +267,9 @@ class Cudo(clouds.Cloud):
|
|
267
267
|
fuzzy_candidate_list, None)
|
268
268
|
|
269
269
|
@classmethod
|
270
|
-
def
|
271
|
-
|
270
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
271
|
+
"""Checks if the user has access credentials to
|
272
|
+
Cudo's compute service."""
|
272
273
|
try:
|
273
274
|
# pylint: disable=import-outside-toplevel,unused-import
|
274
275
|
from cudo_compute import cudo_api
|
sky/clouds/do.py
CHANGED
@@ -259,8 +259,9 @@ class DO(clouds.Cloud):
|
|
259
259
|
fuzzy_candidate_list, None)
|
260
260
|
|
261
261
|
@classmethod
|
262
|
-
def
|
263
|
-
"""Verify that the user has valid credentials for
|
262
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
263
|
+
"""Verify that the user has valid credentials for
|
264
|
+
DO's compute service."""
|
264
265
|
|
265
266
|
try:
|
266
267
|
do.exceptions()
|
sky/clouds/fluidstack.py
CHANGED
@@ -255,8 +255,9 @@ class Fluidstack(clouds.Cloud):
|
|
255
255
|
fuzzy_candidate_list, None)
|
256
256
|
|
257
257
|
@classmethod
|
258
|
-
def
|
259
|
-
|
258
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
259
|
+
"""Checks if the user has access credentials to
|
260
|
+
FluidStack's compute service."""
|
260
261
|
try:
|
261
262
|
assert os.path.exists(
|
262
263
|
os.path.expanduser(fluidstack_utils.FLUIDSTACK_API_KEY_PATH))
|
sky/clouds/gcp.py
CHANGED
@@ -719,9 +719,9 @@ class GCP(clouds.Cloud):
|
|
719
719
|
return DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH
|
720
720
|
|
721
721
|
@classmethod
|
722
|
-
def
|
723
|
-
"""Checks if the user has
|
724
|
-
return cls._check_credentials(
|
722
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
723
|
+
"""Checks if the user has access credentials to this cloud's compute service."""
|
724
|
+
return cls._check_credentials(
|
725
725
|
[
|
726
726
|
('compute', 'Compute Engine'),
|
727
727
|
('cloudresourcemanager', 'Cloud Resource Manager'),
|
@@ -731,12 +731,11 @@ class GCP(clouds.Cloud):
|
|
731
731
|
gcp_utils.get_minimal_compute_permissions())
|
732
732
|
|
733
733
|
@classmethod
|
734
|
-
def
|
735
|
-
"""Checks if the user has
|
736
|
-
return cls._check_credentials(
|
737
|
-
[
|
738
|
-
|
739
|
-
], gcp_utils.get_minimal_storage_permissions())
|
734
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
735
|
+
"""Checks if the user has access credentials to this cloud's storage service."""
|
736
|
+
return cls._check_credentials(
|
737
|
+
[('storage', 'Cloud Storage')],
|
738
|
+
gcp_utils.get_minimal_storage_permissions())
|
740
739
|
|
741
740
|
@classmethod
|
742
741
|
def _check_credentials(
|
sky/clouds/ibm.py
CHANGED
@@ -395,7 +395,21 @@ class IBM(clouds.Cloud):
|
|
395
395
|
return image_size
|
396
396
|
|
397
397
|
@classmethod
|
398
|
-
def
|
398
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
399
|
+
"""Checks if the user has access credentials to
|
400
|
+
IBM's compute service."""
|
401
|
+
return cls._check_credentials()
|
402
|
+
|
403
|
+
@classmethod
|
404
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
405
|
+
"""Checks if the user has access credentials to
|
406
|
+
IBM's storage service."""
|
407
|
+
# TODO(seungjin): Implement separate check for
|
408
|
+
# if the user has access to IBM COS.
|
409
|
+
return cls._check_credentials()
|
410
|
+
|
411
|
+
@classmethod
|
412
|
+
def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
|
399
413
|
"""Checks if the user has access credentials to this cloud."""
|
400
414
|
|
401
415
|
required_fields = ['iam_api_key', 'resource_group_id']
|
@@ -433,11 +447,6 @@ class IBM(clouds.Cloud):
|
|
433
447
|
except Exception as e:
|
434
448
|
return (False, f'{str(e)}' + help_str)
|
435
449
|
|
436
|
-
@classmethod
|
437
|
-
def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
438
|
-
# TODO(seungjin): Check if the user has access to IBM COS.
|
439
|
-
return cls.check_credentials()
|
440
|
-
|
441
450
|
def get_credential_file_mounts(self) -> Dict[str, str]:
|
442
451
|
"""Returns a {remote:local} credential path mapping
|
443
452
|
written to the cluster's file_mounts segment
|
sky/clouds/kubernetes.py
CHANGED
@@ -655,7 +655,9 @@ class Kubernetes(clouds.Cloud):
|
|
655
655
|
[], None)
|
656
656
|
|
657
657
|
@classmethod
|
658
|
-
def
|
658
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
659
|
+
"""Checks if the user has access credentials to
|
660
|
+
Kubernetes."""
|
659
661
|
# Test using python API
|
660
662
|
try:
|
661
663
|
existing_allowed_contexts = cls.existing_allowed_contexts()
|
sky/clouds/lambda_cloud.py
CHANGED
@@ -241,7 +241,9 @@ class Lambda(clouds.Cloud):
|
|
241
241
|
fuzzy_candidate_list, None)
|
242
242
|
|
243
243
|
@classmethod
|
244
|
-
def
|
244
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
245
|
+
"""Checks if the user has access credentials to
|
246
|
+
Lambda's compute service."""
|
245
247
|
try:
|
246
248
|
lambda_utils.LambdaCloudClient().list_instances()
|
247
249
|
except (AssertionError, KeyError, lambda_utils.LambdaCloudError):
|
sky/clouds/nebius.py
CHANGED
@@ -250,8 +250,9 @@ class Nebius(clouds.Cloud):
|
|
250
250
|
fuzzy_candidate_list, None)
|
251
251
|
|
252
252
|
@classmethod
|
253
|
-
def
|
254
|
-
"""
|
253
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
254
|
+
"""Checks if the user has access credentials to
|
255
|
+
Nebius's compute service."""
|
255
256
|
logging.debug('Nebius cloud check credentials')
|
256
257
|
token_cred_msg = (' Credentials can be set up by running: \n'\
|
257
258
|
f' $ nebius iam get-access-token > {nebius.NEBIUS_IAM_TOKEN_PATH} \n'\
|
@@ -279,10 +280,13 @@ class Nebius(clouds.Cloud):
|
|
279
280
|
return True, None
|
280
281
|
|
281
282
|
def get_credential_file_mounts(self) -> Dict[str, str]:
|
282
|
-
|
283
|
+
credential_file_mounts = {
|
283
284
|
f'~/.nebius/{filename}': f'~/.nebius/{filename}'
|
284
285
|
for filename in _CREDENTIAL_FILES
|
285
286
|
}
|
287
|
+
credential_file_mounts['~/.aws/credentials'] = '~/.aws/credentials'
|
288
|
+
|
289
|
+
return credential_file_mounts
|
286
290
|
|
287
291
|
@classmethod
|
288
292
|
def get_current_user_identity(cls) -> Optional[List[str]]:
|
sky/clouds/oci.py
CHANGED
@@ -396,7 +396,21 @@ class OCI(clouds.Cloud):
|
|
396
396
|
fuzzy_candidate_list, None)
|
397
397
|
|
398
398
|
@classmethod
|
399
|
-
def
|
399
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
400
|
+
"""Checks if the user has access credentials to
|
401
|
+
OCI's compute service."""
|
402
|
+
return cls._check_credentials()
|
403
|
+
|
404
|
+
@classmethod
|
405
|
+
def _check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
406
|
+
"""Checks if the user has access credentials to
|
407
|
+
OCI's storage service."""
|
408
|
+
# TODO(seungjin): Implement separate check for
|
409
|
+
# if the user has access to OCI Object Storage.
|
410
|
+
return cls._check_credentials()
|
411
|
+
|
412
|
+
@classmethod
|
413
|
+
def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
|
400
414
|
"""Checks if the user has access credentials to this cloud."""
|
401
415
|
|
402
416
|
short_credential_help_str = (
|
@@ -456,11 +470,6 @@ class OCI(clouds.Cloud):
|
|
456
470
|
f'{cls._INDENT_PREFIX}Error details: '
|
457
471
|
f'{common_utils.format_exception(e, use_bracket=True)}')
|
458
472
|
|
459
|
-
@classmethod
|
460
|
-
def check_storage_credentials(cls) -> Tuple[bool, Optional[str]]:
|
461
|
-
# TODO(seungjin): Check if the user has access to OCI Object Storage.
|
462
|
-
return cls.check_credentials()
|
463
|
-
|
464
473
|
@classmethod
|
465
474
|
def check_disk_tier(
|
466
475
|
cls, instance_type: Optional[str],
|
sky/clouds/paperspace.py
CHANGED
@@ -249,8 +249,9 @@ class Paperspace(clouds.Cloud):
|
|
249
249
|
fuzzy_candidate_list, None)
|
250
250
|
|
251
251
|
@classmethod
|
252
|
-
def
|
253
|
-
"""
|
252
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
253
|
+
"""Checks if the user has access credentials to
|
254
|
+
Paperspace's compute service."""
|
254
255
|
try:
|
255
256
|
# attempt to make a CURL request for listing instances
|
256
257
|
utils.PaperspaceCloudClient().list_instances()
|
sky/clouds/runpod.py
CHANGED
@@ -248,7 +248,13 @@ class RunPod(clouds.Cloud):
|
|
248
248
|
fuzzy_candidate_list, None)
|
249
249
|
|
250
250
|
@classmethod
|
251
|
-
def
|
251
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
252
|
+
"""Checks if the user has access credentials to
|
253
|
+
RunPod's compute service."""
|
254
|
+
return cls._check_credentials()
|
255
|
+
|
256
|
+
@classmethod
|
257
|
+
def _check_credentials(cls) -> Tuple[bool, Optional[str]]:
|
252
258
|
""" Verify that the user has valid credentials for RunPod. """
|
253
259
|
try:
|
254
260
|
import runpod # pylint: disable=import-outside-toplevel
|
sky/clouds/scp.py
CHANGED
@@ -312,7 +312,9 @@ class SCP(clouds.Cloud):
|
|
312
312
|
fuzzy_candidate_list, None)
|
313
313
|
|
314
314
|
@classmethod
|
315
|
-
def
|
315
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
316
|
+
"""Checks if the user has access credentials to
|
317
|
+
SCP's compute service."""
|
316
318
|
try:
|
317
319
|
scp_utils.SCPClient().list_instances()
|
318
320
|
except (AssertionError, KeyError, scp_utils.SCPClientError,
|
@@ -12,6 +12,7 @@ from sky import clouds as sky_clouds
|
|
12
12
|
from sky import sky_logging
|
13
13
|
from sky.adaptors import common as adaptors_common
|
14
14
|
from sky.adaptors import kubernetes
|
15
|
+
from sky.clouds import cloud
|
15
16
|
from sky.clouds.service_catalog import CloudFilter
|
16
17
|
from sky.clouds.service_catalog import common
|
17
18
|
from sky.provision.kubernetes import utils as kubernetes_utils
|
@@ -132,7 +133,8 @@ def _list_accelerators(
|
|
132
133
|
|
133
134
|
# First check if Kubernetes is enabled. This ensures k8s python client is
|
134
135
|
# installed. Do not put any k8s-specific logic before this check.
|
135
|
-
enabled_clouds = sky_check.get_cached_enabled_clouds_or_refresh(
|
136
|
+
enabled_clouds = sky_check.get_cached_enabled_clouds_or_refresh(
|
137
|
+
cloud.CloudCapability.COMPUTE)
|
136
138
|
if not sky_clouds.cloud_in_iterable(sky_clouds.Kubernetes(),
|
137
139
|
enabled_clouds):
|
138
140
|
return {}, {}, {}
|
sky/clouds/vast.py
CHANGED
@@ -234,8 +234,9 @@ class Vast(clouds.Cloud):
|
|
234
234
|
fuzzy_candidate_list, None)
|
235
235
|
|
236
236
|
@classmethod
|
237
|
-
def
|
238
|
-
"""
|
237
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
238
|
+
"""Checks if the user has valid credentials for
|
239
|
+
Vast's compute service. """
|
239
240
|
try:
|
240
241
|
import vastai_sdk as _vast # pylint: disable=import-outside-toplevel
|
241
242
|
vast = _vast.VastAI()
|
sky/clouds/vsphere.py
CHANGED
@@ -254,8 +254,9 @@ class Vsphere(clouds.Cloud):
|
|
254
254
|
fuzzy_candidate_list, None)
|
255
255
|
|
256
256
|
@classmethod
|
257
|
-
def
|
258
|
-
"""Checks if the user has access credentials to
|
257
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
258
|
+
"""Checks if the user has access credentials to
|
259
|
+
vSphere's compute service."""
|
259
260
|
|
260
261
|
try:
|
261
262
|
# pylint: disable=import-outside-toplevel,unused-import
|
sky/core.py
CHANGED
@@ -19,6 +19,7 @@ from sky import optimizer
|
|
19
19
|
from sky import sky_logging
|
20
20
|
from sky import task as task_lib
|
21
21
|
from sky.backends import backend_utils
|
22
|
+
from sky.clouds import cloud as sky_cloud
|
22
23
|
from sky.clouds import service_catalog
|
23
24
|
from sky.jobs.server import core as managed_jobs_core
|
24
25
|
from sky.provision.kubernetes import constants as kubernetes_constants
|
@@ -1001,7 +1002,8 @@ def storage_delete(name: str) -> None:
|
|
1001
1002
|
# ===================
|
1002
1003
|
@usage_lib.entrypoint
|
1003
1004
|
def enabled_clouds() -> List[clouds.Cloud]:
|
1004
|
-
return global_user_state.get_cached_enabled_clouds(
|
1005
|
+
return global_user_state.get_cached_enabled_clouds(
|
1006
|
+
sky_cloud.CloudCapability.COMPUTE)
|
1005
1007
|
|
1006
1008
|
|
1007
1009
|
@usage_lib.entrypoint
|
@@ -1136,7 +1138,7 @@ def local_down() -> None:
|
|
1136
1138
|
ux_utils.spinner_message('Running sky check...')):
|
1137
1139
|
sky_check.check(clouds=['kubernetes'],
|
1138
1140
|
quiet=True,
|
1139
|
-
capability=
|
1141
|
+
capability=sky_cloud.CloudCapability.COMPUTE)
|
1140
1142
|
logger.info(
|
1141
1143
|
ux_utils.finishing_message('Local cluster removed.',
|
1142
1144
|
log_path=log_path,
|
sky/data/data_transfer.py
CHANGED
@@ -14,6 +14,7 @@ Currently implemented:
|
|
14
14
|
TODO:
|
15
15
|
- All combinations of Azure Transfer
|
16
16
|
- All combinations of R2 Transfer
|
17
|
+
- All combinations of Nebius Transfer
|
17
18
|
- GCS -> S3
|
18
19
|
"""
|
19
20
|
import json
|
@@ -135,6 +136,35 @@ def s3_to_r2(s3_bucket_name: str, r2_bucket_name: str) -> None:
|
|
135
136
|
'a local source for the storage object.')
|
136
137
|
|
137
138
|
|
139
|
+
def s3_to_nebius(s3_bucket_name: str, nebius_bucket_name: str) -> None:
|
140
|
+
"""Creates a one-time transfer from Amazon S3 to Nebius Object Storage.
|
141
|
+
|
142
|
+
it will block until the transfer is complete.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
s3_bucket_name: str; Name of the Amazon S3 Bucket
|
146
|
+
nebius_bucket_name: str; Name of the Cloudflare R2 Bucket
|
147
|
+
"""
|
148
|
+
raise NotImplementedError('Moving data directly from clouds to Nebius is '
|
149
|
+
'currently not supported. Please specify '
|
150
|
+
'a local source for the storage object.')
|
151
|
+
|
152
|
+
|
153
|
+
def r2_to_nebius(r2_bucket_name: str, nebius_bucket_name: str) -> None:
|
154
|
+
"""Creates a one-time transfer from Cloudflare R2 Bucket to
|
155
|
+
Nebius Object Storage.
|
156
|
+
|
157
|
+
it will block until the transfer is complete.
|
158
|
+
|
159
|
+
Args:
|
160
|
+
r2_bucket_name: str; Name of the Amazon S3 Bucket
|
161
|
+
nebius_bucket_name: str; Name of the Cloudflare R2 Bucket
|
162
|
+
"""
|
163
|
+
raise NotImplementedError('Moving data directly from clouds to Nebius is '
|
164
|
+
'currently not supported. Please specify '
|
165
|
+
'a local source for the storage object.')
|
166
|
+
|
167
|
+
|
138
168
|
def gcs_to_s3(gs_bucket_name: str, s3_bucket_name: str) -> None:
|
139
169
|
"""Creates a one-time transfer from Google Cloud Storage to Amazon S3.
|
140
170
|
|
@@ -160,6 +190,19 @@ def gcs_to_r2(gs_bucket_name: str, r2_bucket_name: str) -> None:
|
|
160
190
|
'a local source for the storage object.')
|
161
191
|
|
162
192
|
|
193
|
+
def gcs_to_nebius(gs_bucket_name: str, nebius_bucket_name: str) -> None:
|
194
|
+
"""Creates a one-time transfer from Google Cloud Storage to
|
195
|
+
Nebius Object Storage.
|
196
|
+
|
197
|
+
Args:
|
198
|
+
gs_bucket_name: str; Name of the Google Cloud Storage Bucket
|
199
|
+
nebius_bucket_name: str; Name of the Nebius Bucket
|
200
|
+
"""
|
201
|
+
raise NotImplementedError('Moving data directly from clouds to Nebius is '
|
202
|
+
'currently not supported. Please specify '
|
203
|
+
'a local source for the storage object.')
|
204
|
+
|
205
|
+
|
163
206
|
def r2_to_gcs(r2_bucket_name: str, gs_bucket_name: str) -> None:
|
164
207
|
"""Creates a one-time transfer from Cloudflare R2 to Google Cloud Storage.
|
165
208
|
|
@@ -175,6 +218,22 @@ def r2_to_gcs(r2_bucket_name: str, gs_bucket_name: str) -> None:
|
|
175
218
|
'a local source for the storage object.')
|
176
219
|
|
177
220
|
|
221
|
+
def nebius_to_gcs(nebius_bucket_name: str, gs_bucket_name: str) -> None:
|
222
|
+
"""Creates a one-time transfer from Nebius Object Storage
|
223
|
+
to Google Cloud Storage.
|
224
|
+
|
225
|
+
Can be viewed from: https://console.cloud.google.com/transfer/cloud
|
226
|
+
it will block until the transfer is complete.
|
227
|
+
|
228
|
+
Args:
|
229
|
+
nebius_bucket_name: str; Name of the Nebius Object Storage
|
230
|
+
gs_bucket_name: str; Name of the Google Cloud Storage Bucket
|
231
|
+
"""
|
232
|
+
raise NotImplementedError('Moving data directly from Nebius to clouds is '
|
233
|
+
'currently not supported. Please specify '
|
234
|
+
'a local source for the storage object.')
|
235
|
+
|
236
|
+
|
178
237
|
def r2_to_s3(r2_bucket_name: str, s3_bucket_name: str) -> None:
|
179
238
|
"""Creates a one-time transfer from Amazon S3 to Google Cloud Storage.
|
180
239
|
|
@@ -190,6 +249,22 @@ def r2_to_s3(r2_bucket_name: str, s3_bucket_name: str) -> None:
|
|
190
249
|
'a local source for the storage object.')
|
191
250
|
|
192
251
|
|
252
|
+
def nebius_to_s3(nebius_bucket_name: str, s3_bucket_name: str) -> None:
|
253
|
+
"""Creates a one-time transfer from Nebius Object Storage
|
254
|
+
to Amazon S3 Bucket.
|
255
|
+
|
256
|
+
Can be viewed from: https://console.cloud.google.com/transfer/cloud
|
257
|
+
it will block until the transfer is complete.
|
258
|
+
|
259
|
+
Args:
|
260
|
+
nebius_bucket_name: str; Name of the Nebius Object Storage\
|
261
|
+
s3_bucket_name: str; Name of the Amazon S3 Bucket
|
262
|
+
"""
|
263
|
+
raise NotImplementedError('Moving data directly from R2 to clouds is '
|
264
|
+
'currently not supported. Please specify '
|
265
|
+
'a local source for the storage object.')
|
266
|
+
|
267
|
+
|
193
268
|
def _add_bucket_iam_member(bucket_name: str, role: str, member: str) -> None:
|
194
269
|
storage_client = gcp.storage_client()
|
195
270
|
bucket = storage_client.bucket(bucket_name)
|
sky/data/data_utils.py
CHANGED
@@ -20,6 +20,7 @@ from sky.adaptors import azure
|
|
20
20
|
from sky.adaptors import cloudflare
|
21
21
|
from sky.adaptors import gcp
|
22
22
|
from sky.adaptors import ibm
|
23
|
+
from sky.adaptors import nebius
|
23
24
|
from sky.skylet import log_lib
|
24
25
|
from sky.utils import common_utils
|
25
26
|
from sky.utils import ux_utils
|
@@ -94,6 +95,18 @@ def split_r2_path(r2_path: str) -> Tuple[str, str]:
|
|
94
95
|
return bucket, key
|
95
96
|
|
96
97
|
|
98
|
+
def split_nebius_path(nebius_path: str) -> Tuple[str, str]:
|
99
|
+
"""Splits Nebius Path into Bucket name and Relative Path to Bucket
|
100
|
+
|
101
|
+
Args:
|
102
|
+
nebius_path: str; Nebius Path, e.g. nebius://imagenet/train/
|
103
|
+
"""
|
104
|
+
path_parts = nebius_path.replace('nebius://', '').split('/')
|
105
|
+
bucket = path_parts.pop(0)
|
106
|
+
key = '/'.join(path_parts)
|
107
|
+
return bucket, key
|
108
|
+
|
109
|
+
|
97
110
|
def split_cos_path(s3_path: str) -> Tuple[str, str, str]:
|
98
111
|
"""returns extracted region, bucket name and bucket path to data
|
99
112
|
from the specified cos bucket's url.
|
@@ -307,6 +320,16 @@ def create_r2_client(region: str = 'auto') -> Client:
|
|
307
320
|
return cloudflare.client('s3', region)
|
308
321
|
|
309
322
|
|
323
|
+
def create_nebius_client(region: Optional[str]) -> Client:
|
324
|
+
"""Helper method that connects to Boto3 client for Nebius Object Storage
|
325
|
+
|
326
|
+
Args:
|
327
|
+
region: str; Region for Nebius Object Storage
|
328
|
+
"""
|
329
|
+
region = region if region is not None else nebius.DEFAULT_REGION
|
330
|
+
return nebius.client('s3', region)
|
331
|
+
|
332
|
+
|
310
333
|
def verify_r2_bucket(name: str) -> bool:
|
311
334
|
"""Helper method that checks if the R2 bucket exists
|
312
335
|
|
@@ -318,6 +341,17 @@ def verify_r2_bucket(name: str) -> bool:
|
|
318
341
|
return bucket in r2.buckets.all()
|
319
342
|
|
320
343
|
|
344
|
+
def verify_nebius_bucket(name: str) -> bool:
|
345
|
+
"""Helper method that checks if the Nebius bucket exists
|
346
|
+
|
347
|
+
Args:
|
348
|
+
name: str; Name of Nebius Object Storage (without nebius:// prefix)
|
349
|
+
"""
|
350
|
+
nebius_s = nebius.resource('s3')
|
351
|
+
bucket = nebius_s.Bucket(name)
|
352
|
+
return bucket in nebius_s.buckets.all()
|
353
|
+
|
354
|
+
|
321
355
|
def verify_ibm_cos_bucket(name: str) -> bool:
|
322
356
|
"""Helper method that checks if the cos bucket exists
|
323
357
|
|
sky/data/mounting_utils.py
CHANGED
@@ -54,6 +54,24 @@ def get_s3_mount_cmd(bucket_name: str,
|
|
54
54
|
return mount_cmd
|
55
55
|
|
56
56
|
|
57
|
+
def get_nebius_mount_cmd(nebius_profile_name: str,
|
58
|
+
endpoint_url: str,
|
59
|
+
bucket_name: str,
|
60
|
+
mount_path: str,
|
61
|
+
_bucket_sub_path: Optional[str] = None) -> str:
|
62
|
+
"""Returns a command to install Nebius mount utility goofys."""
|
63
|
+
if _bucket_sub_path is None:
|
64
|
+
_bucket_sub_path = ''
|
65
|
+
else:
|
66
|
+
_bucket_sub_path = f':{_bucket_sub_path}'
|
67
|
+
mount_cmd = (f'AWS_PROFILE={nebius_profile_name} goofys -o allow_other '
|
68
|
+
f'--stat-cache-ttl {_STAT_CACHE_TTL} '
|
69
|
+
f'--type-cache-ttl {_TYPE_CACHE_TTL} '
|
70
|
+
f'--endpoint {endpoint_url} '
|
71
|
+
f'{bucket_name}{_bucket_sub_path} {mount_path}')
|
72
|
+
return mount_cmd
|
73
|
+
|
74
|
+
|
57
75
|
def get_gcs_mount_install_cmd() -> str:
|
58
76
|
"""Returns a command to install GCS mount utility gcsfuse."""
|
59
77
|
install_cmd = ('ARCH=$(uname -m) && '
|