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.
Files changed (397) hide show
  1. sky/__init__.py +10 -2
  2. sky/adaptors/aws.py +81 -16
  3. sky/adaptors/common.py +25 -2
  4. sky/adaptors/coreweave.py +278 -0
  5. sky/adaptors/do.py +8 -2
  6. sky/adaptors/gcp.py +11 -0
  7. sky/adaptors/ibm.py +5 -2
  8. sky/adaptors/kubernetes.py +64 -0
  9. sky/adaptors/nebius.py +3 -1
  10. sky/adaptors/primeintellect.py +1 -0
  11. sky/adaptors/seeweb.py +183 -0
  12. sky/adaptors/shadeform.py +89 -0
  13. sky/admin_policy.py +20 -0
  14. sky/authentication.py +157 -263
  15. sky/backends/__init__.py +3 -2
  16. sky/backends/backend.py +11 -3
  17. sky/backends/backend_utils.py +588 -184
  18. sky/backends/cloud_vm_ray_backend.py +1088 -904
  19. sky/backends/local_docker_backend.py +9 -5
  20. sky/backends/task_codegen.py +633 -0
  21. sky/backends/wheel_utils.py +18 -0
  22. sky/catalog/__init__.py +8 -0
  23. sky/catalog/aws_catalog.py +4 -0
  24. sky/catalog/common.py +19 -1
  25. sky/catalog/data_fetchers/fetch_aws.py +102 -80
  26. sky/catalog/data_fetchers/fetch_gcp.py +30 -3
  27. sky/catalog/data_fetchers/fetch_nebius.py +9 -6
  28. sky/catalog/data_fetchers/fetch_runpod.py +698 -0
  29. sky/catalog/data_fetchers/fetch_seeweb.py +329 -0
  30. sky/catalog/data_fetchers/fetch_shadeform.py +142 -0
  31. sky/catalog/kubernetes_catalog.py +24 -28
  32. sky/catalog/primeintellect_catalog.py +95 -0
  33. sky/catalog/runpod_catalog.py +5 -1
  34. sky/catalog/seeweb_catalog.py +184 -0
  35. sky/catalog/shadeform_catalog.py +165 -0
  36. sky/check.py +73 -43
  37. sky/client/cli/command.py +675 -412
  38. sky/client/cli/flags.py +4 -2
  39. sky/{volumes/utils.py → client/cli/table_utils.py} +111 -13
  40. sky/client/cli/utils.py +79 -0
  41. sky/client/common.py +12 -2
  42. sky/client/sdk.py +132 -63
  43. sky/client/sdk_async.py +34 -33
  44. sky/cloud_stores.py +82 -3
  45. sky/clouds/__init__.py +6 -0
  46. sky/clouds/aws.py +337 -129
  47. sky/clouds/azure.py +24 -18
  48. sky/clouds/cloud.py +40 -13
  49. sky/clouds/cudo.py +16 -13
  50. sky/clouds/do.py +9 -7
  51. sky/clouds/fluidstack.py +12 -5
  52. sky/clouds/gcp.py +14 -7
  53. sky/clouds/hyperbolic.py +12 -5
  54. sky/clouds/ibm.py +12 -5
  55. sky/clouds/kubernetes.py +80 -45
  56. sky/clouds/lambda_cloud.py +12 -5
  57. sky/clouds/nebius.py +23 -9
  58. sky/clouds/oci.py +19 -12
  59. sky/clouds/paperspace.py +4 -1
  60. sky/clouds/primeintellect.py +317 -0
  61. sky/clouds/runpod.py +85 -24
  62. sky/clouds/scp.py +12 -8
  63. sky/clouds/seeweb.py +477 -0
  64. sky/clouds/shadeform.py +400 -0
  65. sky/clouds/ssh.py +4 -2
  66. sky/clouds/utils/scp_utils.py +61 -50
  67. sky/clouds/vast.py +33 -27
  68. sky/clouds/vsphere.py +14 -16
  69. sky/core.py +174 -165
  70. sky/dashboard/out/404.html +1 -1
  71. sky/dashboard/out/_next/static/96_E2yl3QAiIJGOYCkSpB/_buildManifest.js +1 -0
  72. sky/dashboard/out/_next/static/chunks/1141-e6aa9ab418717c59.js +11 -0
  73. sky/dashboard/out/_next/static/chunks/1871-7e202677c42f43fe.js +6 -0
  74. sky/dashboard/out/_next/static/chunks/2260-7703229c33c5ebd5.js +1 -0
  75. sky/dashboard/out/_next/static/chunks/2369.fc20f0c2c8ed9fe7.js +15 -0
  76. sky/dashboard/out/_next/static/chunks/2755.edd818326d489a1d.js +26 -0
  77. sky/dashboard/out/_next/static/chunks/3294.20a8540fe697d5ee.js +1 -0
  78. sky/dashboard/out/_next/static/chunks/3785.7e245f318f9d1121.js +1 -0
  79. sky/dashboard/out/_next/static/chunks/{6601-06114c982db410b6.js → 3800-7b45f9fbb6308557.js} +1 -1
  80. sky/dashboard/out/_next/static/chunks/4725.172ede95d1b21022.js +1 -0
  81. sky/dashboard/out/_next/static/chunks/4937.a2baa2df5572a276.js +15 -0
  82. sky/dashboard/out/_next/static/chunks/6212-7bd06f60ba693125.js +13 -0
  83. sky/dashboard/out/_next/static/chunks/6856-8f27d1c10c98def8.js +1 -0
  84. sky/dashboard/out/_next/static/chunks/6990-9146207c4567fdfd.js +1 -0
  85. sky/dashboard/out/_next/static/chunks/7359-c8d04e06886000b3.js +30 -0
  86. sky/dashboard/out/_next/static/chunks/7615-019513abc55b3b47.js +1 -0
  87. sky/dashboard/out/_next/static/chunks/8640.5b9475a2d18c5416.js +16 -0
  88. sky/dashboard/out/_next/static/chunks/8969-452f9d5cbdd2dc73.js +1 -0
  89. sky/dashboard/out/_next/static/chunks/9025.fa408f3242e9028d.js +6 -0
  90. sky/dashboard/out/_next/static/chunks/9353-cff34f7e773b2e2b.js +1 -0
  91. sky/dashboard/out/_next/static/chunks/9360.a536cf6b1fa42355.js +31 -0
  92. sky/dashboard/out/_next/static/chunks/9847.3aaca6bb33455140.js +30 -0
  93. sky/dashboard/out/_next/static/chunks/pages/{_app-ce361c6959bc2001.js → _app-bde01e4a2beec258.js} +1 -1
  94. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-792db96d918c98c9.js +16 -0
  95. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-abfcac9c137aa543.js +1 -0
  96. sky/dashboard/out/_next/static/chunks/pages/clusters-ee39056f9851a3ff.js +1 -0
  97. sky/dashboard/out/_next/static/chunks/pages/infra/{[context]-6563820e094f68ca.js → [context]-c0b5935149902e6f.js} +1 -1
  98. sky/dashboard/out/_next/static/chunks/pages/{infra-aabba60d57826e0f.js → infra-aed0ea19df7cf961.js} +1 -1
  99. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-d66997e2bfc837cf.js +16 -0
  100. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-9faf940b253e3e06.js +21 -0
  101. sky/dashboard/out/_next/static/chunks/pages/jobs-2072b48b617989c9.js +1 -0
  102. sky/dashboard/out/_next/static/chunks/pages/users-f42674164aa73423.js +1 -0
  103. sky/dashboard/out/_next/static/chunks/pages/volumes-b84b948ff357c43e.js +1 -0
  104. sky/dashboard/out/_next/static/chunks/pages/workspaces/{[name]-af76bb06dbb3954f.js → [name]-84a40f8c7c627fe4.js} +1 -1
  105. sky/dashboard/out/_next/static/chunks/pages/{workspaces-7598c33a746cdc91.js → workspaces-531b2f8c4bf89f82.js} +1 -1
  106. sky/dashboard/out/_next/static/chunks/webpack-64e05f17bf2cf8ce.js +1 -0
  107. sky/dashboard/out/_next/static/css/0748ce22df867032.css +3 -0
  108. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  109. sky/dashboard/out/clusters/[cluster].html +1 -1
  110. sky/dashboard/out/clusters.html +1 -1
  111. sky/dashboard/out/config.html +1 -1
  112. sky/dashboard/out/index.html +1 -1
  113. sky/dashboard/out/infra/[context].html +1 -1
  114. sky/dashboard/out/infra.html +1 -1
  115. sky/dashboard/out/jobs/[job].html +1 -1
  116. sky/dashboard/out/jobs/pools/[pool].html +1 -1
  117. sky/dashboard/out/jobs.html +1 -1
  118. sky/dashboard/out/users.html +1 -1
  119. sky/dashboard/out/volumes.html +1 -1
  120. sky/dashboard/out/workspace/new.html +1 -1
  121. sky/dashboard/out/workspaces/[name].html +1 -1
  122. sky/dashboard/out/workspaces.html +1 -1
  123. sky/data/data_utils.py +92 -1
  124. sky/data/mounting_utils.py +162 -29
  125. sky/data/storage.py +200 -19
  126. sky/data/storage_utils.py +10 -45
  127. sky/exceptions.py +18 -7
  128. sky/execution.py +74 -31
  129. sky/global_user_state.py +605 -191
  130. sky/jobs/__init__.py +2 -0
  131. sky/jobs/client/sdk.py +101 -4
  132. sky/jobs/client/sdk_async.py +31 -5
  133. sky/jobs/constants.py +15 -8
  134. sky/jobs/controller.py +726 -284
  135. sky/jobs/file_content_utils.py +128 -0
  136. sky/jobs/log_gc.py +193 -0
  137. sky/jobs/recovery_strategy.py +250 -100
  138. sky/jobs/scheduler.py +271 -173
  139. sky/jobs/server/core.py +367 -114
  140. sky/jobs/server/server.py +81 -35
  141. sky/jobs/server/utils.py +89 -35
  142. sky/jobs/state.py +1498 -620
  143. sky/jobs/utils.py +771 -306
  144. sky/logs/agent.py +40 -5
  145. sky/logs/aws.py +9 -19
  146. sky/metrics/utils.py +282 -39
  147. sky/optimizer.py +1 -1
  148. sky/provision/__init__.py +37 -1
  149. sky/provision/aws/config.py +34 -13
  150. sky/provision/aws/instance.py +5 -2
  151. sky/provision/azure/instance.py +5 -3
  152. sky/provision/common.py +2 -0
  153. sky/provision/cudo/instance.py +4 -3
  154. sky/provision/do/instance.py +4 -3
  155. sky/provision/docker_utils.py +97 -26
  156. sky/provision/fluidstack/instance.py +6 -5
  157. sky/provision/gcp/config.py +6 -1
  158. sky/provision/gcp/instance.py +4 -2
  159. sky/provision/hyperbolic/instance.py +4 -2
  160. sky/provision/instance_setup.py +66 -20
  161. sky/provision/kubernetes/__init__.py +2 -0
  162. sky/provision/kubernetes/config.py +7 -44
  163. sky/provision/kubernetes/constants.py +0 -1
  164. sky/provision/kubernetes/instance.py +609 -213
  165. sky/provision/kubernetes/manifests/fusermount-server-daemonset.yaml +1 -2
  166. sky/provision/kubernetes/network.py +12 -8
  167. sky/provision/kubernetes/network_utils.py +8 -25
  168. sky/provision/kubernetes/utils.py +382 -418
  169. sky/provision/kubernetes/volume.py +150 -18
  170. sky/provision/lambda_cloud/instance.py +16 -13
  171. sky/provision/nebius/instance.py +6 -2
  172. sky/provision/nebius/utils.py +103 -86
  173. sky/provision/oci/instance.py +4 -2
  174. sky/provision/paperspace/instance.py +4 -3
  175. sky/provision/primeintellect/__init__.py +10 -0
  176. sky/provision/primeintellect/config.py +11 -0
  177. sky/provision/primeintellect/instance.py +454 -0
  178. sky/provision/primeintellect/utils.py +398 -0
  179. sky/provision/provisioner.py +30 -9
  180. sky/provision/runpod/__init__.py +2 -0
  181. sky/provision/runpod/instance.py +4 -3
  182. sky/provision/runpod/volume.py +69 -13
  183. sky/provision/scp/instance.py +307 -130
  184. sky/provision/seeweb/__init__.py +11 -0
  185. sky/provision/seeweb/config.py +13 -0
  186. sky/provision/seeweb/instance.py +812 -0
  187. sky/provision/shadeform/__init__.py +11 -0
  188. sky/provision/shadeform/config.py +12 -0
  189. sky/provision/shadeform/instance.py +351 -0
  190. sky/provision/shadeform/shadeform_utils.py +83 -0
  191. sky/provision/vast/instance.py +5 -3
  192. sky/provision/volume.py +164 -0
  193. sky/provision/vsphere/common/ssl_helper.py +1 -1
  194. sky/provision/vsphere/common/vapiconnect.py +2 -1
  195. sky/provision/vsphere/common/vim_utils.py +3 -2
  196. sky/provision/vsphere/instance.py +8 -6
  197. sky/provision/vsphere/vsphere_utils.py +8 -1
  198. sky/resources.py +11 -3
  199. sky/schemas/api/responses.py +107 -6
  200. sky/schemas/db/global_user_state/008_skylet_ssh_tunnel_metadata.py +34 -0
  201. sky/schemas/db/global_user_state/009_last_activity_and_launched_at.py +89 -0
  202. sky/schemas/db/global_user_state/010_save_ssh_key.py +66 -0
  203. sky/schemas/db/global_user_state/011_is_ephemeral.py +34 -0
  204. sky/schemas/db/kv_cache/001_initial_schema.py +29 -0
  205. sky/schemas/db/serve_state/002_yaml_content.py +34 -0
  206. sky/schemas/db/skypilot_config/001_initial_schema.py +30 -0
  207. sky/schemas/db/spot_jobs/002_cluster_pool.py +3 -3
  208. sky/schemas/db/spot_jobs/004_job_file_contents.py +42 -0
  209. sky/schemas/db/spot_jobs/005_logs_gc.py +38 -0
  210. sky/schemas/db/spot_jobs/006_controller_pid_started_at.py +34 -0
  211. sky/schemas/db/spot_jobs/007_config_file_content.py +34 -0
  212. sky/schemas/generated/jobsv1_pb2.py +86 -0
  213. sky/schemas/generated/jobsv1_pb2.pyi +254 -0
  214. sky/schemas/generated/jobsv1_pb2_grpc.py +542 -0
  215. sky/schemas/generated/managed_jobsv1_pb2.py +76 -0
  216. sky/schemas/generated/managed_jobsv1_pb2.pyi +278 -0
  217. sky/schemas/generated/managed_jobsv1_pb2_grpc.py +278 -0
  218. sky/schemas/generated/servev1_pb2.py +58 -0
  219. sky/schemas/generated/servev1_pb2.pyi +115 -0
  220. sky/schemas/generated/servev1_pb2_grpc.py +322 -0
  221. sky/serve/autoscalers.py +2 -0
  222. sky/serve/client/impl.py +55 -21
  223. sky/serve/constants.py +4 -3
  224. sky/serve/controller.py +17 -11
  225. sky/serve/load_balancing_policies.py +1 -1
  226. sky/serve/replica_managers.py +219 -142
  227. sky/serve/serve_rpc_utils.py +179 -0
  228. sky/serve/serve_state.py +63 -54
  229. sky/serve/serve_utils.py +145 -109
  230. sky/serve/server/core.py +46 -25
  231. sky/serve/server/impl.py +311 -162
  232. sky/serve/server/server.py +21 -19
  233. sky/serve/service.py +84 -68
  234. sky/serve/service_spec.py +45 -7
  235. sky/server/auth/loopback.py +38 -0
  236. sky/server/auth/oauth2_proxy.py +12 -7
  237. sky/server/common.py +47 -24
  238. sky/server/config.py +62 -28
  239. sky/server/constants.py +9 -1
  240. sky/server/daemons.py +109 -38
  241. sky/server/metrics.py +76 -96
  242. sky/server/middleware_utils.py +166 -0
  243. sky/server/requests/executor.py +381 -145
  244. sky/server/requests/payloads.py +71 -18
  245. sky/server/requests/preconditions.py +15 -13
  246. sky/server/requests/request_names.py +121 -0
  247. sky/server/requests/requests.py +507 -157
  248. sky/server/requests/serializers/decoders.py +48 -17
  249. sky/server/requests/serializers/encoders.py +85 -20
  250. sky/server/requests/threads.py +117 -0
  251. sky/server/rest.py +116 -24
  252. sky/server/server.py +420 -172
  253. sky/server/stream_utils.py +219 -45
  254. sky/server/uvicorn.py +30 -19
  255. sky/setup_files/MANIFEST.in +6 -1
  256. sky/setup_files/alembic.ini +8 -0
  257. sky/setup_files/dependencies.py +62 -19
  258. sky/setup_files/setup.py +44 -44
  259. sky/sky_logging.py +13 -5
  260. sky/skylet/attempt_skylet.py +106 -24
  261. sky/skylet/configs.py +3 -1
  262. sky/skylet/constants.py +111 -26
  263. sky/skylet/events.py +64 -10
  264. sky/skylet/job_lib.py +141 -104
  265. sky/skylet/log_lib.py +233 -5
  266. sky/skylet/log_lib.pyi +40 -2
  267. sky/skylet/providers/ibm/node_provider.py +12 -8
  268. sky/skylet/providers/ibm/vpc_provider.py +13 -12
  269. sky/skylet/runtime_utils.py +21 -0
  270. sky/skylet/services.py +524 -0
  271. sky/skylet/skylet.py +22 -1
  272. sky/skylet/subprocess_daemon.py +104 -29
  273. sky/skypilot_config.py +99 -79
  274. sky/ssh_node_pools/server.py +9 -8
  275. sky/task.py +221 -104
  276. sky/templates/aws-ray.yml.j2 +1 -0
  277. sky/templates/azure-ray.yml.j2 +1 -0
  278. sky/templates/cudo-ray.yml.j2 +1 -0
  279. sky/templates/do-ray.yml.j2 +1 -0
  280. sky/templates/fluidstack-ray.yml.j2 +1 -0
  281. sky/templates/gcp-ray.yml.j2 +1 -0
  282. sky/templates/hyperbolic-ray.yml.j2 +1 -0
  283. sky/templates/ibm-ray.yml.j2 +2 -1
  284. sky/templates/jobs-controller.yaml.j2 +3 -0
  285. sky/templates/kubernetes-ray.yml.j2 +196 -55
  286. sky/templates/lambda-ray.yml.j2 +1 -0
  287. sky/templates/nebius-ray.yml.j2 +3 -0
  288. sky/templates/oci-ray.yml.j2 +1 -0
  289. sky/templates/paperspace-ray.yml.j2 +1 -0
  290. sky/templates/primeintellect-ray.yml.j2 +72 -0
  291. sky/templates/runpod-ray.yml.j2 +1 -0
  292. sky/templates/scp-ray.yml.j2 +1 -0
  293. sky/templates/seeweb-ray.yml.j2 +171 -0
  294. sky/templates/shadeform-ray.yml.j2 +73 -0
  295. sky/templates/vast-ray.yml.j2 +1 -0
  296. sky/templates/vsphere-ray.yml.j2 +1 -0
  297. sky/templates/websocket_proxy.py +188 -43
  298. sky/usage/usage_lib.py +16 -4
  299. sky/users/permission.py +60 -43
  300. sky/utils/accelerator_registry.py +6 -3
  301. sky/utils/admin_policy_utils.py +18 -5
  302. sky/utils/annotations.py +22 -0
  303. sky/utils/asyncio_utils.py +78 -0
  304. sky/utils/atomic.py +1 -1
  305. sky/utils/auth_utils.py +153 -0
  306. sky/utils/cli_utils/status_utils.py +12 -7
  307. sky/utils/cluster_utils.py +28 -6
  308. sky/utils/command_runner.py +88 -27
  309. sky/utils/command_runner.pyi +36 -3
  310. sky/utils/common.py +3 -1
  311. sky/utils/common_utils.py +37 -4
  312. sky/utils/config_utils.py +1 -14
  313. sky/utils/context.py +127 -40
  314. sky/utils/context_utils.py +73 -18
  315. sky/utils/controller_utils.py +229 -70
  316. sky/utils/db/db_utils.py +95 -18
  317. sky/utils/db/kv_cache.py +149 -0
  318. sky/utils/db/migration_utils.py +24 -7
  319. sky/utils/env_options.py +4 -0
  320. sky/utils/git.py +559 -1
  321. sky/utils/kubernetes/create_cluster.sh +15 -30
  322. sky/utils/kubernetes/delete_cluster.sh +10 -7
  323. sky/utils/kubernetes/{deploy_remote_cluster.py → deploy_ssh_node_pools.py} +258 -380
  324. sky/utils/kubernetes/generate_kind_config.py +6 -66
  325. sky/utils/kubernetes/gpu_labeler.py +13 -3
  326. sky/utils/kubernetes/k8s_gpu_labeler_job.yaml +2 -1
  327. sky/utils/kubernetes/k8s_gpu_labeler_setup.yaml +16 -16
  328. sky/utils/kubernetes/kubernetes_deploy_utils.py +213 -194
  329. sky/utils/kubernetes/rsync_helper.sh +11 -3
  330. sky/utils/kubernetes_enums.py +7 -15
  331. sky/utils/lock_events.py +4 -4
  332. sky/utils/locks.py +128 -31
  333. sky/utils/log_utils.py +0 -319
  334. sky/utils/resource_checker.py +13 -10
  335. sky/utils/resources_utils.py +53 -29
  336. sky/utils/rich_utils.py +8 -4
  337. sky/utils/schemas.py +107 -52
  338. sky/utils/subprocess_utils.py +17 -4
  339. sky/utils/thread_utils.py +91 -0
  340. sky/utils/timeline.py +2 -1
  341. sky/utils/ux_utils.py +35 -1
  342. sky/utils/volume.py +88 -4
  343. sky/utils/yaml_utils.py +9 -0
  344. sky/volumes/client/sdk.py +48 -10
  345. sky/volumes/server/core.py +59 -22
  346. sky/volumes/server/server.py +46 -17
  347. sky/volumes/volume.py +54 -42
  348. sky/workspaces/core.py +57 -21
  349. sky/workspaces/server.py +13 -12
  350. sky_templates/README.md +3 -0
  351. sky_templates/__init__.py +3 -0
  352. sky_templates/ray/__init__.py +0 -0
  353. sky_templates/ray/start_cluster +183 -0
  354. sky_templates/ray/stop_cluster +75 -0
  355. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/METADATA +331 -65
  356. skypilot_nightly-1.0.0.dev20251203.dist-info/RECORD +611 -0
  357. skypilot_nightly-1.0.0.dev20251203.dist-info/top_level.txt +2 -0
  358. sky/client/cli/git.py +0 -549
  359. sky/dashboard/out/_next/static/chunks/1121-408ed10b2f9fce17.js +0 -1
  360. sky/dashboard/out/_next/static/chunks/1141-943efc7aff0f0c06.js +0 -1
  361. sky/dashboard/out/_next/static/chunks/1836-37fede578e2da5f8.js +0 -40
  362. sky/dashboard/out/_next/static/chunks/3015-86cabed5d4669ad0.js +0 -1
  363. sky/dashboard/out/_next/static/chunks/3294.c80326aec9bfed40.js +0 -6
  364. sky/dashboard/out/_next/static/chunks/3785.4872a2f3aa489880.js +0 -1
  365. sky/dashboard/out/_next/static/chunks/4045.b30465273dc5e468.js +0 -21
  366. sky/dashboard/out/_next/static/chunks/4676-9da7fdbde90b5549.js +0 -10
  367. sky/dashboard/out/_next/static/chunks/4725.10f7a9a5d3ea8208.js +0 -1
  368. sky/dashboard/out/_next/static/chunks/5339.3fda4a4010ff4e06.js +0 -51
  369. sky/dashboard/out/_next/static/chunks/6135-4b4d5e824b7f9d3c.js +0 -1
  370. sky/dashboard/out/_next/static/chunks/649.b9d7f7d10c1b8c53.js +0 -45
  371. sky/dashboard/out/_next/static/chunks/6856-dca7962af4814e1b.js +0 -1
  372. sky/dashboard/out/_next/static/chunks/6990-08b2a1cae076a943.js +0 -1
  373. sky/dashboard/out/_next/static/chunks/7325.b4bc99ce0892dcd5.js +0 -6
  374. sky/dashboard/out/_next/static/chunks/754-d0da8ab45f9509e9.js +0 -18
  375. sky/dashboard/out/_next/static/chunks/7669.1f5d9a402bf5cc42.js +0 -36
  376. sky/dashboard/out/_next/static/chunks/8969-0be3036bf86f8256.js +0 -1
  377. sky/dashboard/out/_next/static/chunks/9025.c12318fb6a1a9093.js +0 -6
  378. sky/dashboard/out/_next/static/chunks/9037-fa1737818d0a0969.js +0 -6
  379. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-1cbba24bd1bd35f8.js +0 -16
  380. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-0b4b35dc1dfe046c.js +0 -16
  381. sky/dashboard/out/_next/static/chunks/pages/clusters-469814d711d63b1b.js +0 -1
  382. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-dd64309c3fe67ed2.js +0 -11
  383. sky/dashboard/out/_next/static/chunks/pages/jobs/pools/[pool]-07349868f7905d37.js +0 -16
  384. sky/dashboard/out/_next/static/chunks/pages/jobs-1f70d9faa564804f.js +0 -1
  385. sky/dashboard/out/_next/static/chunks/pages/users-018bf31cda52e11b.js +0 -1
  386. sky/dashboard/out/_next/static/chunks/pages/volumes-739726d6b823f532.js +0 -1
  387. sky/dashboard/out/_next/static/chunks/webpack-4fe903277b57b523.js +0 -1
  388. sky/dashboard/out/_next/static/css/4614e06482d7309e.css +0 -3
  389. sky/dashboard/out/_next/static/mS-4qZPSkRuA1u-g2wQhg/_buildManifest.js +0 -1
  390. sky/templates/kubernetes-ssh-jump.yml.j2 +0 -94
  391. sky/utils/kubernetes/ssh_jump_lifecycle_manager.py +0 -191
  392. skypilot_nightly-1.0.0.dev20250905.dist-info/RECORD +0 -547
  393. skypilot_nightly-1.0.0.dev20250905.dist-info/top_level.txt +0 -1
  394. /sky/dashboard/out/_next/static/{mS-4qZPSkRuA1u-g2wQhg → 96_E2yl3QAiIJGOYCkSpB}/_ssgManifest.js +0 -0
  395. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/WHEEL +0 -0
  396. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/entry_points.txt +0 -0
  397. {skypilot_nightly-1.0.0.dev20250905.dist-info → skypilot_nightly-1.0.0.dev20251203.dist-info}/licenses/LICENSE +0 -0
@@ -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, resources: 'resources_lib.Resources'
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]:
@@ -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
- self.timestamp = ''
175
- self.signature = ''
176
-
177
- self.headers = {
178
- 'X-Cmp-AccessKey': f'{self.access_key}',
179
- 'X-Cmp-ClientType': f'{self.client_type}',
180
- 'X-Cmp-ProjectId': f'{self.project_id}',
181
- 'X-Cmp-Timestamp': f'{self.timestamp}',
182
- 'X-Cmp-Signature': f'{self.signature}'
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.set_timestamp()
194
- self.set_signature(url=url, method=method)
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.set_timestamp()
207
- self.set_signature(url=url, method=method)
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.set_timestamp()
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=self.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
- ports = ['22']
252
+ if cnt == 1:
253
+ ports = ['22']
254
+ else:
255
+ ports = ['22', '6380', '8076', '10001', '11001-11200']
259
256
  else:
260
- ports = ['21', '22', '80', '443']
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
- return self._post(url, request_body)
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, ports):
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
- ports = ['22']
305
+ if cnt == 1:
306
+ ports = ['22']
307
+ else:
308
+ ports = ['22', '6380', '8076', '10001', '11001-11200']
300
309
  else:
301
- ports = ['21', '22', '80', '443']
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
- response = requests.get(f'{API_ENDPOINT}/instance-types',
338
- headers=self.headers)
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, method: str, url: str) -> str:
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 + self.timestamp \
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)