skypilot-nightly 1.0.0.dev20250905__py3-none-any.whl → 1.0.0.dev20251203__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 +10 -2
- sky/adaptors/aws.py +81 -16
- sky/adaptors/common.py +25 -2
- sky/adaptors/coreweave.py +278 -0
- sky/adaptors/do.py +8 -2
- sky/adaptors/gcp.py +11 -0
- sky/adaptors/ibm.py +5 -2
- sky/adaptors/kubernetes.py +64 -0
- sky/adaptors/nebius.py +3 -1
- sky/adaptors/primeintellect.py +1 -0
- sky/adaptors/seeweb.py +183 -0
- sky/adaptors/shadeform.py +89 -0
- sky/admin_policy.py +20 -0
- sky/authentication.py +157 -263
- sky/backends/__init__.py +3 -2
- sky/backends/backend.py +11 -3
- sky/backends/backend_utils.py +588 -184
- sky/backends/cloud_vm_ray_backend.py +1088 -904
- sky/backends/local_docker_backend.py +9 -5
- sky/backends/task_codegen.py +633 -0
- sky/backends/wheel_utils.py +18 -0
- sky/catalog/__init__.py +8 -0
- sky/catalog/aws_catalog.py +4 -0
- sky/catalog/common.py +19 -1
- sky/catalog/data_fetchers/fetch_aws.py +102 -80
- sky/catalog/data_fetchers/fetch_gcp.py +30 -3
- sky/catalog/data_fetchers/fetch_nebius.py +9 -6
- sky/catalog/data_fetchers/fetch_runpod.py +698 -0
- sky/catalog/data_fetchers/fetch_seeweb.py +329 -0
- sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
- sky/catalog/kubernetes_catalog.py +24 -28
- sky/catalog/primeintellect_catalog.py +95 -0
- sky/catalog/runpod_catalog.py +5 -1
- sky/catalog/seeweb_catalog.py +184 -0
- sky/catalog/shadeform_catalog.py +165 -0
- sky/check.py +73 -43
- sky/client/cli/command.py +675 -412
- sky/client/cli/flags.py +4 -2
- sky/{volumes/utils.py → client/cli/table_utils.py} +111 -13
- sky/client/cli/utils.py +79 -0
- sky/client/common.py +12 -2
- sky/client/sdk.py +132 -63
- sky/client/sdk_async.py +34 -33
- sky/cloud_stores.py +82 -3
- sky/clouds/__init__.py +6 -0
- sky/clouds/aws.py +337 -129
- sky/clouds/azure.py +24 -18
- sky/clouds/cloud.py +40 -13
- sky/clouds/cudo.py +16 -13
- sky/clouds/do.py +9 -7
- sky/clouds/fluidstack.py +12 -5
- sky/clouds/gcp.py +14 -7
- sky/clouds/hyperbolic.py +12 -5
- sky/clouds/ibm.py +12 -5
- sky/clouds/kubernetes.py +80 -45
- sky/clouds/lambda_cloud.py +12 -5
- sky/clouds/nebius.py +23 -9
- sky/clouds/oci.py +19 -12
- sky/clouds/paperspace.py +4 -1
- sky/clouds/primeintellect.py +317 -0
- sky/clouds/runpod.py +85 -24
- sky/clouds/scp.py +12 -8
- sky/clouds/seeweb.py +477 -0
- sky/clouds/shadeform.py +400 -0
- sky/clouds/ssh.py +4 -2
- sky/clouds/utils/scp_utils.py +61 -50
- sky/clouds/vast.py +33 -27
- sky/clouds/vsphere.py +14 -16
- sky/core.py +174 -165
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/96_E2yl3QAiIJGOYCkSpB/_buildManifest.js +1 -0
- sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
- sky/dashboard/out/_next/static/chunks/1871-7e202677c42f43fe.js +6 -0
- sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
- sky/dashboard/out/_next/static/chunks/2369.fc20f0c2c8ed9fe7.js +15 -0
- sky/dashboard/out/_next/static/chunks/2755.edd818326d489a1d.js +26 -0
- sky/dashboard/out/_next/static/chunks/3294.20a8540fe697d5ee.js +1 -0
- sky/dashboard/out/_next/static/chunks/3785.7e245f318f9d1121.js +1 -0
- sky/dashboard/out/_next/static/chunks/{6601-06114c982db410b6.js → 3800-7b45f9fbb6308557.js} +1 -1
- sky/dashboard/out/_next/static/chunks/4725.172ede95d1b21022.js +1 -0
- sky/dashboard/out/_next/static/chunks/4937.a2baa2df5572a276.js +15 -0
- sky/dashboard/out/_next/static/chunks/6212-7bd06f60ba693125.js +13 -0
- sky/dashboard/out/_next/static/chunks/6856-8f27d1c10c98def8.js +1 -0
- sky/dashboard/out/_next/static/chunks/6990-9146207c4567fdfd.js +1 -0
- sky/dashboard/out/_next/static/chunks/7359-c8d04e06886000b3.js +30 -0
- sky/dashboard/out/_next/static/chunks/7615-019513abc55b3b47.js +1 -0
- sky/dashboard/out/_next/static/chunks/8640.5b9475a2d18c5416.js +16 -0
- sky/dashboard/out/_next/static/chunks/8969-452f9d5cbdd2dc73.js +1 -0
- sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
- sky/dashboard/out/_next/static/chunks/9353-cff34f7e773b2e2b.js +1 -0
- sky/dashboard/out/_next/static/chunks/9360.a536cf6b1fa42355.js +31 -0
- sky/dashboard/out/_next/static/chunks/9847.3aaca6bb33455140.js +30 -0
- sky/dashboard/out/_next/static/chunks/pages/{_app-ce361c6959bc2001.js → _app-bde01e4a2beec258.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-792db96d918c98c9.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-abfcac9c137aa543.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters-ee39056f9851a3ff.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-6563820e094f68ca.js → [context]-c0b5935149902e6f.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{infra-aabba60d57826e0f.js → infra-aed0ea19df7cf961.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-d66997e2bfc837cf.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-9faf940b253e3e06.js +21 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs-2072b48b617989c9.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/users-f42674164aa73423.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/volumes-b84b948ff357c43e.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-af76bb06dbb3954f.js → [name]-84a40f8c7c627fe4.js} +1 -1
- sky/dashboard/out/_next/static/chunks/pages/{workspaces-7598c33a746cdc91.js → workspaces-531b2f8c4bf89f82.js} +1 -1
- sky/dashboard/out/_next/static/chunks/webpack-64e05f17bf2cf8ce.js +1 -0
- sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
- sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
- sky/dashboard/out/clusters/[cluster].html +1 -1
- sky/dashboard/out/clusters.html +1 -1
- sky/dashboard/out/config.html +1 -1
- sky/dashboard/out/index.html +1 -1
- sky/dashboard/out/infra/[context].html +1 -1
- sky/dashboard/out/infra.html +1 -1
- sky/dashboard/out/jobs/[job].html +1 -1
- sky/dashboard/out/jobs/pools/[pool].html +1 -1
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -1
- sky/dashboard/out/volumes.html +1 -1
- sky/dashboard/out/workspace/new.html +1 -1
- sky/dashboard/out/workspaces/[name].html +1 -1
- sky/dashboard/out/workspaces.html +1 -1
- sky/data/data_utils.py +92 -1
- sky/data/mounting_utils.py +162 -29
- sky/data/storage.py +200 -19
- sky/data/storage_utils.py +10 -45
- sky/exceptions.py +18 -7
- sky/execution.py +74 -31
- sky/global_user_state.py +605 -191
- sky/jobs/__init__.py +2 -0
- sky/jobs/client/sdk.py +101 -4
- sky/jobs/client/sdk_async.py +31 -5
- sky/jobs/constants.py +15 -8
- sky/jobs/controller.py +726 -284
- sky/jobs/file_content_utils.py +128 -0
- sky/jobs/log_gc.py +193 -0
- sky/jobs/recovery_strategy.py +250 -100
- sky/jobs/scheduler.py +271 -173
- sky/jobs/server/core.py +367 -114
- sky/jobs/server/server.py +81 -35
- sky/jobs/server/utils.py +89 -35
- sky/jobs/state.py +1498 -620
- sky/jobs/utils.py +771 -306
- sky/logs/agent.py +40 -5
- sky/logs/aws.py +9 -19
- sky/metrics/utils.py +282 -39
- sky/optimizer.py +1 -1
- sky/provision/__init__.py +37 -1
- sky/provision/aws/config.py +34 -13
- sky/provision/aws/instance.py +5 -2
- sky/provision/azure/instance.py +5 -3
- sky/provision/common.py +2 -0
- sky/provision/cudo/instance.py +4 -3
- sky/provision/do/instance.py +4 -3
- sky/provision/docker_utils.py +97 -26
- sky/provision/fluidstack/instance.py +6 -5
- sky/provision/gcp/config.py +6 -1
- sky/provision/gcp/instance.py +4 -2
- sky/provision/hyperbolic/instance.py +4 -2
- sky/provision/instance_setup.py +66 -20
- sky/provision/kubernetes/__init__.py +2 -0
- sky/provision/kubernetes/config.py +7 -44
- sky/provision/kubernetes/constants.py +0 -1
- sky/provision/kubernetes/instance.py +609 -213
- sky/provision/kubernetes/manifests/fusermount-server-daemonset.yaml +1 -2
- sky/provision/kubernetes/network.py +12 -8
- sky/provision/kubernetes/network_utils.py +8 -25
- sky/provision/kubernetes/utils.py +382 -418
- sky/provision/kubernetes/volume.py +150 -18
- sky/provision/lambda_cloud/instance.py +16 -13
- sky/provision/nebius/instance.py +6 -2
- sky/provision/nebius/utils.py +103 -86
- sky/provision/oci/instance.py +4 -2
- sky/provision/paperspace/instance.py +4 -3
- sky/provision/primeintellect/__init__.py +10 -0
- sky/provision/primeintellect/config.py +11 -0
- sky/provision/primeintellect/instance.py +454 -0
- sky/provision/primeintellect/utils.py +398 -0
- sky/provision/provisioner.py +30 -9
- sky/provision/runpod/__init__.py +2 -0
- sky/provision/runpod/instance.py +4 -3
- sky/provision/runpod/volume.py +69 -13
- sky/provision/scp/instance.py +307 -130
- sky/provision/seeweb/__init__.py +11 -0
- sky/provision/seeweb/config.py +13 -0
- sky/provision/seeweb/instance.py +812 -0
- sky/provision/shadeform/__init__.py +11 -0
- sky/provision/shadeform/config.py +12 -0
- sky/provision/shadeform/instance.py +351 -0
- sky/provision/shadeform/shadeform_utils.py +83 -0
- sky/provision/vast/instance.py +5 -3
- sky/provision/volume.py +164 -0
- sky/provision/vsphere/common/ssl_helper.py +1 -1
- sky/provision/vsphere/common/vapiconnect.py +2 -1
- sky/provision/vsphere/common/vim_utils.py +3 -2
- sky/provision/vsphere/instance.py +8 -6
- sky/provision/vsphere/vsphere_utils.py +8 -1
- sky/resources.py +11 -3
- sky/schemas/api/responses.py +107 -6
- sky/schemas/db/global_user_state/008_skylet_ssh_tunnel_metadata.py +34 -0
- sky/schemas/db/global_user_state/009_last_activity_and_launched_at.py +89 -0
- sky/schemas/db/global_user_state/010_save_ssh_key.py +66 -0
- sky/schemas/db/global_user_state/011_is_ephemeral.py +34 -0
- sky/schemas/db/kv_cache/001_initial_schema.py +29 -0
- sky/schemas/db/serve_state/002_yaml_content.py +34 -0
- sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
- sky/schemas/db/spot_jobs/002_cluster_pool.py +3 -3
- sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
- sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
- sky/schemas/db/spot_jobs/006_controller_pid_started_at.py +34 -0
- sky/schemas/db/spot_jobs/007_config_file_content.py +34 -0
- sky/schemas/generated/jobsv1_pb2.py +86 -0
- sky/schemas/generated/jobsv1_pb2.pyi +254 -0
- sky/schemas/generated/jobsv1_pb2_grpc.py +542 -0
- sky/schemas/generated/managed_jobsv1_pb2.py +76 -0
- sky/schemas/generated/managed_jobsv1_pb2.pyi +278 -0
- sky/schemas/generated/managed_jobsv1_pb2_grpc.py +278 -0
- sky/schemas/generated/servev1_pb2.py +58 -0
- sky/schemas/generated/servev1_pb2.pyi +115 -0
- sky/schemas/generated/servev1_pb2_grpc.py +322 -0
- sky/serve/autoscalers.py +2 -0
- sky/serve/client/impl.py +55 -21
- sky/serve/constants.py +4 -3
- sky/serve/controller.py +17 -11
- sky/serve/load_balancing_policies.py +1 -1
- sky/serve/replica_managers.py +219 -142
- sky/serve/serve_rpc_utils.py +179 -0
- sky/serve/serve_state.py +63 -54
- sky/serve/serve_utils.py +145 -109
- sky/serve/server/core.py +46 -25
- sky/serve/server/impl.py +311 -162
- sky/serve/server/server.py +21 -19
- sky/serve/service.py +84 -68
- sky/serve/service_spec.py +45 -7
- sky/server/auth/loopback.py +38 -0
- sky/server/auth/oauth2_proxy.py +12 -7
- sky/server/common.py +47 -24
- sky/server/config.py +62 -28
- sky/server/constants.py +9 -1
- sky/server/daemons.py +109 -38
- sky/server/metrics.py +76 -96
- sky/server/middleware_utils.py +166 -0
- sky/server/requests/executor.py +381 -145
- sky/server/requests/payloads.py +71 -18
- sky/server/requests/preconditions.py +15 -13
- sky/server/requests/request_names.py +121 -0
- sky/server/requests/requests.py +507 -157
- sky/server/requests/serializers/decoders.py +48 -17
- sky/server/requests/serializers/encoders.py +85 -20
- sky/server/requests/threads.py +117 -0
- sky/server/rest.py +116 -24
- sky/server/server.py +420 -172
- sky/server/stream_utils.py +219 -45
- sky/server/uvicorn.py +30 -19
- sky/setup_files/MANIFEST.in +6 -1
- sky/setup_files/alembic.ini +8 -0
- sky/setup_files/dependencies.py +62 -19
- sky/setup_files/setup.py +44 -44
- sky/sky_logging.py +13 -5
- sky/skylet/attempt_skylet.py +106 -24
- sky/skylet/configs.py +3 -1
- sky/skylet/constants.py +111 -26
- sky/skylet/events.py +64 -10
- sky/skylet/job_lib.py +141 -104
- sky/skylet/log_lib.py +233 -5
- sky/skylet/log_lib.pyi +40 -2
- sky/skylet/providers/ibm/node_provider.py +12 -8
- sky/skylet/providers/ibm/vpc_provider.py +13 -12
- sky/skylet/runtime_utils.py +21 -0
- sky/skylet/services.py +524 -0
- sky/skylet/skylet.py +22 -1
- sky/skylet/subprocess_daemon.py +104 -29
- sky/skypilot_config.py +99 -79
- sky/ssh_node_pools/server.py +9 -8
- sky/task.py +221 -104
- sky/templates/aws-ray.yml.j2 +1 -0
- sky/templates/azure-ray.yml.j2 +1 -0
- sky/templates/cudo-ray.yml.j2 +1 -0
- sky/templates/do-ray.yml.j2 +1 -0
- sky/templates/fluidstack-ray.yml.j2 +1 -0
- sky/templates/gcp-ray.yml.j2 +1 -0
- sky/templates/hyperbolic-ray.yml.j2 +1 -0
- sky/templates/ibm-ray.yml.j2 +2 -1
- sky/templates/jobs-controller.yaml.j2 +3 -0
- sky/templates/kubernetes-ray.yml.j2 +196 -55
- sky/templates/lambda-ray.yml.j2 +1 -0
- sky/templates/nebius-ray.yml.j2 +3 -0
- sky/templates/oci-ray.yml.j2 +1 -0
- sky/templates/paperspace-ray.yml.j2 +1 -0
- sky/templates/primeintellect-ray.yml.j2 +72 -0
- sky/templates/runpod-ray.yml.j2 +1 -0
- sky/templates/scp-ray.yml.j2 +1 -0
- sky/templates/seeweb-ray.yml.j2 +171 -0
- sky/templates/shadeform-ray.yml.j2 +73 -0
- sky/templates/vast-ray.yml.j2 +1 -0
- sky/templates/vsphere-ray.yml.j2 +1 -0
- sky/templates/websocket_proxy.py +188 -43
- sky/usage/usage_lib.py +16 -4
- sky/users/permission.py +60 -43
- sky/utils/accelerator_registry.py +6 -3
- sky/utils/admin_policy_utils.py +18 -5
- sky/utils/annotations.py +22 -0
- sky/utils/asyncio_utils.py +78 -0
- sky/utils/atomic.py +1 -1
- sky/utils/auth_utils.py +153 -0
- sky/utils/cli_utils/status_utils.py +12 -7
- sky/utils/cluster_utils.py +28 -6
- sky/utils/command_runner.py +88 -27
- sky/utils/command_runner.pyi +36 -3
- sky/utils/common.py +3 -1
- sky/utils/common_utils.py +37 -4
- sky/utils/config_utils.py +1 -14
- sky/utils/context.py +127 -40
- sky/utils/context_utils.py +73 -18
- sky/utils/controller_utils.py +229 -70
- sky/utils/db/db_utils.py +95 -18
- sky/utils/db/kv_cache.py +149 -0
- sky/utils/db/migration_utils.py +24 -7
- sky/utils/env_options.py +4 -0
- sky/utils/git.py +559 -1
- sky/utils/kubernetes/create_cluster.sh +15 -30
- sky/utils/kubernetes/delete_cluster.sh +10 -7
- sky/utils/kubernetes/{deploy_remote_cluster.py → deploy_ssh_node_pools.py} +258 -380
- sky/utils/kubernetes/generate_kind_config.py +6 -66
- sky/utils/kubernetes/gpu_labeler.py +13 -3
- sky/utils/kubernetes/k8s_gpu_labeler_job.yaml +2 -1
- sky/utils/kubernetes/k8s_gpu_labeler_setup.yaml +16 -16
- sky/utils/kubernetes/kubernetes_deploy_utils.py +213 -194
- sky/utils/kubernetes/rsync_helper.sh +11 -3
- sky/utils/kubernetes_enums.py +7 -15
- sky/utils/lock_events.py +4 -4
- sky/utils/locks.py +128 -31
- sky/utils/log_utils.py +0 -319
- sky/utils/resource_checker.py +13 -10
- sky/utils/resources_utils.py +53 -29
- sky/utils/rich_utils.py +8 -4
- sky/utils/schemas.py +107 -52
- sky/utils/subprocess_utils.py +17 -4
- sky/utils/thread_utils.py +91 -0
- sky/utils/timeline.py +2 -1
- sky/utils/ux_utils.py +35 -1
- sky/utils/volume.py +88 -4
- sky/utils/yaml_utils.py +9 -0
- sky/volumes/client/sdk.py +48 -10
- sky/volumes/server/core.py +59 -22
- sky/volumes/server/server.py +46 -17
- sky/volumes/volume.py +54 -42
- sky/workspaces/core.py +57 -21
- sky/workspaces/server.py +13 -12
- sky_templates/README.md +3 -0
- sky_templates/__init__.py +3 -0
- sky_templates/ray/__init__.py +0 -0
- sky_templates/ray/start_cluster +183 -0
- sky_templates/ray/stop_cluster +75 -0
- {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/METADATA +331 -65
- skypilot_nightly-1.0.0.dev20251203.dist-info/RECORD +611 -0
- skypilot_nightly-1.0.0.dev20251203.dist-info/top_level.txt +2 -0
- sky/client/cli/git.py +0 -549
- sky/dashboard/out/_next/static/chunks/1121-408ed10b2f9fce17.js +0 -1
- sky/dashboard/out/_next/static/chunks/1141-943efc7aff0f0c06.js +0 -1
- sky/dashboard/out/_next/static/chunks/1836-37fede578e2da5f8.js +0 -40
- sky/dashboard/out/_next/static/chunks/3015-86cabed5d4669ad0.js +0 -1
- sky/dashboard/out/_next/static/chunks/3294.c80326aec9bfed40.js +0 -6
- sky/dashboard/out/_next/static/chunks/3785.4872a2f3aa489880.js +0 -1
- sky/dashboard/out/_next/static/chunks/4045.b30465273dc5e468.js +0 -21
- sky/dashboard/out/_next/static/chunks/4676-9da7fdbde90b5549.js +0 -10
- sky/dashboard/out/_next/static/chunks/4725.10f7a9a5d3ea8208.js +0 -1
- sky/dashboard/out/_next/static/chunks/5339.3fda4a4010ff4e06.js +0 -51
- sky/dashboard/out/_next/static/chunks/6135-4b4d5e824b7f9d3c.js +0 -1
- sky/dashboard/out/_next/static/chunks/649.b9d7f7d10c1b8c53.js +0 -45
- sky/dashboard/out/_next/static/chunks/6856-dca7962af4814e1b.js +0 -1
- sky/dashboard/out/_next/static/chunks/6990-08b2a1cae076a943.js +0 -1
- sky/dashboard/out/_next/static/chunks/7325.b4bc99ce0892dcd5.js +0 -6
- sky/dashboard/out/_next/static/chunks/754-d0da8ab45f9509e9.js +0 -18
- sky/dashboard/out/_next/static/chunks/7669.1f5d9a402bf5cc42.js +0 -36
- sky/dashboard/out/_next/static/chunks/8969-0be3036bf86f8256.js +0 -1
- sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +0 -6
- sky/dashboard/out/_next/static/chunks/9037-fa1737818d0a0969.js +0 -6
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-1cbba24bd1bd35f8.js +0 -16
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-0b4b35dc1dfe046c.js +0 -16
- sky/dashboard/out/_next/static/chunks/pages/clusters-469814d711d63b1b.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-dd64309c3fe67ed2.js +0 -11
- sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-07349868f7905d37.js +0 -16
- sky/dashboard/out/_next/static/chunks/pages/jobs-1f70d9faa564804f.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/users-018bf31cda52e11b.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/volumes-739726d6b823f532.js +0 -1
- sky/dashboard/out/_next/static/chunks/webpack-4fe903277b57b523.js +0 -1
- sky/dashboard/out/_next/static/css/4614e06482d7309e.css +0 -3
- sky/dashboard/out/_next/static/mS-4qZPSkRuA1u-g2wQhg/_buildManifest.js +0 -1
- sky/templates/kubernetes-ssh-jump.yml.j2 +0 -94
- sky/utils/kubernetes/ssh_jump_lifecycle_manager.py +0 -191
- skypilot_nightly-1.0.0.dev20250905.dist-info/RECORD +0 -547
- skypilot_nightly-1.0.0.dev20250905.dist-info/top_level.txt +0 -1
- /sky/dashboard/out/_next/static/{mS-4qZPSkRuA1u-g2wQhg → 96_E2yl3QAiIJGOYCkSpB}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/WHEEL +0 -0
- {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/licenses/LICENSE +0 -0
sky/clouds/shadeform.py
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
""" Shadeform Cloud. """
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import typing
|
|
6
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
from sky import catalog
|
|
9
|
+
from sky import clouds
|
|
10
|
+
from sky.adaptors import common as adaptors_common
|
|
11
|
+
from sky.catalog import shadeform_catalog
|
|
12
|
+
from sky.utils import registry
|
|
13
|
+
from sky.utils import resources_utils
|
|
14
|
+
from sky.utils import status_lib
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from sky import resources as resources_lib
|
|
18
|
+
from sky.utils import volume as volume_lib
|
|
19
|
+
else:
|
|
20
|
+
requests = adaptors_common.LazyImport('requests')
|
|
21
|
+
|
|
22
|
+
# Minimum set of files under ~/.shadeform that grant Shadeform access.
|
|
23
|
+
_CREDENTIAL_FILES = [
|
|
24
|
+
'api_key',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@registry.CLOUD_REGISTRY.register
|
|
29
|
+
class Shadeform(clouds.Cloud):
|
|
30
|
+
"""Shadeform GPU Cloud
|
|
31
|
+
|
|
32
|
+
Shadeform is a unified API for deploying and managing cloud GPUs across
|
|
33
|
+
multiple cloud providers.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Shadeform doesn't have explicit cluster name limits, but conservative
|
|
37
|
+
_MAX_CLUSTER_NAME_LEN_LIMIT = 120
|
|
38
|
+
|
|
39
|
+
# Features not currently supported by Shadeform
|
|
40
|
+
# yapf: disable
|
|
41
|
+
_CLOUD_UNSUPPORTED_FEATURES = {
|
|
42
|
+
clouds.CloudImplementationFeatures.STOP:
|
|
43
|
+
'Stopping instances not supported on Shadeform.',
|
|
44
|
+
clouds.CloudImplementationFeatures.MULTI_NODE:
|
|
45
|
+
'Multi-node clusters not supported on Shadeform.',
|
|
46
|
+
clouds.CloudImplementationFeatures.SPOT_INSTANCE:
|
|
47
|
+
'Spot instances not supported on Shadeform.',
|
|
48
|
+
clouds.CloudImplementationFeatures.CUSTOM_DISK_TIER:
|
|
49
|
+
'Custom disk tiers not supported on Shadeform.',
|
|
50
|
+
clouds.CloudImplementationFeatures.CUSTOM_NETWORK_TIER:
|
|
51
|
+
'Custom network tiers not supported on Shadeform.',
|
|
52
|
+
clouds.CloudImplementationFeatures.STORAGE_MOUNTING:
|
|
53
|
+
'Object storage mounting not supported on Shadeform.',
|
|
54
|
+
clouds.CloudImplementationFeatures.HOST_CONTROLLERS:
|
|
55
|
+
'Host controllers not supported on Shadeform.',
|
|
56
|
+
clouds.CloudImplementationFeatures.HIGH_AVAILABILITY_CONTROLLERS:
|
|
57
|
+
'High availability controllers not supported.',
|
|
58
|
+
clouds.CloudImplementationFeatures.CLONE_DISK_FROM_CLUSTER:
|
|
59
|
+
'Disk cloning not supported on Shadeform.',
|
|
60
|
+
clouds.CloudImplementationFeatures.IMAGE_ID:
|
|
61
|
+
'Custom image IDs not supported on Shadeform.',
|
|
62
|
+
clouds.CloudImplementationFeatures.DOCKER_IMAGE:
|
|
63
|
+
'Docker images not supported on Shadeform yet.',
|
|
64
|
+
clouds.CloudImplementationFeatures.CUSTOM_MULTI_NETWORK:
|
|
65
|
+
'Custom multiple network interfaces not supported.',
|
|
66
|
+
}
|
|
67
|
+
# yapf: enable
|
|
68
|
+
|
|
69
|
+
_regions: List[clouds.Region] = []
|
|
70
|
+
|
|
71
|
+
PROVISIONER_VERSION = clouds.ProvisionerVersion.SKYPILOT
|
|
72
|
+
STATUS_VERSION = clouds.StatusVersion.SKYPILOT
|
|
73
|
+
OPEN_PORTS_VERSION = clouds.OpenPortsVersion.LAUNCH_ONLY
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def _unsupported_features_for_resources(
|
|
77
|
+
cls,
|
|
78
|
+
resources: 'resources_lib.Resources',
|
|
79
|
+
region: Optional[str] = None,
|
|
80
|
+
) -> Dict[clouds.CloudImplementationFeatures, str]:
|
|
81
|
+
"""The features not supported based on the resources provided."""
|
|
82
|
+
del resources # unused
|
|
83
|
+
return cls._CLOUD_UNSUPPORTED_FEATURES
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def _max_cluster_name_length(cls) -> Optional[int]:
|
|
87
|
+
return cls._MAX_CLUSTER_NAME_LEN_LIMIT
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def regions_with_offering(
|
|
91
|
+
cls,
|
|
92
|
+
instance_type: str,
|
|
93
|
+
accelerators: Optional[Dict[str, int]],
|
|
94
|
+
use_spot: bool,
|
|
95
|
+
region: Optional[str],
|
|
96
|
+
zone: Optional[str],
|
|
97
|
+
resources: Optional['resources_lib.Resources'] = None,
|
|
98
|
+
) -> List[clouds.Region]:
|
|
99
|
+
"""Get regions that offer the requested instance type."""
|
|
100
|
+
assert zone is None, 'Shadeform does not support zones.'
|
|
101
|
+
del zone # unused
|
|
102
|
+
if use_spot:
|
|
103
|
+
return [] # No spot support
|
|
104
|
+
|
|
105
|
+
# IMPORTANT: instance_type here is the specific Shadeform instance type
|
|
106
|
+
# (like 'massedcompute_A6000_base'), NOT the accelerator name
|
|
107
|
+
# We only return regions where this exact instance type exists
|
|
108
|
+
regions = shadeform_catalog.get_region_zones_for_instance_type(
|
|
109
|
+
instance_type, use_spot)
|
|
110
|
+
|
|
111
|
+
if region is not None:
|
|
112
|
+
regions = [r for r in regions if r.name == region]
|
|
113
|
+
return regions
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def zones_provision_loop(
|
|
117
|
+
cls,
|
|
118
|
+
*,
|
|
119
|
+
region: str,
|
|
120
|
+
num_nodes: int,
|
|
121
|
+
instance_type: str,
|
|
122
|
+
accelerators: Optional[Dict[str, int]] = None,
|
|
123
|
+
use_spot: bool = False,
|
|
124
|
+
) -> Iterator[None]:
|
|
125
|
+
"""Iterate over zones for provisioning."""
|
|
126
|
+
del num_nodes # unused
|
|
127
|
+
if use_spot:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
regions = cls.regions_with_offering(instance_type, accelerators,
|
|
131
|
+
use_spot, region, None)
|
|
132
|
+
for r in regions:
|
|
133
|
+
assert r.zones is None, r
|
|
134
|
+
yield r.zones
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def get_vcpus_mem_from_instance_type(
|
|
138
|
+
cls,
|
|
139
|
+
instance_type: str,
|
|
140
|
+
) -> Tuple[Optional[float], Optional[float]]:
|
|
141
|
+
"""Get vCPUs and memory from instance type."""
|
|
142
|
+
return catalog.get_vcpus_mem_from_instance_type(instance_type,
|
|
143
|
+
clouds='shadeform')
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def get_accelerators_from_instance_type(
|
|
147
|
+
cls,
|
|
148
|
+
instance_type: str,
|
|
149
|
+
) -> Optional[Dict[str, Union[int, float]]]:
|
|
150
|
+
"""Get accelerator information from instance type."""
|
|
151
|
+
return catalog.get_accelerators_from_instance_type(instance_type,
|
|
152
|
+
clouds='shadeform')
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def get_default_instance_type(
|
|
156
|
+
cls,
|
|
157
|
+
cpus: Optional[str] = None,
|
|
158
|
+
memory: Optional[str] = None,
|
|
159
|
+
disk_tier: Optional[resources_utils.DiskTier] = None,
|
|
160
|
+
region: Optional[str] = None,
|
|
161
|
+
zone: Optional[str] = None,
|
|
162
|
+
) -> Optional[str]:
|
|
163
|
+
"""Get default instance type."""
|
|
164
|
+
del disk_tier # Not supported
|
|
165
|
+
return catalog.get_default_instance_type(cpus=cpus,
|
|
166
|
+
memory=memory,
|
|
167
|
+
disk_tier=None,
|
|
168
|
+
region=region,
|
|
169
|
+
zone=zone,
|
|
170
|
+
clouds='shadeform')
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def get_zone_shell_cmd(cls) -> Optional[str]:
|
|
174
|
+
"""Return shell command to get the zone of the instance."""
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def get_user_identities(cls) -> Optional[List[List[str]]]:
|
|
179
|
+
"""Get user identities for Shadeform."""
|
|
180
|
+
# No user identity support needed
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
def instance_type_exists(self, instance_type: str) -> bool:
|
|
184
|
+
return catalog.instance_type_exists(instance_type, 'shadeform')
|
|
185
|
+
|
|
186
|
+
def instance_type_to_hourly_cost(self,
|
|
187
|
+
instance_type: str,
|
|
188
|
+
use_spot: bool,
|
|
189
|
+
region: Optional[str] = None,
|
|
190
|
+
zone: Optional[str] = None) -> float:
|
|
191
|
+
"""Get hourly cost for instance type."""
|
|
192
|
+
if use_spot:
|
|
193
|
+
raise ValueError('Spot instances are not supported on Shadeform')
|
|
194
|
+
return catalog.get_hourly_cost(instance_type,
|
|
195
|
+
use_spot=use_spot,
|
|
196
|
+
region=region,
|
|
197
|
+
zone=zone,
|
|
198
|
+
clouds='shadeform')
|
|
199
|
+
|
|
200
|
+
def accelerators_to_hourly_cost(self,
|
|
201
|
+
accelerators: Dict[str, int],
|
|
202
|
+
use_spot: bool,
|
|
203
|
+
region: Optional[str] = None,
|
|
204
|
+
zone: Optional[str] = None) -> float:
|
|
205
|
+
"""Get hourly cost for accelerators."""
|
|
206
|
+
return 0.0
|
|
207
|
+
|
|
208
|
+
def get_egress_cost(self, num_gigabytes: float) -> float:
|
|
209
|
+
"""Get egress cost."""
|
|
210
|
+
# No explicit egress pricing from Shadeform API
|
|
211
|
+
return 0.0
|
|
212
|
+
|
|
213
|
+
def __repr__(self):
|
|
214
|
+
return 'Shadeform'
|
|
215
|
+
|
|
216
|
+
@classmethod
|
|
217
|
+
def get_current_user_identity(cls) -> Optional[str]:
|
|
218
|
+
"""Get current user identity."""
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
def make_deploy_resources_variables(
|
|
222
|
+
self,
|
|
223
|
+
resources: 'resources_lib.Resources',
|
|
224
|
+
cluster_name: resources_utils.ClusterName,
|
|
225
|
+
region: 'clouds.Region',
|
|
226
|
+
zones: Optional[List['clouds.Zone']],
|
|
227
|
+
num_nodes: int,
|
|
228
|
+
dryrun: bool = False,
|
|
229
|
+
volume_mounts: Optional[List['volume_lib.VolumeMount']] = None,
|
|
230
|
+
) -> Dict[str, Any]:
|
|
231
|
+
"""Make variables for deployment template."""
|
|
232
|
+
del zones, num_nodes, dryrun, volume_mounts # unused for Shadeform
|
|
233
|
+
|
|
234
|
+
# Get instance type
|
|
235
|
+
r = resources.copy(accelerators=None)
|
|
236
|
+
feasible_resources = self._get_feasible_launchable_resources(r)
|
|
237
|
+
instance_type = feasible_resources.resources_list[0].instance_type
|
|
238
|
+
|
|
239
|
+
resources_vars = {}
|
|
240
|
+
if instance_type is not None:
|
|
241
|
+
instance_type_split = instance_type.split('_')
|
|
242
|
+
cloud = instance_type_split[0]
|
|
243
|
+
resources_vars.update({
|
|
244
|
+
'instance_type': instance_type,
|
|
245
|
+
'region': region.name,
|
|
246
|
+
'cloud': cloud,
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
# Add accelerator resources for Ray
|
|
250
|
+
accelerators = resources.accelerators
|
|
251
|
+
if accelerators is not None:
|
|
252
|
+
resources_vars['custom_resources'] = json.dumps(accelerators,
|
|
253
|
+
separators=(',',
|
|
254
|
+
':'))
|
|
255
|
+
|
|
256
|
+
return resources_vars
|
|
257
|
+
|
|
258
|
+
def get_credential_file_mounts(self) -> Dict[str, str]:
|
|
259
|
+
"""Get credential files that need to be mounted."""
|
|
260
|
+
return {
|
|
261
|
+
f'~/.shadeform/{f}': f'~/.shadeform/{f}' for f in _CREDENTIAL_FILES
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@classmethod
|
|
265
|
+
def get_current_user_identity_str(cls) -> Optional[str]:
|
|
266
|
+
"""Get current user identity string."""
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
def check_credentials(
|
|
271
|
+
cls, cloud_capability: clouds.CloudCapability
|
|
272
|
+
) -> Tuple[bool, Optional[Union[str, Dict[str, str]]]]:
|
|
273
|
+
"""Check if Shadeform credentials are properly configured."""
|
|
274
|
+
del cloud_capability # unused for Shadeform
|
|
275
|
+
try:
|
|
276
|
+
api_key_path = os.path.expanduser('~/.shadeform/api_key')
|
|
277
|
+
if not os.path.exists(api_key_path):
|
|
278
|
+
return False, (f'Shadeform API key not found. '
|
|
279
|
+
f'Please save your API key to {api_key_path}')
|
|
280
|
+
|
|
281
|
+
# Try to read the API key
|
|
282
|
+
with open(api_key_path, 'r', encoding='utf-8') as f:
|
|
283
|
+
api_key = f.read().strip()
|
|
284
|
+
|
|
285
|
+
if not api_key:
|
|
286
|
+
return False, f'Shadeform API key is empty in {api_key_path}'
|
|
287
|
+
|
|
288
|
+
return True, None
|
|
289
|
+
|
|
290
|
+
except (OSError, IOError) as e:
|
|
291
|
+
return False, f'Error checking Shadeform credentials: {str(e)}'
|
|
292
|
+
|
|
293
|
+
def _get_feasible_launchable_resources(
|
|
294
|
+
self, resources: 'resources_lib.Resources'
|
|
295
|
+
) -> 'resources_utils.FeasibleResources':
|
|
296
|
+
"""Get feasible launchable resources."""
|
|
297
|
+
if resources.use_spot:
|
|
298
|
+
return resources_utils.FeasibleResources(
|
|
299
|
+
[], [], 'Spot instances are not supported on Shadeform.')
|
|
300
|
+
|
|
301
|
+
if resources.instance_type is not None:
|
|
302
|
+
# Instance type is already specified, validate it
|
|
303
|
+
assert resources.is_launchable(), resources
|
|
304
|
+
fuzzy_candidate_list = [resources.instance_type]
|
|
305
|
+
return resources_utils.FeasibleResources([resources],
|
|
306
|
+
fuzzy_candidate_list, None)
|
|
307
|
+
|
|
308
|
+
# Map accelerators to instance types
|
|
309
|
+
def _make_resources(instance_type_list):
|
|
310
|
+
resource_list = []
|
|
311
|
+
for instance_type in instance_type_list:
|
|
312
|
+
r = resources.copy(
|
|
313
|
+
cloud=Shadeform(),
|
|
314
|
+
instance_type=instance_type,
|
|
315
|
+
accelerators=resources.
|
|
316
|
+
accelerators, # Keep original accelerators
|
|
317
|
+
cpus=None,
|
|
318
|
+
memory=None,
|
|
319
|
+
)
|
|
320
|
+
resource_list.append(r)
|
|
321
|
+
return resource_list
|
|
322
|
+
|
|
323
|
+
# Handle accelerator requests
|
|
324
|
+
accelerators = resources.accelerators
|
|
325
|
+
if accelerators is not None:
|
|
326
|
+
# Get the first accelerator type and count
|
|
327
|
+
for accelerator_name, accelerator_count in accelerators.items():
|
|
328
|
+
# Get instance types that provide this accelerator
|
|
329
|
+
func = shadeform_catalog.get_instance_type_for_accelerator
|
|
330
|
+
instance_types, errors = func(accelerator_name,
|
|
331
|
+
accelerator_count,
|
|
332
|
+
use_spot=resources.use_spot)
|
|
333
|
+
|
|
334
|
+
if instance_types:
|
|
335
|
+
# Create separate resource objects for each instance type
|
|
336
|
+
# This is crucial: each resource will only be considered
|
|
337
|
+
# for regions where its specific instance type is available
|
|
338
|
+
all_resources = []
|
|
339
|
+
all_candidate_names = []
|
|
340
|
+
|
|
341
|
+
# Create one resource per instance type
|
|
342
|
+
for instance_type in instance_types:
|
|
343
|
+
resource = resources.copy(
|
|
344
|
+
cloud=Shadeform(),
|
|
345
|
+
instance_type=instance_type,
|
|
346
|
+
accelerators=resources.accelerators,
|
|
347
|
+
cpus=None,
|
|
348
|
+
memory=None,
|
|
349
|
+
)
|
|
350
|
+
all_resources.append(resource)
|
|
351
|
+
all_candidate_names.append(instance_type)
|
|
352
|
+
|
|
353
|
+
return resources_utils.FeasibleResources(
|
|
354
|
+
all_resources, all_candidate_names, None)
|
|
355
|
+
else:
|
|
356
|
+
error_msg = (f'No instances available for accelerator '
|
|
357
|
+
f'{accelerator_name}')
|
|
358
|
+
if errors:
|
|
359
|
+
error_msg += f': {"; ".join(errors)}'
|
|
360
|
+
return resources_utils.FeasibleResources([], [], error_msg)
|
|
361
|
+
|
|
362
|
+
# If accelerator not found in mapping, return error
|
|
363
|
+
return resources_utils.FeasibleResources(
|
|
364
|
+
[], [],
|
|
365
|
+
f'Accelerator {list(accelerators.keys())[0]} not supported.')
|
|
366
|
+
|
|
367
|
+
# No accelerators specified, return a default instance type
|
|
368
|
+
if accelerators is None:
|
|
369
|
+
# Return a default instance type
|
|
370
|
+
default_instance_type = Shadeform.get_default_instance_type(
|
|
371
|
+
cpus=resources.cpus,
|
|
372
|
+
memory=resources.memory,
|
|
373
|
+
disk_tier=resources.disk_tier,
|
|
374
|
+
region=resources.region,
|
|
375
|
+
zone=resources.zone)
|
|
376
|
+
if default_instance_type is None:
|
|
377
|
+
# TODO: Add hints to all return values in this method to help
|
|
378
|
+
# users understand why the resources are not launchable.
|
|
379
|
+
return resources_utils.FeasibleResources([], [], None)
|
|
380
|
+
else:
|
|
381
|
+
return resources_utils.FeasibleResources(
|
|
382
|
+
_make_resources([default_instance_type]), [], None)
|
|
383
|
+
|
|
384
|
+
@classmethod
|
|
385
|
+
def _check_compute_credentials(cls) -> Tuple[bool, Optional[str]]:
|
|
386
|
+
"""Check compute credentials."""
|
|
387
|
+
success, msg = cls.check_credentials(clouds.CloudCapability.COMPUTE)
|
|
388
|
+
# Convert return type to match expected signature
|
|
389
|
+
if isinstance(msg, dict):
|
|
390
|
+
msg = str(msg)
|
|
391
|
+
return success, msg
|
|
392
|
+
|
|
393
|
+
@classmethod
|
|
394
|
+
def query_status(cls, name: str, tag_filters: Dict[str, str],
|
|
395
|
+
region: Optional[str], zone: Optional[str],
|
|
396
|
+
**kwargs) -> List[status_lib.ClusterStatus]:
|
|
397
|
+
"""Query cluster status."""
|
|
398
|
+
# For validation purposes, return empty list (no existing clusters)
|
|
399
|
+
# Actual status querying is handled by the provisioner
|
|
400
|
+
return []
|
sky/clouds/ssh.py
CHANGED
|
@@ -44,10 +44,12 @@ class SSH(kubernetes.Kubernetes):
|
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
46
46
|
def _unsupported_features_for_resources(
|
|
47
|
-
cls,
|
|
47
|
+
cls,
|
|
48
|
+
resources: 'resources_lib.Resources',
|
|
49
|
+
region: Optional[str] = None,
|
|
48
50
|
) -> Dict[kubernetes.clouds.CloudImplementationFeatures, str]:
|
|
49
51
|
# Inherit all Kubernetes unsupported features
|
|
50
|
-
return super()._unsupported_features_for_resources(resources)
|
|
52
|
+
return super()._unsupported_features_for_resources(resources, region)
|
|
51
53
|
|
|
52
54
|
@classmethod
|
|
53
55
|
def get_ssh_node_pool_contexts(cls) -> List[str]:
|
sky/clouds/utils/scp_utils.py
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
This module contains a set of rest api functions accessing SCP Open-API.
|
|
4
4
|
"""
|
|
5
5
|
import base64
|
|
6
|
-
import datetime
|
|
7
6
|
from functools import wraps
|
|
8
7
|
import hashlib
|
|
9
8
|
import hmac
|
|
@@ -171,15 +170,18 @@ class SCPClient:
|
|
|
171
170
|
self.secret_key = self._credentials['secret_key']
|
|
172
171
|
self.project_id = self._credentials['project_id']
|
|
173
172
|
self.client_type = 'OpenApi'
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
self.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
'X-Cmp-
|
|
182
|
-
'X-Cmp-
|
|
173
|
+
|
|
174
|
+
def _signed_headers(self, method: str, url: str) -> dict:
|
|
175
|
+
timestamp = str(int(time.time() * 1000))
|
|
176
|
+
signature = self.get_signature(method=method,
|
|
177
|
+
url=url,
|
|
178
|
+
timestamp=timestamp)
|
|
179
|
+
return {
|
|
180
|
+
'X-Cmp-AccessKey': self.access_key,
|
|
181
|
+
'X-Cmp-ClientType': self.client_type,
|
|
182
|
+
'X-Cmp-ProjectId': self.project_id,
|
|
183
|
+
'X-Cmp-Timestamp': timestamp,
|
|
184
|
+
'X-Cmp-Signature': signature,
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
def create_instance(self, instance_config):
|
|
@@ -190,10 +192,8 @@ class SCPClient:
|
|
|
190
192
|
@_retry
|
|
191
193
|
def _get(self, url, contents_key='contents'):
|
|
192
194
|
method = 'GET'
|
|
193
|
-
self.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
response = requests.get(url, headers=self.headers)
|
|
195
|
+
headers = self._signed_headers(method, url)
|
|
196
|
+
response = requests.get(url, headers=headers)
|
|
197
197
|
raise_scp_error(response)
|
|
198
198
|
if contents_key is not None:
|
|
199
199
|
return response.json().get(contents_key, [])
|
|
@@ -203,26 +203,19 @@ class SCPClient:
|
|
|
203
203
|
@_retry
|
|
204
204
|
def _post(self, url, request_body):
|
|
205
205
|
method = 'POST'
|
|
206
|
-
self.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
response = requests.post(url, json=request_body, headers=self.headers)
|
|
210
|
-
|
|
206
|
+
headers = self._signed_headers(method, url)
|
|
207
|
+
response = requests.post(url, json=request_body, headers=headers)
|
|
211
208
|
raise_scp_error(response)
|
|
212
209
|
return response.json()
|
|
213
210
|
|
|
214
211
|
@_retry
|
|
215
212
|
def _delete(self, url, request_body=None):
|
|
216
213
|
method = 'DELETE'
|
|
217
|
-
self.
|
|
218
|
-
self.set_signature(url=url, method=method)
|
|
214
|
+
headers = self._signed_headers(method, url)
|
|
219
215
|
if request_body:
|
|
220
|
-
response = requests.delete(url,
|
|
221
|
-
json=request_body,
|
|
222
|
-
headers=self.headers)
|
|
223
|
-
|
|
216
|
+
response = requests.delete(url, json=request_body, headers=headers)
|
|
224
217
|
else:
|
|
225
|
-
response = requests.delete(url, headers=
|
|
218
|
+
response = requests.delete(url, headers=headers)
|
|
226
219
|
raise_scp_error(response)
|
|
227
220
|
return response.json()
|
|
228
221
|
|
|
@@ -252,12 +245,22 @@ class SCPClient:
|
|
|
252
245
|
return True
|
|
253
246
|
|
|
254
247
|
def add_security_group_rule(self, sg_id, direction,
|
|
255
|
-
ports: Optional[List[str]]
|
|
248
|
+
ports: Optional[List[str]],
|
|
249
|
+
cnt: Optional[int]) -> None:
|
|
256
250
|
if ports is None:
|
|
257
251
|
if direction == 'IN':
|
|
258
|
-
|
|
252
|
+
if cnt == 1:
|
|
253
|
+
ports = ['22']
|
|
254
|
+
else:
|
|
255
|
+
ports = ['22', '6380', '8076', '10001', '11001-11200']
|
|
259
256
|
else:
|
|
260
|
-
|
|
257
|
+
if cnt == 1:
|
|
258
|
+
ports = ['21', '22', '80', '443']
|
|
259
|
+
else:
|
|
260
|
+
ports = [
|
|
261
|
+
'21', '22', '80', '443', '6380', '8076', '10001',
|
|
262
|
+
'11001-11200'
|
|
263
|
+
]
|
|
261
264
|
services = []
|
|
262
265
|
for port in ports:
|
|
263
266
|
services.append({'serviceType': 'TCP', 'serviceValue': port})
|
|
@@ -273,7 +276,9 @@ class SCPClient:
|
|
|
273
276
|
target_address: ['0.0.0.0/0'],
|
|
274
277
|
'ruleDescription': 'sky security group rule'
|
|
275
278
|
}
|
|
276
|
-
|
|
279
|
+
self._post(url, request_body)
|
|
280
|
+
else:
|
|
281
|
+
return None
|
|
277
282
|
|
|
278
283
|
def _firewall_rule_not_exist(self, firewall_id, internal_ip, direction,
|
|
279
284
|
ports):
|
|
@@ -293,12 +298,22 @@ class SCPClient:
|
|
|
293
298
|
return False
|
|
294
299
|
return True
|
|
295
300
|
|
|
296
|
-
def add_firewall_rule(self, firewall_id, internal_ip, direction,
|
|
301
|
+
def add_firewall_rule(self, firewall_id, internal_ip, direction,
|
|
302
|
+
ports: Optional[List[str]], cnt: Optional[int]):
|
|
297
303
|
if ports is None:
|
|
298
304
|
if direction == 'IN':
|
|
299
|
-
|
|
305
|
+
if cnt == 1:
|
|
306
|
+
ports = ['22']
|
|
307
|
+
else:
|
|
308
|
+
ports = ['22', '6380', '8076', '10001', '11001-11200']
|
|
300
309
|
else:
|
|
301
|
-
|
|
310
|
+
if cnt == 1:
|
|
311
|
+
ports = ['21', '22', '80', '443']
|
|
312
|
+
else:
|
|
313
|
+
ports = [
|
|
314
|
+
'21', '22', '80', '443', '6380', '8076', '10001',
|
|
315
|
+
'11001-11200'
|
|
316
|
+
]
|
|
302
317
|
services = []
|
|
303
318
|
for port in ports:
|
|
304
319
|
services.append({'serviceType': 'TCP', 'serviceValue': port})
|
|
@@ -322,6 +337,8 @@ class SCPClient:
|
|
|
322
337
|
'ruleDescription': 'sky firewall rule'
|
|
323
338
|
}
|
|
324
339
|
return self._post(url, request_body)
|
|
340
|
+
else:
|
|
341
|
+
return None
|
|
325
342
|
|
|
326
343
|
def terminate_instance(self, instance_id):
|
|
327
344
|
url = f'{API_ENDPOINT}/virtual-server/v2/virtual-servers/{instance_id}'
|
|
@@ -334,12 +351,19 @@ class SCPClient:
|
|
|
334
351
|
|
|
335
352
|
def get_catalog(self) -> Dict[str, Any]:
|
|
336
353
|
"""List offered instances and their availability."""
|
|
337
|
-
|
|
338
|
-
|
|
354
|
+
url = f'{API_ENDPOINT}/instance-types'
|
|
355
|
+
headers = self._signed_headers('GET', url)
|
|
356
|
+
response = requests.get(url, headers=headers)
|
|
339
357
|
raise_scp_error(response)
|
|
340
358
|
return response.json().get('data', [])
|
|
341
359
|
|
|
342
|
-
def get_signature(self,
|
|
360
|
+
def get_signature(self,
|
|
361
|
+
method: str,
|
|
362
|
+
url: str,
|
|
363
|
+
timestamp: Optional[str] = None) -> str:
|
|
364
|
+
if timestamp is None:
|
|
365
|
+
timestamp = str(int(time.time() * 1000))
|
|
366
|
+
|
|
343
367
|
url_info = parse.urlsplit(url)
|
|
344
368
|
url = (f'{url_info.scheme}://{url_info.netloc}'
|
|
345
369
|
f'{parse.quote(url_info.path)}')
|
|
@@ -349,7 +373,7 @@ class SCPClient:
|
|
|
349
373
|
parse.parse_qs(url_info.query).items()))
|
|
350
374
|
url = f'{url}?{parse.urlencode(enc_params)}'
|
|
351
375
|
|
|
352
|
-
message = method + url +
|
|
376
|
+
message = method + url + timestamp \
|
|
353
377
|
+ self.access_key + self.project_id + self.client_type
|
|
354
378
|
message = bytes(message, 'utf-8')
|
|
355
379
|
secret = bytes(self.secret_key, 'utf-8')
|
|
@@ -360,19 +384,6 @@ class SCPClient:
|
|
|
360
384
|
|
|
361
385
|
return str(signature)
|
|
362
386
|
|
|
363
|
-
def set_timestamp(self) -> None:
|
|
364
|
-
self.timestamp = str(
|
|
365
|
-
int(
|
|
366
|
-
round(
|
|
367
|
-
datetime.datetime.timestamp(datetime.datetime.now() -
|
|
368
|
-
datetime.timedelta(minutes=1)) *
|
|
369
|
-
1000)))
|
|
370
|
-
self.headers['X-Cmp-Timestamp'] = self.timestamp
|
|
371
|
-
|
|
372
|
-
def set_signature(self, method: str, url: str) -> None:
|
|
373
|
-
self.signature = self.get_signature(url=url, method=method)
|
|
374
|
-
self.headers['X-Cmp-Signature'] = self.signature
|
|
375
|
-
|
|
376
387
|
def get_nic(self, instance_id) -> List[dict]:
|
|
377
388
|
url = f'{API_ENDPOINT}/virtual-server/v2/virtual-servers/{instance_id}/nics' # pylint: disable=line-too-long
|
|
378
389
|
return self._get(url)
|