skypilot-nightly 1.0.0.dev20250509__py3-none-any.whl → 1.0.0.dev20251107__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of skypilot-nightly might be problematic. Click here for more details.
- sky/__init__.py +22 -6
- sky/adaptors/aws.py +25 -7
- sky/adaptors/common.py +24 -1
- sky/adaptors/coreweave.py +278 -0
- sky/adaptors/do.py +8 -2
- sky/adaptors/hyperbolic.py +8 -0
- sky/adaptors/kubernetes.py +149 -18
- sky/adaptors/nebius.py +170 -17
- sky/adaptors/primeintellect.py +1 -0
- sky/adaptors/runpod.py +68 -0
- sky/adaptors/seeweb.py +167 -0
- sky/adaptors/shadeform.py +89 -0
- sky/admin_policy.py +187 -4
- sky/authentication.py +179 -225
- sky/backends/__init__.py +4 -2
- sky/backends/backend.py +22 -9
- sky/backends/backend_utils.py +1299 -380
- sky/backends/cloud_vm_ray_backend.py +1715 -518
- sky/backends/docker_utils.py +1 -1
- sky/backends/local_docker_backend.py +11 -6
- sky/backends/wheel_utils.py +37 -9
- sky/{clouds/service_catalog → catalog}/__init__.py +21 -19
- sky/{clouds/service_catalog → catalog}/aws_catalog.py +27 -8
- sky/{clouds/service_catalog → catalog}/azure_catalog.py +10 -7
- sky/{clouds/service_catalog → catalog}/common.py +89 -48
- sky/{clouds/service_catalog → catalog}/cudo_catalog.py +8 -5
- sky/{clouds/service_catalog → catalog}/data_fetchers/analyze.py +1 -1
- sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_aws.py +30 -40
- sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_cudo.py +38 -38
- sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_gcp.py +42 -15
- sky/catalog/data_fetchers/fetch_hyperbolic.py +136 -0
- sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_lambda_cloud.py +1 -0
- sky/catalog/data_fetchers/fetch_nebius.py +335 -0
- 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/{clouds/service_catalog → catalog}/data_fetchers/fetch_vast.py +1 -1
- sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_vsphere.py +1 -1
- sky/{clouds/service_catalog → catalog}/do_catalog.py +5 -2
- sky/{clouds/service_catalog → catalog}/fluidstack_catalog.py +6 -3
- sky/{clouds/service_catalog → catalog}/gcp_catalog.py +41 -15
- sky/catalog/hyperbolic_catalog.py +136 -0
- sky/{clouds/service_catalog → catalog}/ibm_catalog.py +9 -6
- sky/{clouds/service_catalog → catalog}/kubernetes_catalog.py +36 -24
- sky/{clouds/service_catalog → catalog}/lambda_catalog.py +9 -6
- sky/{clouds/service_catalog → catalog}/nebius_catalog.py +9 -7
- sky/{clouds/service_catalog → catalog}/oci_catalog.py +9 -6
- sky/{clouds/service_catalog → catalog}/paperspace_catalog.py +5 -2
- sky/catalog/primeintellect_catalog.py +95 -0
- sky/{clouds/service_catalog → catalog}/runpod_catalog.py +11 -4
- sky/{clouds/service_catalog → catalog}/scp_catalog.py +9 -6
- sky/catalog/seeweb_catalog.py +184 -0
- sky/catalog/shadeform_catalog.py +165 -0
- sky/catalog/ssh_catalog.py +167 -0
- sky/{clouds/service_catalog → catalog}/vast_catalog.py +6 -3
- sky/{clouds/service_catalog → catalog}/vsphere_catalog.py +5 -2
- sky/check.py +491 -203
- sky/cli.py +5 -6005
- sky/client/{cli.py → cli/command.py} +2477 -1885
- sky/client/cli/deprecation_utils.py +99 -0
- sky/client/cli/flags.py +359 -0
- sky/client/cli/table_utils.py +320 -0
- sky/client/common.py +70 -32
- sky/client/oauth.py +82 -0
- sky/client/sdk.py +1203 -297
- sky/client/sdk_async.py +833 -0
- sky/client/service_account_auth.py +47 -0
- sky/cloud_stores.py +73 -0
- sky/clouds/__init__.py +13 -0
- sky/clouds/aws.py +358 -93
- sky/clouds/azure.py +105 -83
- sky/clouds/cloud.py +127 -36
- sky/clouds/cudo.py +68 -50
- sky/clouds/do.py +66 -48
- sky/clouds/fluidstack.py +63 -44
- sky/clouds/gcp.py +339 -110
- sky/clouds/hyperbolic.py +293 -0
- sky/clouds/ibm.py +70 -49
- sky/clouds/kubernetes.py +563 -162
- sky/clouds/lambda_cloud.py +74 -54
- sky/clouds/nebius.py +206 -80
- sky/clouds/oci.py +88 -66
- sky/clouds/paperspace.py +61 -44
- sky/clouds/primeintellect.py +317 -0
- sky/clouds/runpod.py +164 -74
- sky/clouds/scp.py +89 -83
- sky/clouds/seeweb.py +466 -0
- sky/clouds/shadeform.py +400 -0
- sky/clouds/ssh.py +263 -0
- sky/clouds/utils/aws_utils.py +10 -4
- sky/clouds/utils/gcp_utils.py +87 -11
- sky/clouds/utils/oci_utils.py +38 -14
- sky/clouds/utils/scp_utils.py +177 -124
- sky/clouds/vast.py +99 -77
- sky/clouds/vsphere.py +51 -40
- sky/core.py +349 -139
- sky/dag.py +15 -0
- sky/dashboard/out/404.html +1 -1
- sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
- sky/dashboard/out/_next/static/chunks/1272-1ef0bf0237faccdb.js +1 -0
- sky/dashboard/out/_next/static/chunks/1871-74503c8e80fd253b.js +6 -0
- sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
- sky/dashboard/out/_next/static/chunks/2350.fab69e61bac57b23.js +1 -0
- sky/dashboard/out/_next/static/chunks/2369.fc20f0c2c8ed9fe7.js +15 -0
- sky/dashboard/out/_next/static/chunks/2755.fff53c4a3fcae910.js +26 -0
- sky/dashboard/out/_next/static/chunks/3294.72362fa129305b19.js +1 -0
- sky/dashboard/out/_next/static/chunks/3785.ad6adaa2a0fa9768.js +1 -0
- sky/dashboard/out/_next/static/chunks/3850-ff4a9a69d978632b.js +1 -0
- sky/dashboard/out/_next/static/chunks/3937.210053269f121201.js +1 -0
- sky/dashboard/out/_next/static/chunks/4725.a830b5c9e7867c92.js +1 -0
- sky/dashboard/out/_next/static/chunks/4937.a2baa2df5572a276.js +15 -0
- sky/dashboard/out/_next/static/chunks/5739-d67458fcb1386c92.js +8 -0
- sky/dashboard/out/_next/static/chunks/6130-2be46d70a38f1e82.js +1 -0
- sky/dashboard/out/_next/static/chunks/616-3d59f75e2ccf9321.js +39 -0
- sky/dashboard/out/_next/static/chunks/6212-7bd06f60ba693125.js +13 -0
- sky/dashboard/out/_next/static/chunks/6601-06114c982db410b6.js +1 -0
- sky/dashboard/out/_next/static/chunks/6856-ef8ba11f96d8c4a3.js +1 -0
- sky/dashboard/out/_next/static/chunks/6989-01359c57e018caa4.js +1 -0
- sky/dashboard/out/_next/static/chunks/6990-32b6e2d3822301fa.js +1 -0
- sky/dashboard/out/_next/static/chunks/7359-c8d04e06886000b3.js +30 -0
- sky/dashboard/out/_next/static/chunks/7411-b15471acd2cba716.js +41 -0
- sky/dashboard/out/_next/static/chunks/7615-3301e838e5f25772.js +1 -0
- sky/dashboard/out/_next/static/chunks/8640.5b9475a2d18c5416.js +16 -0
- sky/dashboard/out/_next/static/chunks/8969-1e4613c651bf4051.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.7310982cf5a0dc79.js +31 -0
- sky/dashboard/out/_next/static/chunks/9847.3aaca6bb33455140.js +30 -0
- sky/dashboard/out/_next/static/chunks/fd9d1056-86323a29a8f7e46a.js +1 -0
- sky/dashboard/out/_next/static/chunks/framework-cf60a09ccd051a10.js +33 -0
- sky/dashboard/out/_next/static/chunks/main-app-587214043926b3cc.js +1 -0
- sky/dashboard/out/_next/static/chunks/main-f15ccb73239a3bf1.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/_app-bde01e4a2beec258.js +34 -0
- sky/dashboard/out/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-c736ead69c2d86ec.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-a37d2063af475a1c.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/clusters-d44859594e6f8064.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/config-dfb9bf07b13045f4.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/index-444f1804401f04ea.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/infra/[context]-c0b5935149902e6f.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/infra-aed0ea19df7cf961.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-5796e8d6aea291a0.js +16 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-6edeb7d06032adfc.js +21 -0
- sky/dashboard/out/_next/static/chunks/pages/jobs-479dde13399cf270.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/users-5ab3b907622cf0fe.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/volumes-b84b948ff357c43e.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/workspace/new-3f88a1c7e86a3f86.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-c5a3eeee1c218af1.js +1 -0
- sky/dashboard/out/_next/static/chunks/pages/workspaces-22b23febb3e89ce1.js +1 -0
- sky/dashboard/out/_next/static/chunks/webpack-2679be77fc08a2f8.js +1 -0
- sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
- sky/dashboard/out/_next/static/zB0ed6ge_W1MDszVHhijS/_buildManifest.js +1 -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 -0
- sky/dashboard/out/index.html +1 -1
- sky/dashboard/out/infra/[context].html +1 -0
- sky/dashboard/out/infra.html +1 -0
- sky/dashboard/out/jobs/[job].html +1 -1
- sky/dashboard/out/jobs/pools/[pool].html +1 -0
- sky/dashboard/out/jobs.html +1 -1
- sky/dashboard/out/users.html +1 -0
- sky/dashboard/out/volumes.html +1 -0
- sky/dashboard/out/workspace/new.html +1 -0
- sky/dashboard/out/workspaces/[name].html +1 -0
- sky/dashboard/out/workspaces.html +1 -0
- sky/data/data_utils.py +137 -1
- sky/data/mounting_utils.py +269 -84
- sky/data/storage.py +1451 -1807
- sky/data/storage_utils.py +43 -57
- sky/exceptions.py +132 -2
- sky/execution.py +206 -63
- sky/global_user_state.py +2374 -586
- sky/jobs/__init__.py +5 -0
- sky/jobs/client/sdk.py +242 -65
- sky/jobs/client/sdk_async.py +143 -0
- sky/jobs/constants.py +9 -8
- sky/jobs/controller.py +839 -277
- sky/jobs/file_content_utils.py +80 -0
- sky/jobs/log_gc.py +201 -0
- sky/jobs/recovery_strategy.py +398 -152
- sky/jobs/scheduler.py +315 -189
- sky/jobs/server/core.py +829 -255
- sky/jobs/server/server.py +156 -115
- sky/jobs/server/utils.py +136 -0
- sky/jobs/state.py +2092 -701
- sky/jobs/utils.py +1242 -160
- sky/logs/__init__.py +21 -0
- sky/logs/agent.py +108 -0
- sky/logs/aws.py +243 -0
- sky/logs/gcp.py +91 -0
- sky/metrics/__init__.py +0 -0
- sky/metrics/utils.py +443 -0
- sky/models.py +78 -1
- sky/optimizer.py +164 -70
- sky/provision/__init__.py +90 -4
- sky/provision/aws/config.py +147 -26
- sky/provision/aws/instance.py +135 -50
- sky/provision/azure/instance.py +10 -5
- sky/provision/common.py +13 -1
- sky/provision/cudo/cudo_machine_type.py +1 -1
- sky/provision/cudo/cudo_utils.py +14 -8
- sky/provision/cudo/cudo_wrapper.py +72 -71
- sky/provision/cudo/instance.py +10 -6
- sky/provision/do/instance.py +10 -6
- sky/provision/do/utils.py +4 -3
- sky/provision/docker_utils.py +114 -23
- sky/provision/fluidstack/instance.py +13 -8
- sky/provision/gcp/__init__.py +1 -0
- sky/provision/gcp/config.py +301 -19
- sky/provision/gcp/constants.py +218 -0
- sky/provision/gcp/instance.py +36 -8
- sky/provision/gcp/instance_utils.py +18 -4
- sky/provision/gcp/volume_utils.py +247 -0
- sky/provision/hyperbolic/__init__.py +12 -0
- sky/provision/hyperbolic/config.py +10 -0
- sky/provision/hyperbolic/instance.py +437 -0
- sky/provision/hyperbolic/utils.py +373 -0
- sky/provision/instance_setup.py +93 -14
- sky/provision/kubernetes/__init__.py +5 -0
- sky/provision/kubernetes/config.py +9 -52
- sky/provision/kubernetes/constants.py +17 -0
- sky/provision/kubernetes/instance.py +789 -247
- sky/provision/kubernetes/manifests/fusermount-server-daemonset.yaml +1 -2
- sky/provision/kubernetes/network.py +27 -17
- sky/provision/kubernetes/network_utils.py +40 -43
- sky/provision/kubernetes/utils.py +1192 -531
- sky/provision/kubernetes/volume.py +282 -0
- sky/provision/lambda_cloud/instance.py +22 -16
- sky/provision/nebius/constants.py +50 -0
- sky/provision/nebius/instance.py +19 -6
- sky/provision/nebius/utils.py +196 -91
- sky/provision/oci/instance.py +10 -5
- sky/provision/paperspace/instance.py +10 -7
- sky/provision/paperspace/utils.py +1 -1
- 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 +110 -36
- sky/provision/runpod/__init__.py +5 -0
- sky/provision/runpod/instance.py +27 -6
- sky/provision/runpod/utils.py +51 -18
- sky/provision/runpod/volume.py +180 -0
- sky/provision/scp/__init__.py +15 -0
- sky/provision/scp/config.py +93 -0
- sky/provision/scp/instance.py +531 -0
- sky/provision/seeweb/__init__.py +11 -0
- sky/provision/seeweb/config.py +13 -0
- sky/provision/seeweb/instance.py +807 -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/ssh/__init__.py +18 -0
- sky/provision/vast/instance.py +13 -8
- sky/provision/vast/utils.py +10 -7
- sky/provision/vsphere/common/vim_utils.py +1 -2
- sky/provision/vsphere/instance.py +15 -10
- sky/provision/vsphere/vsphere_utils.py +9 -19
- sky/py.typed +0 -0
- sky/resources.py +844 -118
- sky/schemas/__init__.py +0 -0
- sky/schemas/api/__init__.py +0 -0
- sky/schemas/api/responses.py +225 -0
- sky/schemas/db/README +4 -0
- sky/schemas/db/env.py +90 -0
- sky/schemas/db/global_user_state/001_initial_schema.py +124 -0
- sky/schemas/db/global_user_state/002_add_workspace_to_cluster_history.py +35 -0
- sky/schemas/db/global_user_state/003_fix_initial_revision.py +61 -0
- sky/schemas/db/global_user_state/004_is_managed.py +34 -0
- sky/schemas/db/global_user_state/005_cluster_event.py +32 -0
- sky/schemas/db/global_user_state/006_provision_log.py +41 -0
- sky/schemas/db/global_user_state/007_cluster_event_request_id.py +34 -0
- 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/script.py.mako +28 -0
- sky/schemas/db/serve_state/001_initial_schema.py +67 -0
- sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
- sky/schemas/db/spot_jobs/001_initial_schema.py +97 -0
- sky/schemas/db/spot_jobs/002_cluster_pool.py +42 -0
- sky/schemas/db/spot_jobs/003_pool_hash.py +34 -0
- 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/generated/__init__.py +0 -0
- sky/schemas/generated/autostopv1_pb2.py +36 -0
- sky/schemas/generated/autostopv1_pb2.pyi +43 -0
- sky/schemas/generated/autostopv1_pb2_grpc.py +146 -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 +74 -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 +357 -5
- sky/serve/client/impl.py +310 -0
- sky/serve/client/sdk.py +47 -139
- sky/serve/client/sdk_async.py +130 -0
- sky/serve/constants.py +10 -8
- sky/serve/controller.py +64 -19
- sky/serve/load_balancer.py +106 -60
- sky/serve/load_balancing_policies.py +115 -1
- sky/serve/replica_managers.py +273 -162
- sky/serve/serve_rpc_utils.py +179 -0
- sky/serve/serve_state.py +554 -251
- sky/serve/serve_utils.py +733 -220
- sky/serve/server/core.py +66 -711
- sky/serve/server/impl.py +1093 -0
- sky/serve/server/server.py +21 -18
- sky/serve/service.py +133 -48
- sky/serve/service_spec.py +135 -16
- sky/serve/spot_placer.py +3 -0
- sky/server/auth/__init__.py +0 -0
- sky/server/auth/authn.py +50 -0
- sky/server/auth/loopback.py +38 -0
- sky/server/auth/oauth2_proxy.py +200 -0
- sky/server/common.py +475 -181
- sky/server/config.py +81 -23
- sky/server/constants.py +44 -6
- sky/server/daemons.py +229 -0
- sky/server/html/token_page.html +185 -0
- sky/server/metrics.py +160 -0
- sky/server/requests/executor.py +528 -138
- sky/server/requests/payloads.py +351 -17
- sky/server/requests/preconditions.py +21 -17
- sky/server/requests/process.py +112 -29
- sky/server/requests/request_names.py +120 -0
- sky/server/requests/requests.py +817 -224
- sky/server/requests/serializers/decoders.py +82 -31
- sky/server/requests/serializers/encoders.py +140 -22
- sky/server/requests/threads.py +106 -0
- sky/server/rest.py +417 -0
- sky/server/server.py +1290 -284
- sky/server/state.py +20 -0
- sky/server/stream_utils.py +345 -57
- sky/server/uvicorn.py +217 -3
- sky/server/versions.py +270 -0
- sky/setup_files/MANIFEST.in +5 -0
- sky/setup_files/alembic.ini +156 -0
- sky/setup_files/dependencies.py +136 -31
- sky/setup_files/setup.py +44 -42
- sky/sky_logging.py +102 -5
- sky/skylet/attempt_skylet.py +1 -0
- sky/skylet/autostop_lib.py +129 -8
- sky/skylet/configs.py +27 -20
- sky/skylet/constants.py +171 -19
- sky/skylet/events.py +105 -21
- sky/skylet/job_lib.py +335 -104
- sky/skylet/log_lib.py +297 -18
- sky/skylet/log_lib.pyi +44 -1
- sky/skylet/ray_patches/__init__.py +17 -3
- sky/skylet/ray_patches/autoscaler.py.diff +18 -0
- sky/skylet/ray_patches/cli.py.diff +19 -0
- sky/skylet/ray_patches/command_runner.py.diff +17 -0
- sky/skylet/ray_patches/log_monitor.py.diff +20 -0
- sky/skylet/ray_patches/resource_demand_scheduler.py.diff +32 -0
- sky/skylet/ray_patches/updater.py.diff +18 -0
- sky/skylet/ray_patches/worker.py.diff +41 -0
- sky/skylet/services.py +564 -0
- sky/skylet/skylet.py +63 -4
- sky/skylet/subprocess_daemon.py +103 -29
- sky/skypilot_config.py +506 -99
- sky/ssh_node_pools/__init__.py +1 -0
- sky/ssh_node_pools/core.py +135 -0
- sky/ssh_node_pools/server.py +233 -0
- sky/task.py +621 -137
- sky/templates/aws-ray.yml.j2 +10 -3
- sky/templates/azure-ray.yml.j2 +1 -1
- sky/templates/do-ray.yml.j2 +1 -1
- sky/templates/gcp-ray.yml.j2 +57 -0
- sky/templates/hyperbolic-ray.yml.j2 +67 -0
- sky/templates/jobs-controller.yaml.j2 +27 -24
- sky/templates/kubernetes-loadbalancer.yml.j2 +2 -0
- sky/templates/kubernetes-ray.yml.j2 +607 -51
- sky/templates/lambda-ray.yml.j2 +1 -1
- sky/templates/nebius-ray.yml.j2 +33 -12
- sky/templates/paperspace-ray.yml.j2 +1 -1
- sky/templates/primeintellect-ray.yml.j2 +71 -0
- sky/templates/runpod-ray.yml.j2 +9 -1
- sky/templates/scp-ray.yml.j2 +3 -50
- sky/templates/seeweb-ray.yml.j2 +108 -0
- sky/templates/shadeform-ray.yml.j2 +72 -0
- sky/templates/sky-serve-controller.yaml.j2 +22 -2
- sky/templates/websocket_proxy.py +178 -18
- sky/usage/usage_lib.py +18 -11
- sky/users/__init__.py +0 -0
- sky/users/model.conf +15 -0
- sky/users/permission.py +387 -0
- sky/users/rbac.py +121 -0
- sky/users/server.py +720 -0
- sky/users/token_service.py +218 -0
- sky/utils/accelerator_registry.py +34 -5
- sky/utils/admin_policy_utils.py +84 -38
- sky/utils/annotations.py +16 -5
- sky/utils/asyncio_utils.py +78 -0
- sky/utils/auth_utils.py +153 -0
- sky/utils/benchmark_utils.py +60 -0
- sky/utils/cli_utils/status_utils.py +159 -86
- sky/utils/cluster_utils.py +31 -9
- sky/utils/command_runner.py +354 -68
- sky/utils/command_runner.pyi +93 -3
- sky/utils/common.py +35 -8
- sky/utils/common_utils.py +310 -87
- sky/utils/config_utils.py +87 -5
- sky/utils/context.py +402 -0
- sky/utils/context_utils.py +222 -0
- sky/utils/controller_utils.py +264 -89
- sky/utils/dag_utils.py +31 -12
- sky/utils/db/__init__.py +0 -0
- sky/utils/db/db_utils.py +470 -0
- sky/utils/db/migration_utils.py +133 -0
- sky/utils/directory_utils.py +12 -0
- sky/utils/env_options.py +13 -0
- sky/utils/git.py +567 -0
- sky/utils/git_clone.sh +460 -0
- sky/utils/infra_utils.py +195 -0
- sky/utils/kubernetes/cleanup-tunnel.sh +62 -0
- sky/utils/kubernetes/config_map_utils.py +133 -0
- sky/utils/kubernetes/create_cluster.sh +13 -27
- sky/utils/kubernetes/delete_cluster.sh +10 -7
- sky/utils/kubernetes/deploy_remote_cluster.py +1299 -0
- sky/utils/kubernetes/exec_kubeconfig_converter.py +22 -31
- sky/utils/kubernetes/generate_kind_config.py +6 -66
- sky/utils/kubernetes/generate_kubeconfig.sh +4 -1
- sky/utils/kubernetes/gpu_labeler.py +5 -5
- sky/utils/kubernetes/kubernetes_deploy_utils.py +354 -47
- sky/utils/kubernetes/ssh-tunnel.sh +379 -0
- sky/utils/kubernetes/ssh_utils.py +221 -0
- sky/utils/kubernetes_enums.py +8 -15
- sky/utils/lock_events.py +94 -0
- sky/utils/locks.py +368 -0
- sky/utils/log_utils.py +300 -6
- sky/utils/perf_utils.py +22 -0
- sky/utils/resource_checker.py +298 -0
- sky/utils/resources_utils.py +249 -32
- sky/utils/rich_utils.py +213 -37
- sky/utils/schemas.py +905 -147
- sky/utils/serialize_utils.py +16 -0
- sky/utils/status_lib.py +10 -0
- sky/utils/subprocess_utils.py +38 -15
- sky/utils/tempstore.py +70 -0
- sky/utils/timeline.py +24 -52
- sky/utils/ux_utils.py +84 -15
- sky/utils/validator.py +11 -1
- sky/utils/volume.py +86 -0
- sky/utils/yaml_utils.py +111 -0
- sky/volumes/__init__.py +13 -0
- sky/volumes/client/__init__.py +0 -0
- sky/volumes/client/sdk.py +149 -0
- sky/volumes/server/__init__.py +0 -0
- sky/volumes/server/core.py +258 -0
- sky/volumes/server/server.py +122 -0
- sky/volumes/volume.py +212 -0
- sky/workspaces/__init__.py +0 -0
- sky/workspaces/core.py +655 -0
- sky/workspaces/server.py +101 -0
- sky/workspaces/utils.py +56 -0
- skypilot_nightly-1.0.0.dev20251107.dist-info/METADATA +675 -0
- skypilot_nightly-1.0.0.dev20251107.dist-info/RECORD +594 -0
- {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/WHEEL +1 -1
- sky/benchmark/benchmark_state.py +0 -256
- sky/benchmark/benchmark_utils.py +0 -641
- sky/clouds/service_catalog/constants.py +0 -7
- sky/dashboard/out/_next/static/LksQgChY5izXjokL3LcEu/_buildManifest.js +0 -1
- sky/dashboard/out/_next/static/chunks/236-f49500b82ad5392d.js +0 -6
- sky/dashboard/out/_next/static/chunks/312-c3c8845990db8ffc.js +0 -15
- sky/dashboard/out/_next/static/chunks/37-0a572fe0dbb89c4d.js +0 -6
- sky/dashboard/out/_next/static/chunks/678-206dddca808e6d16.js +0 -59
- sky/dashboard/out/_next/static/chunks/845-0f8017370869e269.js +0 -1
- sky/dashboard/out/_next/static/chunks/979-7bf73a4c7cea0f5c.js +0 -1
- sky/dashboard/out/_next/static/chunks/fd9d1056-2821b0f0cabcd8bd.js +0 -1
- sky/dashboard/out/_next/static/chunks/framework-87d061ee6ed71b28.js +0 -33
- sky/dashboard/out/_next/static/chunks/main-app-241eb28595532291.js +0 -1
- sky/dashboard/out/_next/static/chunks/main-e0e2335212e72357.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/_app-e6b013bc3f77ad60.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/_error-1be831200e60c5c0.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-e15db85d0ea1fbe1.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-f383db7389368ea7.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/clusters-a93b93e10b8b074e.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/index-f9f039532ca8cbc4.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-03f279c6741fb48b.js +0 -1
- sky/dashboard/out/_next/static/chunks/pages/jobs-a75029b67aab6a2e.js +0 -1
- sky/dashboard/out/_next/static/chunks/webpack-830f59b8404e96b8.js +0 -1
- sky/dashboard/out/_next/static/css/c6933bbb2ce7f4dd.css +0 -3
- sky/jobs/dashboard/dashboard.py +0 -223
- sky/jobs/dashboard/static/favicon.ico +0 -0
- sky/jobs/dashboard/templates/index.html +0 -831
- sky/jobs/server/dashboard_utils.py +0 -69
- sky/skylet/providers/scp/__init__.py +0 -2
- sky/skylet/providers/scp/config.py +0 -149
- sky/skylet/providers/scp/node_provider.py +0 -578
- sky/templates/kubernetes-ssh-jump.yml.j2 +0 -94
- sky/utils/db_utils.py +0 -100
- sky/utils/kubernetes/deploy_remote_cluster.sh +0 -308
- sky/utils/kubernetes/ssh_jump_lifecycle_manager.py +0 -191
- skypilot_nightly-1.0.0.dev20250509.dist-info/METADATA +0 -361
- skypilot_nightly-1.0.0.dev20250509.dist-info/RECORD +0 -396
- /sky/{clouds/service_catalog → catalog}/config.py +0 -0
- /sky/{benchmark → catalog/data_fetchers}/__init__.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_azure.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_fluidstack.py +0 -0
- /sky/{clouds/service_catalog → catalog}/data_fetchers/fetch_ibm.py +0 -0
- /sky/{clouds/service_catalog/data_fetchers → client/cli}/__init__.py +0 -0
- /sky/dashboard/out/_next/static/{LksQgChY5izXjokL3LcEu → zB0ed6ge_W1MDszVHhijS}/_ssgManifest.js +0 -0
- {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/entry_points.txt +0 -0
- {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/licenses/LICENSE +0 -0
- {skypilot_nightly-1.0.0.dev20250509.dist-info → skypilot_nightly-1.0.0.dev20251107.dist-info}/top_level.txt +0 -0
sky/adaptors/seeweb.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
""" Seeweb Adaptor """
|
|
2
|
+
import configparser
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import pydantic
|
|
7
|
+
import requests # type: ignore
|
|
8
|
+
|
|
9
|
+
from sky.adaptors import common
|
|
10
|
+
from sky.utils import annotations
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SeewebError(Exception):
|
|
14
|
+
"""Base exception for Seeweb adaptor errors."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SeewebCredentialsFileNotFound(SeewebError):
|
|
18
|
+
"""Raised when the Seeweb credentials file is missing."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SeewebApiKeyMissing(SeewebError):
|
|
22
|
+
"""Raised when the Seeweb API key is missing or empty."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SeewebAuthenticationError(SeewebError):
|
|
26
|
+
"""Raised when authenticating with Seeweb API fails."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_IMPORT_ERROR_MESSAGE = ('Failed to import dependencies for Seeweb.'
|
|
30
|
+
'Try pip install "skypilot[seeweb]"')
|
|
31
|
+
|
|
32
|
+
ecsapi = common.LazyImport(
|
|
33
|
+
'ecsapi',
|
|
34
|
+
import_error_message=_IMPORT_ERROR_MESSAGE,
|
|
35
|
+
)
|
|
36
|
+
boto3 = common.LazyImport('boto3', import_error_message=_IMPORT_ERROR_MESSAGE)
|
|
37
|
+
botocore = common.LazyImport('botocore',
|
|
38
|
+
import_error_message=_IMPORT_ERROR_MESSAGE)
|
|
39
|
+
|
|
40
|
+
_LAZY_MODULES = (ecsapi, boto3, botocore)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@common.load_lazy_modules(_LAZY_MODULES)
|
|
44
|
+
def check_compute_credentials() -> bool:
|
|
45
|
+
"""Checks if the user has access credentials to Seeweb's compute service.
|
|
46
|
+
|
|
47
|
+
Returns True if credentials are valid; otherwise raises a SeewebError.
|
|
48
|
+
"""
|
|
49
|
+
# Read API key from standard Seeweb configuration file
|
|
50
|
+
key_path = pathlib.Path('~/.seeweb_cloud/seeweb_keys').expanduser()
|
|
51
|
+
if not key_path.exists():
|
|
52
|
+
raise SeewebCredentialsFileNotFound(
|
|
53
|
+
'Missing Seeweb API key file ~/.seeweb_cloud/seeweb_keys')
|
|
54
|
+
|
|
55
|
+
parser = configparser.ConfigParser()
|
|
56
|
+
parser.read(key_path)
|
|
57
|
+
try:
|
|
58
|
+
api_key = parser['DEFAULT']['api_key'].strip()
|
|
59
|
+
except KeyError as e:
|
|
60
|
+
raise SeewebApiKeyMissing(
|
|
61
|
+
'Missing api_key in ~/.seeweb_cloud/seeweb_keys') from e
|
|
62
|
+
if not api_key:
|
|
63
|
+
raise SeewebApiKeyMissing(
|
|
64
|
+
'Empty api_key in ~/.seeweb_cloud/seeweb_keys')
|
|
65
|
+
|
|
66
|
+
# Test connection by fetching servers list to validate the key
|
|
67
|
+
try:
|
|
68
|
+
seeweb_client = ecsapi.Api(token=api_key)
|
|
69
|
+
seeweb_client.fetch_servers()
|
|
70
|
+
except Exception as e: # pylint: disable=broad-except
|
|
71
|
+
raise SeewebAuthenticationError(
|
|
72
|
+
f'Unable to authenticate with Seeweb API: {e}') from e
|
|
73
|
+
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@common.load_lazy_modules(_LAZY_MODULES)
|
|
78
|
+
def check_storage_credentials() -> bool:
|
|
79
|
+
"""Checks if the user has access credentials to Seeweb's storage service.
|
|
80
|
+
|
|
81
|
+
Mirrors compute credentials validation.
|
|
82
|
+
"""
|
|
83
|
+
return check_compute_credentials()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@common.load_lazy_modules(_LAZY_MODULES)
|
|
87
|
+
@annotations.lru_cache(scope='global', maxsize=1)
|
|
88
|
+
def client():
|
|
89
|
+
"""Returns an authenticated ecsapi.Api object."""
|
|
90
|
+
# Create authenticated client using the same credential pattern
|
|
91
|
+
key_path = pathlib.Path('~/.seeweb_cloud/seeweb_keys').expanduser()
|
|
92
|
+
if not key_path.exists():
|
|
93
|
+
raise SeewebCredentialsFileNotFound(
|
|
94
|
+
'Missing Seeweb API key file ~/.seeweb_cloud/seeweb_keys')
|
|
95
|
+
|
|
96
|
+
parser = configparser.ConfigParser()
|
|
97
|
+
parser.read(key_path)
|
|
98
|
+
try:
|
|
99
|
+
api_key = parser['DEFAULT']['api_key'].strip()
|
|
100
|
+
except KeyError as e:
|
|
101
|
+
raise SeewebApiKeyMissing(
|
|
102
|
+
'Missing api_key in ~/.seeweb_cloud/seeweb_keys') from e
|
|
103
|
+
if not api_key:
|
|
104
|
+
raise SeewebApiKeyMissing(
|
|
105
|
+
'Empty api_key in ~/.seeweb_cloud/seeweb_keys')
|
|
106
|
+
|
|
107
|
+
api = ecsapi.Api(token=api_key)
|
|
108
|
+
|
|
109
|
+
# Monkey-patch fetch_servers to be tolerant to API schema mismatches.
|
|
110
|
+
orig_fetch_servers = api.fetch_servers
|
|
111
|
+
orig_delete_server = api.delete_server
|
|
112
|
+
|
|
113
|
+
def _tolerant_fetch_servers(
|
|
114
|
+
timeout: Optional[int] = None): # type: ignore[override]
|
|
115
|
+
try:
|
|
116
|
+
return orig_fetch_servers(timeout=timeout)
|
|
117
|
+
except pydantic.ValidationError:
|
|
118
|
+
# Fallback path: fetch raw JSON, drop snapshot fields, then validate
|
|
119
|
+
# pylint: disable=protected-access
|
|
120
|
+
base_url = api._Api__generate_base_url() # type: ignore
|
|
121
|
+
headers = api._Api__generate_authentication_headers(
|
|
122
|
+
) # type: ignore
|
|
123
|
+
url = f'{base_url}/servers'
|
|
124
|
+
resp = requests.get(url, headers=headers, timeout=timeout or 15)
|
|
125
|
+
resp.raise_for_status()
|
|
126
|
+
data = resp.json()
|
|
127
|
+
try:
|
|
128
|
+
servers = data.get('server', [])
|
|
129
|
+
for s in servers:
|
|
130
|
+
s.pop('last_restored_snapshot', None)
|
|
131
|
+
except (KeyError, TypeError, ValueError):
|
|
132
|
+
pass
|
|
133
|
+
server_list_response_cls = ecsapi._server._ServerListResponse
|
|
134
|
+
servers_response = server_list_response_cls.model_validate(data)
|
|
135
|
+
return servers_response.server
|
|
136
|
+
|
|
137
|
+
api.fetch_servers = _tolerant_fetch_servers # type: ignore[assignment]
|
|
138
|
+
|
|
139
|
+
def _tolerant_delete_server(server_name: str,
|
|
140
|
+
timeout: Optional[int] = None):
|
|
141
|
+
try:
|
|
142
|
+
return orig_delete_server(server_name, timeout=timeout)
|
|
143
|
+
except pydantic.ValidationError:
|
|
144
|
+
# Fallback: perform raw DELETE and interpret not_found as success
|
|
145
|
+
# pylint: disable=protected-access
|
|
146
|
+
base_url = api._Api__generate_base_url() # type: ignore
|
|
147
|
+
headers = api._Api__generate_authentication_headers(
|
|
148
|
+
) # type: ignore
|
|
149
|
+
url = f'{base_url}/servers/{server_name}'
|
|
150
|
+
resp = requests.delete(url, headers=headers, timeout=timeout or 15)
|
|
151
|
+
# Treat 404 as idempotent success
|
|
152
|
+
if resp.status_code == 404:
|
|
153
|
+
return None
|
|
154
|
+
# Some APIs return {status: 'not_found', message: ...}
|
|
155
|
+
try:
|
|
156
|
+
data = resp.json()
|
|
157
|
+
if isinstance(data, dict) and data.get('status') == 'not_found':
|
|
158
|
+
return None
|
|
159
|
+
except (ValueError, TypeError):
|
|
160
|
+
pass
|
|
161
|
+
# If not clearly not_found, re-raise original behavior
|
|
162
|
+
resp.raise_for_status()
|
|
163
|
+
# Best-effort: return None to indicate deletion requested
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
api.delete_server = _tolerant_delete_server # type: ignore[assignment]
|
|
167
|
+
return api
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Shadeform cloud adaptor."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import socket
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from sky import sky_logging
|
|
10
|
+
from sky.provision.shadeform import shadeform_utils
|
|
11
|
+
from sky.utils import common_utils
|
|
12
|
+
|
|
13
|
+
logger = sky_logging.init_logger(__name__)
|
|
14
|
+
|
|
15
|
+
_shadeform_sdk = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def import_package(func):
|
|
19
|
+
|
|
20
|
+
@functools.wraps(func)
|
|
21
|
+
def wrapper(*args, **kwargs):
|
|
22
|
+
global _shadeform_sdk
|
|
23
|
+
if _shadeform_sdk is None:
|
|
24
|
+
try:
|
|
25
|
+
import shadeform as _shadeform # pylint: disable=import-outside-toplevel
|
|
26
|
+
_shadeform_sdk = _shadeform
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise ImportError(
|
|
29
|
+
'Failed to import dependencies for Shadeform. '
|
|
30
|
+
'Try pip install "skypilot[shadeform]"') from None
|
|
31
|
+
return func(*args, **kwargs)
|
|
32
|
+
|
|
33
|
+
return wrapper
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@import_package
|
|
37
|
+
def shadeform():
|
|
38
|
+
"""Return the shadeform package."""
|
|
39
|
+
return _shadeform_sdk
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def list_ssh_keys() -> List[Dict[str, Any]]:
|
|
43
|
+
"""List all SSH keys in Shadeform account."""
|
|
44
|
+
try:
|
|
45
|
+
response = shadeform_utils.get_ssh_keys()
|
|
46
|
+
return response.get('ssh_keys', [])
|
|
47
|
+
except (ValueError, KeyError, requests.exceptions.RequestException) as e:
|
|
48
|
+
logger.warning(f'Failed to list SSH keys from Shadeform: {e}')
|
|
49
|
+
return []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def add_ssh_key_to_shadeform(public_key: str) -> Optional[str]:
|
|
53
|
+
"""Add SSH key to Shadeform if it doesn't already exist.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
public_key: The SSH public key string.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The name of the key if added successfully, None otherwise.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
# Check if key already exists
|
|
63
|
+
existing_keys = list_ssh_keys()
|
|
64
|
+
key_exists = False
|
|
65
|
+
key_id = None
|
|
66
|
+
for key in existing_keys:
|
|
67
|
+
if key.get('public_key') == public_key:
|
|
68
|
+
key_exists = True
|
|
69
|
+
key_id = key.get('id')
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
if key_exists:
|
|
73
|
+
logger.info('SSH key already exists in Shadeform account')
|
|
74
|
+
return key_id
|
|
75
|
+
|
|
76
|
+
# Generate a unique key name
|
|
77
|
+
hostname = socket.gethostname()
|
|
78
|
+
key_name = f'skypilot-{hostname}-{common_utils.get_user_hash()[:8]}'
|
|
79
|
+
|
|
80
|
+
# Add the key
|
|
81
|
+
response = shadeform_utils.add_ssh_key(name=key_name,
|
|
82
|
+
public_key=public_key)
|
|
83
|
+
key_id = response['id']
|
|
84
|
+
logger.info(f'Added SSH key to Shadeform: {key_name, key_id}')
|
|
85
|
+
return key_id
|
|
86
|
+
|
|
87
|
+
except (ValueError, KeyError, requests.exceptions.RequestException) as e:
|
|
88
|
+
logger.warning(f'Failed to add SSH key to Shadeform: {e}')
|
|
89
|
+
return None
|
sky/admin_policy.py
CHANGED
|
@@ -4,12 +4,26 @@ import dataclasses
|
|
|
4
4
|
import typing
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
|
+
import colorama
|
|
8
|
+
import pydantic
|
|
9
|
+
|
|
10
|
+
import sky
|
|
11
|
+
from sky import exceptions
|
|
12
|
+
from sky import models
|
|
13
|
+
from sky.adaptors import common as adaptors_common
|
|
14
|
+
from sky.server.requests import request_names
|
|
15
|
+
from sky.utils import common_utils
|
|
16
|
+
from sky.utils import config_utils
|
|
17
|
+
from sky.utils import ux_utils
|
|
18
|
+
from sky.utils import yaml_utils
|
|
19
|
+
|
|
7
20
|
if typing.TYPE_CHECKING:
|
|
8
|
-
import
|
|
21
|
+
import requests
|
|
22
|
+
else:
|
|
23
|
+
requests = adaptors_common.LazyImport('requests')
|
|
9
24
|
|
|
10
25
|
|
|
11
|
-
|
|
12
|
-
class RequestOptions:
|
|
26
|
+
class RequestOptions(pydantic.BaseModel):
|
|
13
27
|
"""Request options for admin policy.
|
|
14
28
|
|
|
15
29
|
Args:
|
|
@@ -21,11 +35,28 @@ class RequestOptions:
|
|
|
21
35
|
dryrun: Is the request a dryrun?
|
|
22
36
|
"""
|
|
23
37
|
cluster_name: Optional[str]
|
|
38
|
+
# Keep these two fields for backward compatibility. The values are copied
|
|
39
|
+
# from task.resources.autostop_config, so that legacy admin policy plugins
|
|
40
|
+
# can still read the correct autostop config from request options before
|
|
41
|
+
# we drop the compatibility.
|
|
42
|
+
# TODO(aylei): remove these fields after 0.12.0
|
|
24
43
|
idle_minutes_to_autostop: Optional[int]
|
|
25
44
|
down: bool
|
|
26
45
|
dryrun: bool
|
|
27
46
|
|
|
28
47
|
|
|
48
|
+
class _UserRequestBody(pydantic.BaseModel):
|
|
49
|
+
"""Auxiliary model to validate and serialize a user request."""
|
|
50
|
+
# We have to use serialized YAML string, instead of a dict, because dict
|
|
51
|
+
# will be converted to JSON string, which will lose the None key.
|
|
52
|
+
task: str
|
|
53
|
+
skypilot_config: str
|
|
54
|
+
request_name: str
|
|
55
|
+
request_options: Optional[RequestOptions] = None
|
|
56
|
+
at_client_side: bool = False
|
|
57
|
+
user: str
|
|
58
|
+
|
|
59
|
+
|
|
29
60
|
@dataclasses.dataclass
|
|
30
61
|
class UserRequest:
|
|
31
62
|
"""A user request.
|
|
@@ -45,20 +76,101 @@ class UserRequest:
|
|
|
45
76
|
task: User specified task.
|
|
46
77
|
skypilot_config: Global skypilot config to be used in this request.
|
|
47
78
|
request_options: Request options. It is None for jobs and services.
|
|
79
|
+
at_client_side: Is the request intercepted by the policy at client-side?
|
|
80
|
+
user: User who made the request.
|
|
81
|
+
Only available on the server side.
|
|
82
|
+
This value is None if at_client_side is True.
|
|
48
83
|
"""
|
|
49
84
|
task: 'sky.Task'
|
|
50
85
|
skypilot_config: 'sky.Config'
|
|
86
|
+
request_name: request_names.AdminPolicyRequestName
|
|
51
87
|
request_options: Optional['RequestOptions'] = None
|
|
88
|
+
at_client_side: bool = False
|
|
89
|
+
user: Optional['models.User'] = None
|
|
90
|
+
|
|
91
|
+
def encode(self) -> str:
|
|
92
|
+
return _UserRequestBody(
|
|
93
|
+
task=yaml_utils.dump_yaml_str(self.task.to_yaml_config()),
|
|
94
|
+
skypilot_config=yaml_utils.dump_yaml_str(dict(
|
|
95
|
+
self.skypilot_config)),
|
|
96
|
+
request_name=self.request_name.value,
|
|
97
|
+
request_options=self.request_options,
|
|
98
|
+
at_client_side=self.at_client_side,
|
|
99
|
+
user=(yaml_utils.dump_yaml_str(self.user.to_dict())
|
|
100
|
+
if self.user is not None else ''),
|
|
101
|
+
).model_dump_json()
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def decode(cls, body: str) -> 'UserRequest':
|
|
105
|
+
user_request_body = _UserRequestBody.model_validate_json(body)
|
|
106
|
+
user_dict = yaml_utils.read_yaml_str(
|
|
107
|
+
user_request_body.user) if user_request_body.user != '' else None
|
|
108
|
+
user = models.User(
|
|
109
|
+
id=user_dict['id'],
|
|
110
|
+
name=user_dict['name']) if user_dict is not None else None
|
|
111
|
+
return cls(
|
|
112
|
+
task=sky.Task.from_yaml_config(
|
|
113
|
+
yaml_utils.read_yaml_all_str(user_request_body.task)[0]),
|
|
114
|
+
skypilot_config=config_utils.Config.from_dict(
|
|
115
|
+
yaml_utils.read_yaml_all_str(
|
|
116
|
+
user_request_body.skypilot_config)[0]),
|
|
117
|
+
request_name=request_names.AdminPolicyRequestName(
|
|
118
|
+
user_request_body.request_name),
|
|
119
|
+
request_options=user_request_body.request_options,
|
|
120
|
+
at_client_side=user_request_body.at_client_side,
|
|
121
|
+
user=user,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _MutatedUserRequestBody(pydantic.BaseModel):
|
|
126
|
+
"""Auxiliary model to validate and serialize a user request."""
|
|
127
|
+
task: str
|
|
128
|
+
skypilot_config: str
|
|
52
129
|
|
|
53
130
|
|
|
54
131
|
@dataclasses.dataclass
|
|
55
132
|
class MutatedUserRequest:
|
|
133
|
+
"""Mutated user request."""
|
|
134
|
+
|
|
56
135
|
task: 'sky.Task'
|
|
57
136
|
skypilot_config: 'sky.Config'
|
|
58
137
|
|
|
138
|
+
def encode(self) -> str:
|
|
139
|
+
return _MutatedUserRequestBody(
|
|
140
|
+
task=yaml_utils.dump_yaml_str(self.task.to_yaml_config()),
|
|
141
|
+
skypilot_config=yaml_utils.dump_yaml_str(dict(
|
|
142
|
+
self.skypilot_config),)).model_dump_json()
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def decode(cls, mutated_user_request_body: str,
|
|
146
|
+
original_request: UserRequest) -> 'MutatedUserRequest':
|
|
147
|
+
mutated_user_request_body = _MutatedUserRequestBody.model_validate_json(
|
|
148
|
+
mutated_user_request_body)
|
|
149
|
+
task = sky.Task.from_yaml_config(
|
|
150
|
+
yaml_utils.read_yaml_all_str(mutated_user_request_body.task)[0])
|
|
151
|
+
# Some internal Task fields are not serialized. We need to manually
|
|
152
|
+
# restore them from the original request.
|
|
153
|
+
task.managed_job_dag = original_request.task.managed_job_dag
|
|
154
|
+
task.service_name = original_request.task.service_name
|
|
155
|
+
return cls(task=task,
|
|
156
|
+
skypilot_config=config_utils.Config.from_dict(
|
|
157
|
+
yaml_utils.read_yaml_all_str(
|
|
158
|
+
mutated_user_request_body.skypilot_config)[0],))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class PolicyInterface:
|
|
162
|
+
"""Interface for admin-defined policy for user requests."""
|
|
163
|
+
|
|
164
|
+
@abc.abstractmethod
|
|
165
|
+
def apply(self, user_request: UserRequest) -> MutatedUserRequest:
|
|
166
|
+
"""Apply the admin policy to the user request."""
|
|
167
|
+
|
|
168
|
+
def __str__(self):
|
|
169
|
+
return f'{self.__class__.__name__}'
|
|
170
|
+
|
|
59
171
|
|
|
60
172
|
# pylint: disable=line-too-long
|
|
61
|
-
class AdminPolicy:
|
|
173
|
+
class AdminPolicy(PolicyInterface):
|
|
62
174
|
"""Abstract interface of an admin-defined policy for all user requests.
|
|
63
175
|
|
|
64
176
|
Admins can implement a subclass of AdminPolicy with the following signature:
|
|
@@ -99,3 +211,74 @@ class AdminPolicy:
|
|
|
99
211
|
"""
|
|
100
212
|
raise NotImplementedError(
|
|
101
213
|
'Your policy must implement validate_and_mutate')
|
|
214
|
+
|
|
215
|
+
def apply(self, user_request: UserRequest) -> MutatedUserRequest:
|
|
216
|
+
return self.validate_and_mutate(user_request)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class PolicyTemplate(PolicyInterface):
|
|
220
|
+
"""Admin policy template that can be instantiated to create a policy."""
|
|
221
|
+
|
|
222
|
+
@abc.abstractmethod
|
|
223
|
+
def validate_and_mutate(self,
|
|
224
|
+
user_request: UserRequest) -> MutatedUserRequest:
|
|
225
|
+
"""Validates and mutates the user request and returns mutated request.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
user_request: The user request to validate and mutate.
|
|
229
|
+
UserRequest contains (sky.Task, sky.Config)
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
MutatedUserRequest: The mutated user request.
|
|
233
|
+
|
|
234
|
+
Raises:
|
|
235
|
+
Exception to throw if the user request failed the validation.
|
|
236
|
+
"""
|
|
237
|
+
raise NotImplementedError(
|
|
238
|
+
'Your policy must implement validate_and_mutate')
|
|
239
|
+
|
|
240
|
+
def apply(self, user_request: UserRequest) -> MutatedUserRequest:
|
|
241
|
+
return self.validate_and_mutate(user_request)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class RestfulAdminPolicy(PolicyTemplate):
|
|
245
|
+
"""Admin policy that calls a RESTful API for validation."""
|
|
246
|
+
|
|
247
|
+
def __init__(self, policy_url: str):
|
|
248
|
+
super().__init__()
|
|
249
|
+
self.policy_url = policy_url
|
|
250
|
+
|
|
251
|
+
def validate_and_mutate(self,
|
|
252
|
+
user_request: UserRequest) -> MutatedUserRequest:
|
|
253
|
+
try:
|
|
254
|
+
response = requests.post(
|
|
255
|
+
self.policy_url,
|
|
256
|
+
json=user_request.encode(),
|
|
257
|
+
headers={'Content-Type': 'application/json'},
|
|
258
|
+
# TODO(aylei): make this configurable
|
|
259
|
+
timeout=30)
|
|
260
|
+
if response.status_code == 400:
|
|
261
|
+
raise exceptions.UserRequestRejectedByPolicy(
|
|
262
|
+
f'{colorama.Fore.RED}User request is rejected by admin '
|
|
263
|
+
f'policy {self.policy_url}{colorama.Fore.RESET}: '
|
|
264
|
+
f'{response.text}')
|
|
265
|
+
response.raise_for_status()
|
|
266
|
+
except requests.exceptions.RequestException as e:
|
|
267
|
+
with ux_utils.print_exception_no_traceback():
|
|
268
|
+
raise exceptions.RestfulPolicyError(
|
|
269
|
+
f'Failed to call admin policy URL '
|
|
270
|
+
f'{self.policy_url}: {e}') from None
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
mutated_user_request = MutatedUserRequest.decode(
|
|
274
|
+
response.json(), user_request)
|
|
275
|
+
except Exception as e: # pylint: disable=broad-except
|
|
276
|
+
with ux_utils.print_exception_no_traceback():
|
|
277
|
+
raise exceptions.RestfulPolicyError(
|
|
278
|
+
f'Failed to decode response from admin policy URL '
|
|
279
|
+
f'{self.policy_url}: {common_utils.format_exception(e, use_bracket=True)}'
|
|
280
|
+
) from None
|
|
281
|
+
return mutated_user_request
|
|
282
|
+
|
|
283
|
+
def __repr__(self):
|
|
284
|
+
return f'RestfulAdminPolicy(policy_url={self.policy_url})'
|