skypilot-nightly 1.0.0.dev20250609__py3-none-any.whl → 1.0.0.dev20250611__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 (117) hide show
  1. sky/__init__.py +2 -2
  2. sky/admin_policy.py +134 -5
  3. sky/authentication.py +1 -7
  4. sky/backends/cloud_vm_ray_backend.py +9 -20
  5. sky/benchmark/benchmark_state.py +39 -1
  6. sky/cli.py +3 -5
  7. sky/client/cli.py +3 -5
  8. sky/client/sdk.py +49 -4
  9. sky/clouds/kubernetes.py +15 -24
  10. sky/dashboard/out/404.html +1 -1
  11. sky/dashboard/out/_next/static/chunks/211.692afc57e812ae1a.js +1 -0
  12. sky/dashboard/out/_next/static/chunks/350.9e123a4551f68b0d.js +1 -0
  13. sky/dashboard/out/_next/static/chunks/37-d8aebf1683522a0b.js +6 -0
  14. sky/dashboard/out/_next/static/chunks/42.d39e24467181b06b.js +6 -0
  15. sky/dashboard/out/_next/static/chunks/443.b2242d0efcdf5f47.js +1 -0
  16. sky/dashboard/out/_next/static/chunks/470-4d1a5dbe58a8a2b9.js +1 -0
  17. sky/dashboard/out/_next/static/chunks/{121-865d2bf8a3b84c6a.js → 491.b3d264269613fe09.js} +3 -3
  18. sky/dashboard/out/_next/static/chunks/513.211357a2914a34b2.js +1 -0
  19. sky/dashboard/out/_next/static/chunks/600.15a0009177e86b86.js +16 -0
  20. sky/dashboard/out/_next/static/chunks/616-d6128fa9e7cae6e6.js +39 -0
  21. sky/dashboard/out/_next/static/chunks/664-047bc03493fda379.js +1 -0
  22. sky/dashboard/out/_next/static/chunks/682.4dd5dc116f740b5f.js +6 -0
  23. sky/dashboard/out/_next/static/chunks/760-a89d354797ce7af5.js +1 -0
  24. sky/dashboard/out/_next/static/chunks/799-3625946b2ec2eb30.js +8 -0
  25. sky/dashboard/out/_next/static/chunks/804-4c9fc53aa74bc191.js +21 -0
  26. sky/dashboard/out/_next/static/chunks/843-6fcc4bf91ac45b39.js +11 -0
  27. sky/dashboard/out/_next/static/chunks/856-0776dc6ed6000c39.js +1 -0
  28. sky/dashboard/out/_next/static/chunks/901-b424d293275e1fd7.js +1 -0
  29. sky/dashboard/out/_next/static/chunks/938-ab185187a63f9cdb.js +1 -0
  30. sky/dashboard/out/_next/static/chunks/947-6620842ef80ae879.js +35 -0
  31. sky/dashboard/out/_next/static/chunks/969-20d54a9d998dc102.js +1 -0
  32. sky/dashboard/out/_next/static/chunks/973-c807fc34f09c7df3.js +1 -0
  33. sky/dashboard/out/_next/static/chunks/pages/_app-7bbd9d39d6f9a98a.js +20 -0
  34. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-89216c616dbaa9c5.js +6 -0
  35. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-451a14e7e755ebbc.js +6 -0
  36. sky/dashboard/out/_next/static/chunks/pages/clusters-e56b17fd85d0ba58.js +1 -0
  37. sky/dashboard/out/_next/static/chunks/pages/config-497a35a7ed49734a.js +1 -0
  38. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-d2910be98e9227cb.js +1 -0
  39. sky/dashboard/out/_next/static/chunks/pages/infra-780860bcc1103945.js +1 -0
  40. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-b3dbf38b51cb29be.js +16 -0
  41. sky/dashboard/out/_next/static/chunks/pages/jobs-fe233baf3d073491.js +1 -0
  42. sky/dashboard/out/_next/static/chunks/pages/users-c69ffcab9d6e5269.js +1 -0
  43. sky/dashboard/out/_next/static/chunks/pages/workspace/new-31aa8bdcb7592635.js +1 -0
  44. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-c8c2191328532b7d.js +1 -0
  45. sky/dashboard/out/_next/static/chunks/pages/workspaces-82e6601baa5dd280.js +1 -0
  46. sky/dashboard/out/_next/static/chunks/webpack-208a9812ab4f61c9.js +1 -0
  47. sky/dashboard/out/_next/static/css/{8b1c8321d4c02372.css → 5d71bfc09f184bab.css} +1 -1
  48. sky/dashboard/out/_next/static/zJqasksBQ3HcqMpA2wTUZ/_buildManifest.js +1 -0
  49. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  50. sky/dashboard/out/clusters/[cluster].html +1 -1
  51. sky/dashboard/out/clusters.html +1 -1
  52. sky/dashboard/out/config.html +1 -1
  53. sky/dashboard/out/index.html +1 -1
  54. sky/dashboard/out/infra/[context].html +1 -1
  55. sky/dashboard/out/infra.html +1 -1
  56. sky/dashboard/out/jobs/[job].html +1 -1
  57. sky/dashboard/out/jobs.html +1 -1
  58. sky/dashboard/out/users.html +1 -1
  59. sky/dashboard/out/workspace/new.html +1 -1
  60. sky/dashboard/out/workspaces/[name].html +1 -1
  61. sky/dashboard/out/workspaces.html +1 -1
  62. sky/exceptions.py +18 -0
  63. sky/global_user_state.py +181 -74
  64. sky/jobs/client/sdk.py +29 -21
  65. sky/jobs/scheduler.py +4 -5
  66. sky/jobs/state.py +104 -11
  67. sky/jobs/utils.py +5 -5
  68. sky/provision/kubernetes/constants.py +9 -0
  69. sky/provision/kubernetes/utils.py +106 -7
  70. sky/serve/client/sdk.py +56 -45
  71. sky/server/common.py +1 -5
  72. sky/server/requests/executor.py +50 -20
  73. sky/server/requests/payloads.py +3 -0
  74. sky/server/requests/process.py +69 -29
  75. sky/server/server.py +1 -0
  76. sky/server/stream_utils.py +111 -55
  77. sky/skylet/constants.py +1 -2
  78. sky/skylet/job_lib.py +95 -40
  79. sky/skypilot_config.py +99 -25
  80. sky/users/permission.py +34 -17
  81. sky/utils/admin_policy_utils.py +41 -16
  82. sky/utils/context.py +21 -1
  83. sky/utils/controller_utils.py +16 -1
  84. sky/utils/kubernetes/exec_kubeconfig_converter.py +19 -47
  85. sky/utils/schemas.py +11 -3
  86. {skypilot_nightly-1.0.0.dev20250609.dist-info → skypilot_nightly-1.0.0.dev20250611.dist-info}/METADATA +1 -1
  87. {skypilot_nightly-1.0.0.dev20250609.dist-info → skypilot_nightly-1.0.0.dev20250611.dist-info}/RECORD +92 -81
  88. sky/dashboard/out/_next/static/chunks/236-619ed0248fb6fdd9.js +0 -6
  89. sky/dashboard/out/_next/static/chunks/293-351268365226d251.js +0 -1
  90. sky/dashboard/out/_next/static/chunks/37-600191c5804dcae2.js +0 -6
  91. sky/dashboard/out/_next/static/chunks/470-680c19413b8f808b.js +0 -1
  92. sky/dashboard/out/_next/static/chunks/63-e2d7b1e75e67c713.js +0 -66
  93. sky/dashboard/out/_next/static/chunks/682-b60cfdacc15202e8.js +0 -6
  94. sky/dashboard/out/_next/static/chunks/843-16c7194621b2b512.js +0 -11
  95. sky/dashboard/out/_next/static/chunks/856-affc52adf5403a3a.js +0 -1
  96. sky/dashboard/out/_next/static/chunks/969-2c584e28e6b4b106.js +0 -1
  97. sky/dashboard/out/_next/static/chunks/973-aed916d5b02d2d63.js +0 -1
  98. sky/dashboard/out/_next/static/chunks/pages/_app-5f16aba5794ee8e7.js +0 -1
  99. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-d31688d3e52736dd.js +0 -6
  100. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-e7d8710a9b0491e5.js +0 -6
  101. sky/dashboard/out/_next/static/chunks/pages/clusters-3c674e5d970e05cb.js +0 -1
  102. sky/dashboard/out/_next/static/chunks/pages/config-3aac7a015c6eede1.js +0 -6
  103. sky/dashboard/out/_next/static/chunks/pages/infra/[context]-46d2e4ad6c487260.js +0 -1
  104. sky/dashboard/out/_next/static/chunks/pages/infra-7013d816a2a0e76c.js +0 -1
  105. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-f7f0c9e156d328bc.js +0 -16
  106. sky/dashboard/out/_next/static/chunks/pages/jobs-87e60396c376292f.js +0 -1
  107. sky/dashboard/out/_next/static/chunks/pages/users-9355a0f13d1db61d.js +0 -16
  108. sky/dashboard/out/_next/static/chunks/pages/workspace/new-9a749cca1813bd27.js +0 -1
  109. sky/dashboard/out/_next/static/chunks/pages/workspaces/[name]-8eeb628e03902f1b.js +0 -1
  110. sky/dashboard/out/_next/static/chunks/pages/workspaces-8fbcc5ab4af316d0.js +0 -1
  111. sky/dashboard/out/_next/static/chunks/webpack-65d465f948974c0d.js +0 -1
  112. sky/dashboard/out/_next/static/xos0euNCptbGAM7_Q3Acl/_buildManifest.js +0 -1
  113. /sky/dashboard/out/_next/static/{xos0euNCptbGAM7_Q3Acl → zJqasksBQ3HcqMpA2wTUZ}/_ssgManifest.js +0 -0
  114. {skypilot_nightly-1.0.0.dev20250609.dist-info → skypilot_nightly-1.0.0.dev20250611.dist-info}/WHEEL +0 -0
  115. {skypilot_nightly-1.0.0.dev20250609.dist-info → skypilot_nightly-1.0.0.dev20250611.dist-info}/entry_points.txt +0 -0
  116. {skypilot_nightly-1.0.0.dev20250609.dist-info → skypilot_nightly-1.0.0.dev20250611.dist-info}/licenses/LICENSE +0 -0
  117. {skypilot_nightly-1.0.0.dev20250609.dist-info → skypilot_nightly-1.0.0.dev20250611.dist-info}/top_level.txt +0 -0
sky/global_user_state.py CHANGED
@@ -6,11 +6,13 @@ Concepts:
6
6
  - Cluster handle: (non-user facing) an opaque backend handle for us to
7
7
  interact with a cluster.
8
8
  """
9
+ import functools
9
10
  import json
10
11
  import os
11
12
  import pathlib
12
13
  import pickle
13
14
  import re
15
+ import threading
14
16
  import time
15
17
  import typing
16
18
  from typing import Any, Dict, List, Optional, Set, Tuple
@@ -44,6 +46,9 @@ logger = sky_logging.init_logger(__name__)
44
46
 
45
47
  _ENABLED_CLOUDS_KEY_PREFIX = 'enabled_clouds_'
46
48
 
49
+ _SQLALCHEMY_ENGINE: Optional[sqlalchemy.engine.Engine] = None
50
+ _DB_INIT_LOCK = threading.Lock()
51
+
47
52
  Base = declarative.declarative_base()
48
53
 
49
54
  config_table = sqlalchemy.Table(
@@ -171,11 +176,11 @@ def create_table():
171
176
  # https://github.com/microsoft/WSL/issues/2395
172
177
  # TODO(romilb): We do not enable WAL for WSL because of known issue in WSL.
173
178
  # This may cause the database locked problem from WSL issue #1441.
174
- if (SQLALCHEMY_ENGINE.dialect.name
179
+ if (_SQLALCHEMY_ENGINE.dialect.name
175
180
  == db_utils.SQLAlchemyDialect.SQLITE.value and
176
181
  not common_utils.is_wsl()):
177
182
  try:
178
- with orm.Session(SQLALCHEMY_ENGINE) as session:
183
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
179
184
  session.execute(sqlalchemy.text('PRAGMA journal_mode=WAL'))
180
185
  session.commit()
181
186
  except sqlalchemy_exc.OperationalError as e:
@@ -185,12 +190,12 @@ def create_table():
185
190
  # is not critical and is likely to be enabled by other processes.
186
191
 
187
192
  # Create tables if they don't exist
188
- Base.metadata.create_all(bind=SQLALCHEMY_ENGINE)
193
+ Base.metadata.create_all(bind=_SQLALCHEMY_ENGINE)
189
194
 
190
195
  # For backward compatibility.
191
196
  # TODO(zhwu): Remove this function after all users have migrated to
192
197
  # the latest version of SkyPilot.
193
- with orm.Session(SQLALCHEMY_ENGINE) as session:
198
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
194
199
  # Add autostop column to clusters table
195
200
  db_utils.add_column_to_table_sqlalchemy(session,
196
201
  'clusters',
@@ -298,30 +303,53 @@ def create_table():
298
303
  session.commit()
299
304
 
300
305
 
301
- conn_string = None
302
- if os.environ.get(constants.ENV_VAR_IS_SKYPILOT_SERVER) is not None:
303
- conn_string = skypilot_config.get_nested(('db',), None)
304
- if conn_string:
305
- logger.debug(f'using db URI from {conn_string}')
306
- SQLALCHEMY_ENGINE = sqlalchemy.create_engine(conn_string)
307
- else:
308
- _DB_PATH = os.path.expanduser('~/.sky/state.db')
309
- pathlib.Path(_DB_PATH).parents[0].mkdir(parents=True, exist_ok=True)
310
- SQLALCHEMY_ENGINE = sqlalchemy.create_engine('sqlite:///' + _DB_PATH)
311
- create_table()
312
-
313
-
306
+ def initialize_and_get_db() -> sqlalchemy.engine.Engine:
307
+ global _SQLALCHEMY_ENGINE
308
+ if _SQLALCHEMY_ENGINE is not None:
309
+ return _SQLALCHEMY_ENGINE
310
+ with _DB_INIT_LOCK:
311
+ if _SQLALCHEMY_ENGINE is None:
312
+ conn_string = None
313
+ if os.environ.get(constants.ENV_VAR_IS_SKYPILOT_SERVER) is not None:
314
+ conn_string = skypilot_config.get_nested(('db',), None)
315
+ if conn_string:
316
+ logger.debug(f'using db URI from {conn_string}')
317
+ _SQLALCHEMY_ENGINE = sqlalchemy.create_engine(conn_string)
318
+ else:
319
+ db_path = os.path.expanduser('~/.sky/state.db')
320
+ pathlib.Path(db_path).parents[0].mkdir(parents=True,
321
+ exist_ok=True)
322
+ _SQLALCHEMY_ENGINE = sqlalchemy.create_engine('sqlite:///' +
323
+ db_path)
324
+ create_table()
325
+ return _SQLALCHEMY_ENGINE
326
+
327
+
328
+ def _init_db(func):
329
+ """Initialize the database."""
330
+
331
+ @functools.wraps(func)
332
+ def wrapper(*args, **kwargs):
333
+ initialize_and_get_db()
334
+ return func(*args, **kwargs)
335
+
336
+ return wrapper
337
+
338
+
339
+ @_init_db
314
340
  def add_or_update_user(user: models.User) -> bool:
315
341
  """Store the mapping from user hash to user name for display purposes.
316
342
 
317
343
  Returns:
318
344
  Boolean: whether the user is newly added
319
345
  """
346
+ assert _SQLALCHEMY_ENGINE is not None
347
+
320
348
  if user.name is None:
321
349
  return False
322
350
 
323
- with orm.Session(SQLALCHEMY_ENGINE) as session:
324
- if (SQLALCHEMY_ENGINE.dialect.name ==
351
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
352
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
325
353
  db_utils.SQLAlchemyDialect.SQLITE.value):
326
354
  # For SQLite, use INSERT OR IGNORE followed by UPDATE to detect new
327
355
  # vs existing
@@ -343,7 +371,7 @@ def add_or_update_user(user: models.User) -> bool:
343
371
  session.commit()
344
372
  return was_inserted
345
373
 
346
- elif (SQLALCHEMY_ENGINE.dialect.name ==
374
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
347
375
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
348
376
  # For PostgreSQL, use INSERT ... ON CONFLICT with RETURNING to
349
377
  # detect insert vs update
@@ -371,20 +399,25 @@ def add_or_update_user(user: models.User) -> bool:
371
399
  raise ValueError('Unsupported database dialect')
372
400
 
373
401
 
402
+ @_init_db
374
403
  def get_user(user_id: str) -> Optional[models.User]:
375
- with orm.Session(SQLALCHEMY_ENGINE) as session:
404
+ assert _SQLALCHEMY_ENGINE is not None
405
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
376
406
  row = session.query(user_table).filter_by(id=user_id).first()
377
407
  if row is None:
378
408
  return None
379
409
  return models.User(id=row.id, name=row.name)
380
410
 
381
411
 
412
+ @_init_db
382
413
  def get_all_users() -> List[models.User]:
383
- with orm.Session(SQLALCHEMY_ENGINE) as session:
414
+ assert _SQLALCHEMY_ENGINE is not None
415
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
384
416
  rows = session.query(user_table).all()
385
417
  return [models.User(id=row.id, name=row.name) for row in rows]
386
418
 
387
419
 
420
+ @_init_db
388
421
  def add_or_update_cluster(cluster_name: str,
389
422
  cluster_handle: 'backends.ResourceHandle',
390
423
  requested_resources: Optional[Set[Any]],
@@ -405,6 +438,7 @@ def add_or_update_cluster(cluster_name: str,
405
438
  config_hash: Configuration hash for the cluster.
406
439
  task_config: The config of the task being launched.
407
440
  """
441
+ assert _SQLALCHEMY_ENGINE is not None
408
442
  # FIXME: launched_at will be changed when `sky launch -c` is called.
409
443
  handle = pickle.dumps(cluster_handle)
410
444
  cluster_launched_at = int(time.time()) if is_launch else None
@@ -457,7 +491,7 @@ def add_or_update_cluster(cluster_name: str,
457
491
  'config_hash': config_hash,
458
492
  })
459
493
 
460
- with orm.Session(SQLALCHEMY_ENGINE) as session:
494
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
461
495
  # with_for_update() locks the row until commit() or rollback()
462
496
  # is called, or until the code escapes the with block.
463
497
  cluster_row = session.query(cluster_table).filter_by(
@@ -484,10 +518,10 @@ def add_or_update_cluster(cluster_name: str,
484
518
  'last_creation_command': last_use,
485
519
  })
486
520
 
487
- if (SQLALCHEMY_ENGINE.dialect.name ==
521
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
488
522
  db_utils.SQLAlchemyDialect.SQLITE.value):
489
523
  insert_func = sqlite.insert
490
- elif (SQLALCHEMY_ENGINE.dialect.name ==
524
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
491
525
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
492
526
  insert_func = postgresql.insert
493
527
  else:
@@ -562,29 +596,34 @@ def _get_user_hash_or_current_user(user_hash: Optional[str]) -> str:
562
596
  return common_utils.get_user_hash()
563
597
 
564
598
 
599
+ @_init_db
565
600
  def update_cluster_handle(cluster_name: str,
566
601
  cluster_handle: 'backends.ResourceHandle'):
602
+ assert _SQLALCHEMY_ENGINE is not None
567
603
  handle = pickle.dumps(cluster_handle)
568
- with orm.Session(SQLALCHEMY_ENGINE) as session:
604
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
569
605
  session.query(cluster_table).filter_by(name=cluster_name).update(
570
606
  {cluster_table.c.handle: handle})
571
607
  session.commit()
572
608
 
573
609
 
610
+ @_init_db
574
611
  def update_last_use(cluster_name: str):
575
612
  """Updates the last used command for the cluster."""
576
- with orm.Session(SQLALCHEMY_ENGINE) as session:
613
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
577
614
  session.query(cluster_table).filter_by(name=cluster_name).update(
578
615
  {cluster_table.c.last_use: common_utils.get_current_command()})
579
616
  session.commit()
580
617
 
581
618
 
619
+ @_init_db
582
620
  def remove_cluster(cluster_name: str, terminate: bool) -> None:
583
621
  """Removes cluster_name mapping."""
622
+ assert _SQLALCHEMY_ENGINE is not None
584
623
  cluster_hash = _get_hash_for_existing_cluster(cluster_name)
585
624
  usage_intervals = _get_cluster_usage_intervals(cluster_hash)
586
625
 
587
- with orm.Session(SQLALCHEMY_ENGINE) as session:
626
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
588
627
  # usage_intervals is not None and not empty
589
628
  if usage_intervals:
590
629
  assert cluster_hash is not None, cluster_name
@@ -614,24 +653,28 @@ def remove_cluster(cluster_name: str, terminate: bool) -> None:
614
653
  session.commit()
615
654
 
616
655
 
656
+ @_init_db
617
657
  def get_handle_from_cluster_name(
618
658
  cluster_name: str) -> Optional['backends.ResourceHandle']:
659
+ assert _SQLALCHEMY_ENGINE is not None
619
660
  assert cluster_name is not None, 'cluster_name cannot be None'
620
- with orm.Session(SQLALCHEMY_ENGINE) as session:
661
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
621
662
  row = session.query(cluster_table).filter_by(name=cluster_name).first()
622
663
  if row is None:
623
664
  return None
624
665
  return pickle.loads(row.handle)
625
666
 
626
667
 
668
+ @_init_db
627
669
  def get_glob_cluster_names(cluster_name: str) -> List[str]:
670
+ assert _SQLALCHEMY_ENGINE is not None
628
671
  assert cluster_name is not None, 'cluster_name cannot be None'
629
- with orm.Session(SQLALCHEMY_ENGINE) as session:
630
- if (SQLALCHEMY_ENGINE.dialect.name ==
672
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
673
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
631
674
  db_utils.SQLAlchemyDialect.SQLITE.value):
632
675
  rows = session.query(cluster_table).filter(
633
676
  cluster_table.c.name.op('GLOB')(cluster_name)).all()
634
- elif (SQLALCHEMY_ENGINE.dialect.name ==
677
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
635
678
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
636
679
  rows = session.query(cluster_table).filter(
637
680
  cluster_table.c.name.op('SIMILAR TO')(
@@ -641,10 +684,12 @@ def get_glob_cluster_names(cluster_name: str) -> List[str]:
641
684
  return [row.name for row in rows]
642
685
 
643
686
 
687
+ @_init_db
644
688
  def set_cluster_status(cluster_name: str,
645
689
  status: status_lib.ClusterStatus) -> None:
690
+ assert _SQLALCHEMY_ENGINE is not None
646
691
  current_time = int(time.time())
647
- with orm.Session(SQLALCHEMY_ENGINE) as session:
692
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
648
693
  count = session.query(cluster_table).filter_by(
649
694
  name=cluster_name).update({
650
695
  cluster_table.c.status: status.value,
@@ -656,9 +701,11 @@ def set_cluster_status(cluster_name: str,
656
701
  raise ValueError(f'Cluster {cluster_name} not found.')
657
702
 
658
703
 
704
+ @_init_db
659
705
  def set_cluster_autostop_value(cluster_name: str, idle_minutes: int,
660
706
  to_down: bool) -> None:
661
- with orm.Session(SQLALCHEMY_ENGINE) as session:
707
+ assert _SQLALCHEMY_ENGINE is not None
708
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
662
709
  count = session.query(cluster_table).filter_by(
663
710
  name=cluster_name).update({
664
711
  cluster_table.c.autostop: idle_minutes,
@@ -670,24 +717,30 @@ def set_cluster_autostop_value(cluster_name: str, idle_minutes: int,
670
717
  raise ValueError(f'Cluster {cluster_name} not found.')
671
718
 
672
719
 
720
+ @_init_db
673
721
  def get_cluster_launch_time(cluster_name: str) -> Optional[int]:
674
- with orm.Session(SQLALCHEMY_ENGINE) as session:
722
+ assert _SQLALCHEMY_ENGINE is not None
723
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
675
724
  row = session.query(cluster_table).filter_by(name=cluster_name).first()
676
725
  if row is None or row.launched_at is None:
677
726
  return None
678
727
  return int(row.launched_at)
679
728
 
680
729
 
730
+ @_init_db
681
731
  def get_cluster_info(cluster_name: str) -> Optional[Dict[str, Any]]:
682
- with orm.Session(SQLALCHEMY_ENGINE) as session:
732
+ assert _SQLALCHEMY_ENGINE is not None
733
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
683
734
  row = session.query(cluster_table).filter_by(name=cluster_name).first()
684
735
  if row is None or row.metadata is None:
685
736
  return None
686
737
  return json.loads(row.metadata)
687
738
 
688
739
 
740
+ @_init_db
689
741
  def set_cluster_info(cluster_name: str, metadata: Dict[str, Any]) -> None:
690
- with orm.Session(SQLALCHEMY_ENGINE) as session:
742
+ assert _SQLALCHEMY_ENGINE is not None
743
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
691
744
  count = session.query(cluster_table).filter_by(
692
745
  name=cluster_name).update(
693
746
  {cluster_table.c.metadata: json.dumps(metadata)})
@@ -697,18 +750,22 @@ def set_cluster_info(cluster_name: str, metadata: Dict[str, Any]) -> None:
697
750
  raise ValueError(f'Cluster {cluster_name} not found.')
698
751
 
699
752
 
753
+ @_init_db
700
754
  def get_cluster_storage_mounts_metadata(
701
755
  cluster_name: str) -> Optional[Dict[str, Any]]:
702
- with orm.Session(SQLALCHEMY_ENGINE) as session:
756
+ assert _SQLALCHEMY_ENGINE is not None
757
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
703
758
  row = session.query(cluster_table).filter_by(name=cluster_name).first()
704
759
  if row is None or row.storage_mounts_metadata is None:
705
760
  return None
706
761
  return pickle.loads(row.storage_mounts_metadata)
707
762
 
708
763
 
764
+ @_init_db
709
765
  def set_cluster_storage_mounts_metadata(
710
766
  cluster_name: str, storage_mounts_metadata: Dict[str, Any]) -> None:
711
- with orm.Session(SQLALCHEMY_ENGINE) as session:
767
+ assert _SQLALCHEMY_ENGINE is not None
768
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
712
769
  count = session.query(cluster_table).filter_by(
713
770
  name=cluster_name).update({
714
771
  cluster_table.c.storage_mounts_metadata:
@@ -720,12 +777,14 @@ def set_cluster_storage_mounts_metadata(
720
777
  raise ValueError(f'Cluster {cluster_name} not found.')
721
778
 
722
779
 
780
+ @_init_db
723
781
  def _get_cluster_usage_intervals(
724
782
  cluster_hash: Optional[str]
725
783
  ) -> Optional[List[Tuple[int, Optional[int]]]]:
784
+ assert _SQLALCHEMY_ENGINE is not None
726
785
  if cluster_hash is None:
727
786
  return None
728
- with orm.Session(SQLALCHEMY_ENGINE) as session:
787
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
729
788
  row = session.query(cluster_history_table).filter_by(
730
789
  cluster_hash=cluster_hash).first()
731
790
  if row is None or row.usage_intervals is None:
@@ -759,10 +818,12 @@ def _get_cluster_duration(cluster_hash: str) -> int:
759
818
  return total_duration
760
819
 
761
820
 
821
+ @_init_db
762
822
  def _set_cluster_usage_intervals(
763
823
  cluster_hash: str, usage_intervals: List[Tuple[int,
764
824
  Optional[int]]]) -> None:
765
- with orm.Session(SQLALCHEMY_ENGINE) as session:
825
+ assert _SQLALCHEMY_ENGINE is not None
826
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
766
827
  count = session.query(cluster_history_table).filter_by(
767
828
  cluster_hash=cluster_hash).update({
768
829
  cluster_history_table.c.usage_intervals:
@@ -774,12 +835,14 @@ def _set_cluster_usage_intervals(
774
835
  raise ValueError(f'Cluster hash {cluster_hash} not found.')
775
836
 
776
837
 
838
+ @_init_db
777
839
  def set_owner_identity_for_cluster(cluster_name: str,
778
840
  owner_identity: Optional[List[str]]) -> None:
841
+ assert _SQLALCHEMY_ENGINE is not None
779
842
  if owner_identity is None:
780
843
  return
781
844
  owner_identity_str = json.dumps(owner_identity)
782
- with orm.Session(SQLALCHEMY_ENGINE) as session:
845
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
783
846
  count = session.query(cluster_table).filter_by(
784
847
  name=cluster_name).update(
785
848
  {cluster_table.c.owner: owner_identity_str})
@@ -789,17 +852,21 @@ def set_owner_identity_for_cluster(cluster_name: str,
789
852
  raise ValueError(f'Cluster {cluster_name} not found.')
790
853
 
791
854
 
855
+ @_init_db
792
856
  def _get_hash_for_existing_cluster(cluster_name: str) -> Optional[str]:
793
- with orm.Session(SQLALCHEMY_ENGINE) as session:
857
+ assert _SQLALCHEMY_ENGINE is not None
858
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
794
859
  row = session.query(cluster_table).filter_by(name=cluster_name).first()
795
860
  if row is None or row.cluster_hash is None:
796
861
  return None
797
862
  return row.cluster_hash
798
863
 
799
864
 
865
+ @_init_db
800
866
  def get_launched_resources_from_cluster_hash(
801
867
  cluster_hash: str) -> Optional[Tuple[int, Any]]:
802
- with orm.Session(SQLALCHEMY_ENGINE) as session:
868
+ assert _SQLALCHEMY_ENGINE is not None
869
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
803
870
  row = session.query(cluster_history_table).filter_by(
804
871
  cluster_hash=cluster_hash).first()
805
872
  if row is None:
@@ -841,10 +908,12 @@ def _load_storage_mounts_metadata(
841
908
  return pickle.loads(record_storage_mounts_metadata)
842
909
 
843
910
 
911
+ @_init_db
844
912
  @context_utils.cancellation_guard
845
913
  def get_cluster_from_name(
846
914
  cluster_name: Optional[str]) -> Optional[Dict[str, Any]]:
847
- with orm.Session(SQLALCHEMY_ENGINE) as session:
915
+ assert _SQLALCHEMY_ENGINE is not None
916
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
848
917
  row = session.query(cluster_table).filter_by(name=cluster_name).first()
849
918
  if row is None:
850
919
  return None
@@ -878,8 +947,10 @@ def get_cluster_from_name(
878
947
  return record
879
948
 
880
949
 
950
+ @_init_db
881
951
  def get_clusters() -> List[Dict[str, Any]]:
882
- with orm.Session(SQLALCHEMY_ENGINE) as session:
952
+ assert _SQLALCHEMY_ENGINE is not None
953
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
883
954
  rows = session.query(cluster_table).order_by(
884
955
  sqlalchemy.desc(cluster_table.c.launched_at)).all()
885
956
  records = []
@@ -915,8 +986,10 @@ def get_clusters() -> List[Dict[str, Any]]:
915
986
  return records
916
987
 
917
988
 
989
+ @_init_db
918
990
  def get_clusters_from_history() -> List[Dict[str, Any]]:
919
- with orm.Session(SQLALCHEMY_ENGINE) as session:
991
+ assert _SQLALCHEMY_ENGINE is not None
992
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
920
993
  rows = session.query(
921
994
  cluster_history_table.join(cluster_table,
922
995
  cluster_history_table.c.cluster_hash ==
@@ -951,16 +1024,20 @@ def get_clusters_from_history() -> List[Dict[str, Any]]:
951
1024
  return records
952
1025
 
953
1026
 
1027
+ @_init_db
954
1028
  def get_cluster_names_start_with(starts_with: str) -> List[str]:
955
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1029
+ assert _SQLALCHEMY_ENGINE is not None
1030
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
956
1031
  rows = session.query(cluster_table).filter(
957
1032
  cluster_table.c.name.like(f'{starts_with}%')).all()
958
1033
  return [row.name for row in rows]
959
1034
 
960
1035
 
1036
+ @_init_db
961
1037
  def get_cached_enabled_clouds(cloud_capability: 'cloud.CloudCapability',
962
1038
  workspace: str) -> List['clouds.Cloud']:
963
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1039
+ assert _SQLALCHEMY_ENGINE is not None
1040
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
964
1041
  row = session.query(config_table).filter_by(
965
1042
  key=_get_enabled_clouds_key(cloud_capability, workspace)).first()
966
1043
  ret = []
@@ -981,14 +1058,16 @@ def get_cached_enabled_clouds(cloud_capability: 'cloud.CloudCapability',
981
1058
  return enabled_clouds
982
1059
 
983
1060
 
1061
+ @_init_db
984
1062
  def set_enabled_clouds(enabled_clouds: List[str],
985
1063
  cloud_capability: 'cloud.CloudCapability',
986
1064
  workspace: str) -> None:
987
- with orm.Session(SQLALCHEMY_ENGINE) as session:
988
- if (SQLALCHEMY_ENGINE.dialect.name ==
1065
+ assert _SQLALCHEMY_ENGINE is not None
1066
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1067
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
989
1068
  db_utils.SQLAlchemyDialect.SQLITE.value):
990
1069
  insert_func = sqlite.insert
991
- elif (SQLALCHEMY_ENGINE.dialect.name ==
1070
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
992
1071
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
993
1072
  insert_func = postgresql.insert
994
1073
  else:
@@ -1008,9 +1087,11 @@ def _get_enabled_clouds_key(cloud_capability: 'cloud.CloudCapability',
1008
1087
  return _ENABLED_CLOUDS_KEY_PREFIX + workspace + '_' + cloud_capability.value
1009
1088
 
1010
1089
 
1090
+ @_init_db
1011
1091
  def add_or_update_storage(storage_name: str,
1012
1092
  storage_handle: 'Storage.StorageMetadata',
1013
1093
  storage_status: status_lib.StorageStatus):
1094
+ assert _SQLALCHEMY_ENGINE is not None
1014
1095
  storage_launched_at = int(time.time())
1015
1096
  handle = pickle.dumps(storage_handle)
1016
1097
  last_use = common_utils.get_current_command()
@@ -1021,11 +1102,11 @@ def add_or_update_storage(storage_name: str,
1021
1102
  if not status_check(storage_status):
1022
1103
  raise ValueError(f'Error in updating global state. Storage Status '
1023
1104
  f'{storage_status} is passed in incorrectly')
1024
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1025
- if (SQLALCHEMY_ENGINE.dialect.name ==
1105
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1106
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1026
1107
  db_utils.SQLAlchemyDialect.SQLITE.value):
1027
1108
  insert_func = sqlite.insert
1028
- elif (SQLALCHEMY_ENGINE.dialect.name ==
1109
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1029
1110
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1030
1111
  insert_func = postgresql.insert
1031
1112
  else:
@@ -1048,16 +1129,20 @@ def add_or_update_storage(storage_name: str,
1048
1129
  session.commit()
1049
1130
 
1050
1131
 
1132
+ @_init_db
1051
1133
  def remove_storage(storage_name: str):
1052
1134
  """Removes Storage from Database"""
1053
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1135
+ assert _SQLALCHEMY_ENGINE is not None
1136
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1054
1137
  session.query(storage_table).filter_by(name=storage_name).delete()
1055
1138
  session.commit()
1056
1139
 
1057
1140
 
1141
+ @_init_db
1058
1142
  def set_storage_status(storage_name: str,
1059
1143
  status: status_lib.StorageStatus) -> None:
1060
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1144
+ assert _SQLALCHEMY_ENGINE is not None
1145
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1061
1146
  count = session.query(storage_table).filter_by(
1062
1147
  name=storage_name).update({storage_table.c.status: status.value})
1063
1148
  session.commit()
@@ -1066,18 +1151,22 @@ def set_storage_status(storage_name: str,
1066
1151
  raise ValueError(f'Storage {storage_name} not found.')
1067
1152
 
1068
1153
 
1154
+ @_init_db
1069
1155
  def get_storage_status(storage_name: str) -> Optional[status_lib.StorageStatus]:
1156
+ assert _SQLALCHEMY_ENGINE is not None
1070
1157
  assert storage_name is not None, 'storage_name cannot be None'
1071
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1158
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1072
1159
  row = session.query(storage_table).filter_by(name=storage_name).first()
1073
1160
  if row:
1074
1161
  return status_lib.StorageStatus[row.status]
1075
1162
  return None
1076
1163
 
1077
1164
 
1165
+ @_init_db
1078
1166
  def set_storage_handle(storage_name: str,
1079
1167
  handle: 'Storage.StorageMetadata') -> None:
1080
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1168
+ assert _SQLALCHEMY_ENGINE is not None
1169
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1081
1170
  count = session.query(storage_table).filter_by(
1082
1171
  name=storage_name).update(
1083
1172
  {storage_table.c.handle: pickle.dumps(handle)})
@@ -1087,25 +1176,29 @@ def set_storage_handle(storage_name: str,
1087
1176
  raise ValueError(f'Storage{storage_name} not found.')
1088
1177
 
1089
1178
 
1179
+ @_init_db
1090
1180
  def get_handle_from_storage_name(
1091
1181
  storage_name: Optional[str]) -> Optional['Storage.StorageMetadata']:
1182
+ assert _SQLALCHEMY_ENGINE is not None
1092
1183
  if storage_name is None:
1093
1184
  return None
1094
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1185
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1095
1186
  row = session.query(storage_table).filter_by(name=storage_name).first()
1096
1187
  if row:
1097
1188
  return pickle.loads(row.handle)
1098
1189
  return None
1099
1190
 
1100
1191
 
1192
+ @_init_db
1101
1193
  def get_glob_storage_name(storage_name: str) -> List[str]:
1194
+ assert _SQLALCHEMY_ENGINE is not None
1102
1195
  assert storage_name is not None, 'storage_name cannot be None'
1103
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1104
- if (SQLALCHEMY_ENGINE.dialect.name ==
1196
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1197
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1105
1198
  db_utils.SQLAlchemyDialect.SQLITE.value):
1106
1199
  rows = session.query(storage_table).filter(
1107
1200
  storage_table.c.name.op('GLOB')(storage_name)).all()
1108
- elif (SQLALCHEMY_ENGINE.dialect.name ==
1201
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1109
1202
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1110
1203
  rows = session.query(storage_table).filter(
1111
1204
  storage_table.c.name.op('SIMILAR TO')(
@@ -1115,15 +1208,19 @@ def get_glob_storage_name(storage_name: str) -> List[str]:
1115
1208
  return [row.name for row in rows]
1116
1209
 
1117
1210
 
1211
+ @_init_db
1118
1212
  def get_storage_names_start_with(starts_with: str) -> List[str]:
1119
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1213
+ assert _SQLALCHEMY_ENGINE is not None
1214
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1120
1215
  rows = session.query(storage_table).filter(
1121
1216
  storage_table.c.name.like(f'{starts_with}%')).all()
1122
1217
  return [row.name for row in rows]
1123
1218
 
1124
1219
 
1220
+ @_init_db
1125
1221
  def get_storage() -> List[Dict[str, Any]]:
1126
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1222
+ assert _SQLALCHEMY_ENGINE is not None
1223
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1127
1224
  rows = session.query(storage_table).all()
1128
1225
  records = []
1129
1226
  for row in rows:
@@ -1138,8 +1235,10 @@ def get_storage() -> List[Dict[str, Any]]:
1138
1235
  return records
1139
1236
 
1140
1237
 
1238
+ @_init_db
1141
1239
  def get_ssh_keys(user_hash: str) -> Tuple[str, str, bool]:
1142
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1240
+ assert _SQLALCHEMY_ENGINE is not None
1241
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1143
1242
  row = session.query(ssh_key_table).filter_by(
1144
1243
  user_hash=user_hash).first()
1145
1244
  if row:
@@ -1147,12 +1246,14 @@ def get_ssh_keys(user_hash: str) -> Tuple[str, str, bool]:
1147
1246
  return '', '', False
1148
1247
 
1149
1248
 
1249
+ @_init_db
1150
1250
  def set_ssh_keys(user_hash: str, ssh_public_key: str, ssh_private_key: str):
1151
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1152
- if (SQLALCHEMY_ENGINE.dialect.name ==
1251
+ assert _SQLALCHEMY_ENGINE is not None
1252
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1253
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1153
1254
  db_utils.SQLAlchemyDialect.SQLITE.value):
1154
1255
  insert_func = sqlite.insert
1155
- elif (SQLALCHEMY_ENGINE.dialect.name ==
1256
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1156
1257
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1157
1258
  insert_func = postgresql.insert
1158
1259
  else:
@@ -1171,6 +1272,7 @@ def set_ssh_keys(user_hash: str, ssh_public_key: str, ssh_private_key: str):
1171
1272
  session.commit()
1172
1273
 
1173
1274
 
1275
+ @_init_db
1174
1276
  def get_cluster_yaml_str(cluster_yaml_path: Optional[str]) -> Optional[str]:
1175
1277
  """Get the cluster yaml from the database or the local file system.
1176
1278
  If the cluster yaml is not in the database, check if it exists on the
@@ -1178,11 +1280,12 @@ def get_cluster_yaml_str(cluster_yaml_path: Optional[str]) -> Optional[str]:
1178
1280
 
1179
1281
  It is assumed that the cluster yaml file is named as <cluster_name>.yml.
1180
1282
  """
1283
+ assert _SQLALCHEMY_ENGINE is not None
1181
1284
  if cluster_yaml_path is None:
1182
1285
  raise ValueError('Attempted to read a None YAML.')
1183
1286
  cluster_file_name = os.path.basename(cluster_yaml_path)
1184
1287
  cluster_name, _ = os.path.splitext(cluster_file_name)
1185
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1288
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1186
1289
  row = session.query(cluster_yaml_table).filter_by(
1187
1290
  cluster_name=cluster_name).first()
1188
1291
  if row is None:
@@ -1210,13 +1313,15 @@ def get_cluster_yaml_dict(cluster_yaml_path: Optional[str]) -> Dict[str, Any]:
1210
1313
  return yaml.safe_load(yaml_str)
1211
1314
 
1212
1315
 
1316
+ @_init_db
1213
1317
  def set_cluster_yaml(cluster_name: str, yaml_str: str) -> None:
1214
1318
  """Set the cluster yaml in the database."""
1215
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1216
- if (SQLALCHEMY_ENGINE.dialect.name ==
1319
+ assert _SQLALCHEMY_ENGINE is not None
1320
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1321
+ if (_SQLALCHEMY_ENGINE.dialect.name ==
1217
1322
  db_utils.SQLAlchemyDialect.SQLITE.value):
1218
1323
  insert_func = sqlite.insert
1219
- elif (SQLALCHEMY_ENGINE.dialect.name ==
1324
+ elif (_SQLALCHEMY_ENGINE.dialect.name ==
1220
1325
  db_utils.SQLAlchemyDialect.POSTGRESQL.value):
1221
1326
  insert_func = postgresql.insert
1222
1327
  else:
@@ -1230,8 +1335,10 @@ def set_cluster_yaml(cluster_name: str, yaml_str: str) -> None:
1230
1335
  session.commit()
1231
1336
 
1232
1337
 
1338
+ @_init_db
1233
1339
  def remove_cluster_yaml(cluster_name: str):
1234
- with orm.Session(SQLALCHEMY_ENGINE) as session:
1340
+ assert _SQLALCHEMY_ENGINE is not None
1341
+ with orm.Session(_SQLALCHEMY_ENGINE) as session:
1235
1342
  session.query(cluster_yaml_table).filter_by(
1236
1343
  cluster_name=cluster_name).delete()
1237
1344
  session.commit()