kaqing 2.0.98__py3-none-any.whl → 2.0.203__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 (254) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -12
  3. adam/apps.py +18 -4
  4. adam/batch.py +11 -25
  5. adam/checks/check_utils.py +16 -46
  6. adam/checks/cpu.py +7 -1
  7. adam/checks/cpu_metrics.py +52 -0
  8. adam/checks/disk.py +2 -3
  9. adam/columns/columns.py +3 -1
  10. adam/columns/cpu.py +3 -1
  11. adam/columns/cpu_metrics.py +22 -0
  12. adam/columns/memory.py +3 -4
  13. adam/commands/__init__.py +24 -0
  14. adam/commands/alter_tables.py +37 -63
  15. adam/commands/app/app.py +38 -0
  16. adam/commands/{app_ping.py → app/app_ping.py} +8 -14
  17. adam/commands/app/show_app_actions.py +49 -0
  18. adam/commands/{show → app}/show_app_id.py +8 -11
  19. adam/commands/{show → app}/show_app_queues.py +8 -14
  20. adam/commands/app/utils_app.py +106 -0
  21. adam/commands/audit/audit.py +31 -35
  22. adam/commands/audit/audit_repair_tables.py +26 -48
  23. adam/commands/audit/audit_run.py +50 -0
  24. adam/commands/audit/completions_l.py +15 -0
  25. adam/commands/audit/show_last10.py +36 -0
  26. adam/commands/audit/show_slow10.py +36 -0
  27. adam/commands/audit/show_top10.py +36 -0
  28. adam/commands/audit/utils_show_top10.py +71 -0
  29. adam/commands/bash/__init__.py +5 -0
  30. adam/commands/bash/bash.py +36 -0
  31. adam/commands/bash/bash_completer.py +93 -0
  32. adam/commands/bash/utils_bash.py +16 -0
  33. adam/commands/cassandra/__init__.py +0 -0
  34. adam/commands/cassandra/download_cassandra_log.py +45 -0
  35. adam/commands/cassandra/nodetool.py +64 -0
  36. adam/commands/cassandra/nodetool_commands.py +120 -0
  37. adam/commands/{restart.py → cassandra/restart_cluster.py} +12 -26
  38. adam/commands/cassandra/restart_node.py +51 -0
  39. adam/commands/cassandra/restart_nodes.py +47 -0
  40. adam/commands/cassandra/rollout.py +88 -0
  41. adam/commands/cat.py +36 -0
  42. adam/commands/cd.py +14 -92
  43. adam/commands/check.py +18 -21
  44. adam/commands/cli_commands.py +8 -4
  45. adam/commands/clipboard_copy.py +87 -0
  46. adam/commands/code.py +57 -0
  47. adam/commands/command.py +212 -39
  48. adam/commands/commands_utils.py +20 -28
  49. adam/commands/cql/alter_tables.py +66 -0
  50. adam/commands/cql/completions_c.py +29 -0
  51. adam/commands/cql/cqlsh.py +10 -29
  52. adam/commands/cql/utils_cql.py +305 -0
  53. adam/commands/debug/__init__.py +0 -0
  54. adam/commands/debug/debug.py +22 -0
  55. adam/commands/debug/debug_completes.py +35 -0
  56. adam/commands/debug/debug_timings.py +35 -0
  57. adam/commands/deploy/code_start.py +7 -10
  58. adam/commands/deploy/code_stop.py +4 -21
  59. adam/commands/deploy/code_utils.py +3 -3
  60. adam/commands/deploy/deploy.py +4 -21
  61. adam/commands/deploy/deploy_frontend.py +14 -17
  62. adam/commands/deploy/deploy_pg_agent.py +3 -6
  63. adam/commands/deploy/deploy_pod.py +65 -73
  64. adam/commands/deploy/deploy_utils.py +14 -24
  65. adam/commands/deploy/undeploy.py +4 -21
  66. adam/commands/deploy/undeploy_frontend.py +4 -7
  67. adam/commands/deploy/undeploy_pg_agent.py +6 -8
  68. adam/commands/deploy/undeploy_pod.py +11 -12
  69. adam/commands/devices/__init__.py +0 -0
  70. adam/commands/devices/device.py +149 -0
  71. adam/commands/devices/device_app.py +163 -0
  72. adam/commands/devices/device_auit_log.py +49 -0
  73. adam/commands/devices/device_cass.py +179 -0
  74. adam/commands/devices/device_export.py +87 -0
  75. adam/commands/devices/device_postgres.py +160 -0
  76. adam/commands/devices/devices.py +25 -0
  77. adam/commands/download_cassandra_log.py +45 -0
  78. adam/commands/download_file.py +47 -0
  79. adam/commands/exit.py +1 -4
  80. adam/commands/export/__init__.py +0 -0
  81. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  82. adam/commands/export/clean_up_export_sessions.py +39 -0
  83. adam/commands/export/completions_x.py +11 -0
  84. adam/commands/export/download_export_session.py +40 -0
  85. adam/commands/export/drop_export_database.py +39 -0
  86. adam/commands/export/drop_export_databases.py +37 -0
  87. adam/commands/export/export.py +37 -0
  88. adam/commands/export/export_databases.py +247 -0
  89. adam/commands/export/export_select.py +34 -0
  90. adam/commands/export/export_sessions.py +211 -0
  91. adam/commands/export/export_use.py +49 -0
  92. adam/commands/export/export_x_select.py +48 -0
  93. adam/commands/export/exporter.py +361 -0
  94. adam/commands/export/import_files.py +44 -0
  95. adam/commands/export/import_session.py +44 -0
  96. adam/commands/export/importer.py +82 -0
  97. adam/commands/export/importer_athena.py +150 -0
  98. adam/commands/export/importer_sqlite.py +69 -0
  99. adam/commands/export/show_column_counts.py +45 -0
  100. adam/commands/export/show_export_databases.py +39 -0
  101. adam/commands/export/show_export_session.py +39 -0
  102. adam/commands/export/show_export_sessions.py +37 -0
  103. adam/commands/export/utils_export.py +366 -0
  104. adam/commands/find_files.py +51 -0
  105. adam/commands/find_processes.py +76 -0
  106. adam/commands/generate_report.py +52 -0
  107. adam/commands/head.py +36 -0
  108. adam/commands/help.py +12 -8
  109. adam/commands/intermediate_command.py +52 -0
  110. adam/commands/issues.py +14 -40
  111. adam/commands/kubectl.py +38 -0
  112. adam/commands/login.py +26 -25
  113. adam/commands/ls.py +11 -116
  114. adam/commands/medusa/medusa.py +4 -22
  115. adam/commands/medusa/medusa_backup.py +20 -27
  116. adam/commands/medusa/medusa_restore.py +35 -48
  117. adam/commands/medusa/medusa_show_backupjobs.py +17 -18
  118. adam/commands/medusa/medusa_show_restorejobs.py +13 -18
  119. adam/commands/medusa/utils_medusa.py +15 -0
  120. adam/commands/nodetool.py +8 -19
  121. adam/commands/os/__init__.py +0 -0
  122. adam/commands/os/cat.py +36 -0
  123. adam/commands/os/download_file.py +47 -0
  124. adam/commands/os/find_files.py +51 -0
  125. adam/commands/os/find_processes.py +76 -0
  126. adam/commands/os/head.py +36 -0
  127. adam/commands/os/shell.py +41 -0
  128. adam/commands/param_get.py +11 -14
  129. adam/commands/param_set.py +8 -12
  130. adam/commands/postgres/completions_p.py +22 -0
  131. adam/commands/postgres/postgres.py +47 -55
  132. adam/commands/postgres/postgres_databases.py +269 -0
  133. adam/commands/postgres/postgres_ls.py +4 -8
  134. adam/commands/postgres/postgres_preview.py +5 -9
  135. adam/commands/postgres/utils_postgres.py +79 -0
  136. adam/commands/preview_table.py +10 -61
  137. adam/commands/pwd.py +14 -46
  138. adam/commands/reaper/reaper.py +4 -24
  139. adam/commands/reaper/reaper_forward.py +49 -56
  140. adam/commands/reaper/reaper_forward_session.py +6 -0
  141. adam/commands/reaper/reaper_forward_stop.py +10 -16
  142. adam/commands/reaper/reaper_restart.py +7 -14
  143. adam/commands/reaper/reaper_run_abort.py +8 -33
  144. adam/commands/reaper/reaper_runs.py +43 -58
  145. adam/commands/reaper/reaper_runs_abort.py +29 -49
  146. adam/commands/reaper/reaper_schedule_activate.py +14 -33
  147. adam/commands/reaper/reaper_schedule_start.py +9 -33
  148. adam/commands/reaper/reaper_schedule_stop.py +9 -33
  149. adam/commands/reaper/reaper_schedules.py +4 -14
  150. adam/commands/reaper/reaper_status.py +8 -16
  151. adam/commands/reaper/utils_reaper.py +203 -0
  152. adam/commands/repair/repair.py +4 -22
  153. adam/commands/repair/repair_log.py +5 -11
  154. adam/commands/repair/repair_run.py +27 -34
  155. adam/commands/repair/repair_scan.py +32 -40
  156. adam/commands/repair/repair_stop.py +5 -12
  157. adam/commands/restart_cluster.py +47 -0
  158. adam/commands/restart_node.py +51 -0
  159. adam/commands/restart_nodes.py +47 -0
  160. adam/commands/rollout.py +19 -24
  161. adam/commands/shell.py +12 -4
  162. adam/commands/show/show.py +10 -23
  163. adam/commands/show/show_adam.py +3 -3
  164. adam/commands/show/show_cassandra_repairs.py +37 -0
  165. adam/commands/show/show_cassandra_status.py +47 -51
  166. adam/commands/show/show_cassandra_version.py +5 -18
  167. adam/commands/show/show_cli_commands.py +56 -0
  168. adam/commands/show/show_host.py +1 -1
  169. adam/commands/show/show_login.py +23 -27
  170. adam/commands/show/show_params.py +2 -5
  171. adam/commands/show/show_processes.py +18 -21
  172. adam/commands/show/show_storage.py +11 -20
  173. adam/commands/watch.py +26 -29
  174. adam/config.py +5 -15
  175. adam/embedded_params.py +1 -1
  176. adam/log.py +4 -4
  177. adam/repl.py +105 -133
  178. adam/repl_commands.py +68 -28
  179. adam/repl_session.py +9 -1
  180. adam/repl_state.py +300 -62
  181. adam/sql/async_executor.py +44 -0
  182. adam/sql/lark_completer.py +286 -0
  183. adam/sql/lark_parser.py +604 -0
  184. adam/sql/qingl.lark +1076 -0
  185. adam/sql/sql_completer.py +104 -64
  186. adam/sql/sql_state_machine.py +630 -0
  187. adam/sql/term_completer.py +3 -0
  188. adam/sso/authn_ad.py +6 -8
  189. adam/sso/authn_okta.py +4 -6
  190. adam/sso/cred_cache.py +3 -5
  191. adam/sso/idp.py +9 -12
  192. adam/utils.py +640 -10
  193. adam/utils_athena.py +140 -87
  194. adam/utils_audits.py +102 -0
  195. adam/utils_issues.py +32 -0
  196. adam/utils_k8s/app_clusters.py +28 -0
  197. adam/utils_k8s/app_pods.py +35 -0
  198. adam/utils_k8s/cassandra_clusters.py +34 -21
  199. adam/utils_k8s/cassandra_nodes.py +9 -6
  200. adam/utils_k8s/custom_resources.py +16 -17
  201. adam/utils_k8s/ingresses.py +2 -2
  202. adam/utils_k8s/jobs.py +7 -11
  203. adam/utils_k8s/k8s.py +96 -0
  204. adam/utils_k8s/kube_context.py +3 -6
  205. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +11 -5
  206. adam/utils_k8s/pods.py +146 -75
  207. adam/utils_k8s/secrets.py +4 -4
  208. adam/utils_k8s/service_accounts.py +5 -4
  209. adam/utils_k8s/services.py +2 -2
  210. adam/utils_k8s/statefulsets.py +6 -14
  211. adam/utils_local.py +42 -0
  212. adam/utils_net.py +4 -4
  213. adam/utils_repl/__init__.py +0 -0
  214. adam/utils_repl/appendable_completer.py +6 -0
  215. adam/utils_repl/automata_completer.py +48 -0
  216. adam/utils_repl/repl_completer.py +89 -0
  217. adam/utils_repl/state_machine.py +173 -0
  218. adam/utils_sqlite.py +137 -0
  219. adam/version.py +1 -1
  220. {kaqing-2.0.98.dist-info → kaqing-2.0.203.dist-info}/METADATA +1 -1
  221. kaqing-2.0.203.dist-info/RECORD +277 -0
  222. kaqing-2.0.203.dist-info/top_level.txt +2 -0
  223. teddy/__init__.py +0 -0
  224. teddy/lark_parser.py +436 -0
  225. teddy/lark_parser2.py +618 -0
  226. adam/commands/app.py +0 -67
  227. adam/commands/bash.py +0 -92
  228. adam/commands/cp.py +0 -95
  229. adam/commands/cql/cql_completions.py +0 -11
  230. adam/commands/cql/cql_table_completer.py +0 -8
  231. adam/commands/cql/cql_utils.py +0 -115
  232. adam/commands/describe/describe.py +0 -47
  233. adam/commands/describe/describe_keyspace.py +0 -60
  234. adam/commands/describe/describe_keyspaces.py +0 -49
  235. adam/commands/describe/describe_schema.py +0 -49
  236. adam/commands/describe/describe_table.py +0 -60
  237. adam/commands/describe/describe_tables.py +0 -49
  238. adam/commands/devices.py +0 -118
  239. adam/commands/logs.py +0 -39
  240. adam/commands/postgres/postgres_session.py +0 -240
  241. adam/commands/postgres/postgres_utils.py +0 -31
  242. adam/commands/postgres/psql_completions.py +0 -10
  243. adam/commands/postgres/psql_table_completer.py +0 -11
  244. adam/commands/reaper/reaper_session.py +0 -159
  245. adam/commands/report.py +0 -57
  246. adam/commands/show/show_app_actions.py +0 -53
  247. adam/commands/show/show_commands.py +0 -61
  248. adam/commands/show/show_repairs.py +0 -47
  249. adam/sql/state_machine.py +0 -460
  250. kaqing-2.0.98.dist-info/RECORD +0 -191
  251. kaqing-2.0.98.dist-info/top_level.txt +0 -1
  252. /adam/commands/{describe → app}/__init__.py +0 -0
  253. {kaqing-2.0.98.dist-info → kaqing-2.0.203.dist-info}/WHEEL +0 -0
  254. {kaqing-2.0.98.dist-info → kaqing-2.0.203.dist-info}/entry_points.txt +0 -0
adam/repl_state.py CHANGED
@@ -1,8 +1,10 @@
1
- import copy
1
+ from copy import copy
2
2
  from enum import Enum
3
3
  import re
4
+ from typing import Callable
4
5
 
5
- from adam.commands.postgres.postgres_session import PostgresSession
6
+ from adam.utils_k8s.app_clusters import AppClusters
7
+ from adam.utils_k8s.app_pods import AppPods
6
8
  from adam.utils_k8s.cassandra_clusters import CassandraClusters
7
9
  from adam.utils_k8s.cassandra_nodes import CassandraNodes
8
10
  from adam.utils_k8s.kube_context import KubeContext
@@ -17,25 +19,8 @@ class BashSession:
17
19
  def pwd(self, state: 'ReplState'):
18
20
  command = f'cat /tmp/.qing-{self.session_id}'
19
21
 
20
- if state.pod:
21
- rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=False)]
22
- elif state.sts:
23
- rs = CassandraClusters.exec(state.sts, state.namespace, command, action='bash', show_out=False)
24
-
25
- dir = None
26
- for r in rs:
27
- if r.exit_code(): # if fails to read the session file, ignore
28
- continue
29
-
30
- dir0 = r.stdout.strip(' \r\n')
31
- if dir:
32
- if dir != dir0:
33
- log2('Inconsitent working dir found across multiple pods.')
34
- return None
35
- else:
36
- dir = dir0
37
-
38
- return dir
22
+ with device(state) as pods:
23
+ return pods.exec(command, action='bash', show_out=False)
39
24
 
40
25
  class RequiredState(Enum):
41
26
  CLUSTER = 'cluster'
@@ -44,17 +29,22 @@ class RequiredState(Enum):
44
29
  NAMESPACE = 'namespace'
45
30
  PG_DATABASE = 'pg_database'
46
31
  APP_APP = 'app_app'
32
+ EXPORT_DB = 'export_db'
47
33
 
48
34
  class ReplState:
49
35
  A = 'a'
50
36
  C = 'c'
51
37
  L = 'l'
52
38
  P = 'p'
39
+ X = 'x'
40
+
41
+ ANY = [A, C, L, P, X]
42
+ NON_L = [A, C, P, X]
53
43
 
54
44
  def __init__(self, device: str = None,
55
45
  sts: str = None, pod: str = None, namespace: str = None, ns_sts: str = None,
56
46
  pg_path: str = None,
57
- app_env: str = None, app_app: str = None,
47
+ app_env: str = None, app_app: str = None, app_pod: str = None,
58
48
  in_repl = False, bash_session: BashSession = None, remote_dir = None):
59
49
  self.namespace = KubeContext.in_cluster_namespace()
60
50
 
@@ -64,12 +54,16 @@ class ReplState:
64
54
  self.pg_path = pg_path
65
55
  self.app_env = app_env
66
56
  self.app_app = app_app
57
+ self.app_pod = app_pod
67
58
  if namespace:
68
59
  self.namespace = namespace
69
60
  self.in_repl = in_repl
70
61
  self.bash_session = bash_session
71
62
  self.remote_dir = remote_dir
72
- # self.wait_log_flag = False
63
+ self.original_state: ReplState = None
64
+ self.pod_targetted = False
65
+
66
+ self.export_session: str = None
73
67
 
74
68
  if ns_sts:
75
69
  nn = ns_sts.split('@')
@@ -84,13 +78,51 @@ class ReplState:
84
78
  def __hash__(self):
85
79
  return hash((self.sts, self.pod))
86
80
 
81
+ def __str__(self):
82
+ msg = ''
83
+ if self.device == ReplState.P:
84
+ msg = f'{ReplState.P}:'
85
+ host, database = self.pg_host_n_database()
86
+ if database:
87
+ msg += database
88
+ elif host:
89
+ msg += host
90
+ elif self.device == ReplState.A:
91
+ msg = f'{ReplState.A}:'
92
+ if self.app_env:
93
+ msg += self.app_env
94
+ if self.app_app:
95
+ msg += f'/{self.app_app}'
96
+ if self.app_pod:
97
+ # azops88-c3-c3-k8sdeploy-appleader-001-79957cf5b6-9k4bw
98
+ group = re.match(r".*?-.*?-.*?-.*?-(.*?-.*?)-.*", self.app_pod)
99
+ msg += '/' + group[1]
100
+ elif self.device == ReplState.L:
101
+ msg = f'{ReplState.L}:'
102
+ elif self.device == ReplState.X:
103
+ msg = f'{ReplState.X}:'
104
+ if self.export_session:
105
+ msg += self.export_session
106
+ else:
107
+ msg = f'{ReplState.C}:'
108
+ if self.pod:
109
+ # cs-d0767a536f-cs-d0767a536f-default-sts-0
110
+ group = re.match(r".*?-.*?-(.*)", self.pod)
111
+ msg += group[1]
112
+ elif self.sts:
113
+ # cs-d0767a536f-cs-d0767a536f-default-sts
114
+ group = re.match(r".*?-.*?-(.*)", self.sts)
115
+ msg += group[1]
116
+
117
+ return msg
118
+
87
119
  def apply_args(self, args: list[str], cmd: list[str] = None, resolve_pg = True, args_to_check = 6) -> tuple['ReplState', list[str]]:
88
120
  state = self
89
121
 
90
122
  new_args = []
91
123
  for index, arg in enumerate(args):
92
124
  if index < args_to_check:
93
- state = copy.copy(state)
125
+ state = copy(state)
94
126
 
95
127
  s, n = KubeContext.is_sts_name(arg)
96
128
  if s:
@@ -128,9 +160,9 @@ class ReplState:
128
160
  new_args = []
129
161
  for index, arg in enumerate(args):
130
162
  if index < 6:
131
- state = copy.copy(state)
163
+ state = copy(state)
132
164
 
133
- groups = re.match(r'^([a|c|l|p]):(.*)$', arg)
165
+ groups = re.match(r'^([a|c|l|p|x]):(.*)$', arg)
134
166
  if groups:
135
167
  if groups[1] == 'p':
136
168
  state.device = 'p'
@@ -149,6 +181,8 @@ class ReplState:
149
181
  state.namespace = ns
150
182
  elif groups[1] == 'l':
151
183
  state.device = 'l'
184
+ elif groups[1] == 'x':
185
+ state.device = 'x'
152
186
  else:
153
187
  state.device = 'a'
154
188
  if path := groups[2]:
@@ -166,84 +200,288 @@ class ReplState:
166
200
 
167
201
  return (state, new_args)
168
202
 
169
- def validate(self, required: RequiredState = None, pg_required: RequiredState = None, app_required: RequiredState = None):
170
- if not pg_required and not app_required:
171
- if required == RequiredState.CLUSTER:
172
- if not self.namespace or not self.sts:
203
+ def validate(self, required: list[RequiredState] = [], show_err = True):
204
+ if not required:
205
+ return True
206
+
207
+ def default_err():
208
+ if self.in_repl:
209
+ log2(f'Not a valid command on {self.device}: drive.')
210
+ else:
211
+ log2('* on a wrong device.')
212
+ log2()
213
+ display_help()
214
+
215
+ if type(required) is not list:
216
+ valid, err = self._validate(required)
217
+ if valid:
218
+ return True
219
+
220
+ if show_err:
221
+ if err:
222
+ err()
223
+ else:
224
+ default_err()
225
+
226
+ return False
227
+
228
+ devices = [r for r in required if r in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
229
+ non_devices = [r for r in required if r not in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X]]
230
+
231
+ first_error: Callable = None
232
+ for r in non_devices:
233
+ valid, err = self._validate(r)
234
+ if valid:
235
+ return True
236
+
237
+ if not first_error:
238
+ first_error = err
239
+
240
+ if devices:
241
+ valid, err = self._validate_device(devices)
242
+ if valid:
243
+ return True
244
+
245
+ if not first_error:
246
+ first_error = err
247
+
248
+ if show_err and first_error:
249
+ if first_error:
250
+ first_error()
251
+ else:
252
+ default_err()
253
+
254
+ return False
255
+
256
+ def _validate(self, required: RequiredState):
257
+ if required == RequiredState.CLUSTER:
258
+ if self.device != ReplState.C:
259
+ return (False, None)
260
+
261
+ if not self.namespace or not self.sts:
262
+ def error():
173
263
  if self.in_repl:
174
264
  log2('cd to a Cassandra cluster first.')
175
265
  else:
176
266
  log2('* Cassandra cluster is missing.')
177
267
  log2()
178
268
  display_help()
269
+ return (False, error)
179
270
 
180
- return False
181
- elif required == RequiredState.POD:
182
- if not self.namespace or not self.pod:
271
+ elif required == RequiredState.POD:
272
+ if self.device != ReplState.C:
273
+ return (False, None)
274
+
275
+ if not self.namespace or not self.pod:
276
+ def error():
183
277
  if self.in_repl:
184
278
  log2('cd to a pod first.')
185
279
  else:
186
280
  log2('* Pod is missing.')
187
281
  log2()
188
282
  display_help()
283
+ return (False, error)
284
+
285
+ elif required == RequiredState.CLUSTER_OR_POD:
286
+ if self.device != ReplState.C:
287
+ return (False, None)
189
288
 
190
- return False
191
- elif required == RequiredState.CLUSTER_OR_POD:
192
- if not self.namespace or not self.sts and not self.pod:
289
+ if not self.namespace or not self.sts and not self.pod:
290
+ def error():
193
291
  if self.in_repl:
194
292
  log2('cd to a Cassandra cluster first.')
195
293
  else:
196
294
  log2('* Cassandra cluster or pod is missing.')
197
295
  log2()
198
296
  display_help()
297
+ return (False, error)
199
298
 
200
- return False
201
- elif required == RequiredState.NAMESPACE:
202
- if not self.namespace:
299
+ elif required == RequiredState.NAMESPACE:
300
+ if self.device != ReplState.C:
301
+ return (False, None)
302
+
303
+ if not self.namespace:
304
+ def error():
203
305
  if self.in_repl:
204
306
  log2('Namespace is required.')
205
307
  else:
206
308
  log2('* namespace is missing.')
207
309
  log2()
208
310
  display_help()
311
+ return (False, error)
312
+
313
+ elif required == RequiredState.PG_DATABASE:
314
+ if self.device != ReplState.P:
315
+ return (False, None)
316
+
317
+ _, database = self.pg_host_n_database()
318
+ if not database:
319
+ def error():
320
+ if self.in_repl:
321
+ log2('cd to a database first.')
322
+ else:
323
+ log2('* database is missing.')
324
+ log2()
325
+ display_help()
326
+ return (False, error)
327
+
328
+ elif required == RequiredState.APP_APP:
329
+ if self.device != ReplState.A:
330
+ return (False, None)
331
+
332
+ if not self.app_app:
333
+ def error():
334
+ if self.in_repl:
335
+ log2('cd to an app first.')
336
+ else:
337
+ log2('* app is missing.')
338
+ log2()
339
+ display_help()
340
+ return (False, error)
209
341
 
210
- return False
342
+ elif required == RequiredState.EXPORT_DB:
343
+ if self.device not in [ReplState.C, ReplState.X]:
344
+ return (False, None)
211
345
 
212
- if pg_required == RequiredState.PG_DATABASE:
213
- pg = PostgresSession(self.namespace, self.pg_path)
214
- if not pg.db:
346
+ if not self.export_session:
347
+ def error():
348
+ if self.in_repl:
349
+ if self.device == ReplState.C:
350
+ log2("Select an export database first with 'use' command.")
351
+ else:
352
+ log2('cd to an export database first.')
353
+ else:
354
+ log2('* export database is missing.')
355
+ log2()
356
+ display_help()
357
+ return (False, error)
358
+
359
+ elif required in [ReplState.L, ReplState.A, ReplState.C, ReplState.P, ReplState.X] and self.device != required:
360
+ def error():
215
361
  if self.in_repl:
216
- log2('cd to a database first.')
362
+ log2(f'Switch to {required}: first.')
217
363
  else:
218
- log2('* database is missing.')
364
+ log2('* on a wrong device.')
219
365
  log2()
220
366
  display_help()
367
+ return (False, error)
221
368
 
222
- return False
369
+ return (True, None)
223
370
 
224
- if app_required == RequiredState.APP_APP and not self.app_app:
225
- if self.in_repl:
226
- log2('cd to an app first.')
227
- else:
228
- log2('* app is missing.')
229
- log2()
230
- display_help()
231
-
232
- return False
371
+ def _validate_device(self, devices: list[RequiredState]):
372
+ if self.device not in devices:
373
+ def error():
374
+ if self.in_repl:
375
+ log2(f'Not a valid command on {self.device}: drive.')
376
+ else:
377
+ log2('* on a wrong device.')
378
+ log2()
379
+ display_help()
380
+ return (False, error)
233
381
 
234
- return True
382
+ return (True, None)
235
383
 
236
384
  def user_pass(self, secret_path = 'cql.secret'):
237
385
  return Secrets.get_user_pass(self.pod if self.pod else self.sts, self.namespace, secret_path=secret_path)
238
386
 
239
387
  def enter_bash(self, bash_session: BashSession):
388
+ self.push()
389
+
240
390
  self.bash_session = bash_session
241
- if self.device != ReplState.C:
242
- self.device = ReplState.C
243
- log2(f'Moved to {ReplState.C}: automatically. Will move back to {ReplState.P}: when you exit the bash session.')
244
391
 
245
392
  def exit_bash(self):
246
- if self.bash_session and self.bash_session.device:
247
- self.device = self.bash_session.device
393
+ self.pop()
394
+ self.bash_session = None
395
+
396
+ def push(self, pod_targetted=False):
397
+ if not self.original_state:
398
+ self.original_state = copy(self)
399
+ self.pod_targetted = pod_targetted
400
+
401
+ def pop(self):
402
+ if o := self.original_state:
403
+ self.device = o.device
404
+ self.sts = o.sts
405
+ self.pod = o.pod
406
+ self.pg_path = o.pg_path
407
+ self.app_env = o.app_env
408
+ self.app_app = o.app_app
409
+ self.app_pod = o.app_pod
410
+ # self.export_session = o.export_session
411
+ self.namespace = o.namespace
412
+
413
+ self.original_state = None
414
+ self.pod_targetted = False
415
+
416
+ def pg_host_n_database(self):
417
+ host = None
418
+ database = None
419
+
420
+ if self.pg_path:
421
+ host_n_db = self.pg_path.split('/')
422
+ host = host_n_db[0]
423
+ if len(host_n_db) > 1:
424
+ database = host_n_db[1]
425
+
426
+ return host, database
427
+
428
+ def with_no_pod(self):
429
+ state1 = copy(self)
430
+ state1.pod = None
431
+
432
+ return state1
433
+
434
+ def with_pod(self, pod: str):
435
+ state1 = copy(self)
436
+ state1.pod = pod
437
+
438
+ return state1
439
+
440
+ class DevicePodService:
441
+ def __init__(self, handler: 'DeviceExecHandler'):
442
+ self.handler = handler
443
+
444
+ def exec(self, command: str, action='bash', show_out = True):
445
+ state = self.handler.state
446
+
447
+ rs = None
448
+ if state.device == ReplState.A and state.app_app:
449
+ if state.app_pod:
450
+ rs = [AppPods.exec(state.app_pod, state.namespace, command, show_out=show_out)]
451
+ else:
452
+ pods = AppPods.pod_names(state.namespace, state.app_env, state.app_app)
453
+ rs = AppClusters.exec(pods, state.namespace, command, show_out=show_out)
454
+ elif state.pod:
455
+ rs = [CassandraNodes.exec(state.pod, state.namespace, command, show_out=show_out)]
456
+ elif state.sts:
457
+ rs = CassandraClusters.exec(state.sts, state.namespace, command, action=action, show_out=show_out)
458
+ # assume that pg-agent or ops pod is single pod
459
+
460
+ dir = None
461
+ if rs:
462
+ for r in rs:
463
+ if r.exit_code(): # if fails to read the session file, ignore
464
+ continue
465
+
466
+ dir0 = r.stdout.strip(' \r\n')
467
+ if dir:
468
+ if dir != dir0:
469
+ log2('Inconsitent working dir found across multiple pods.')
470
+ return None
471
+ else:
472
+ dir = dir0
473
+
474
+ return dir
475
+
476
+ class DeviceExecHandler:
477
+ def __init__(self, state: ReplState):
478
+ self.state = state
479
+
480
+ def __enter__(self):
481
+ return DevicePodService(self)
482
+
483
+ def __exit__(self, exc_type, exc_val, exc_tb):
484
+ return False
248
485
 
249
- self.bash_session = None
486
+ def device(state: ReplState):
487
+ return DeviceExecHandler(state)
@@ -0,0 +1,44 @@
1
+ import asyncio
2
+ from concurrent.futures import ThreadPoolExecutor
3
+ import inspect
4
+ import threading
5
+ import traceback
6
+
7
+ from adam.utils import log2, log_timing
8
+
9
+ class AsyncExecutor:
10
+ # some lib does not handle asyncio loop properly, as sync exec submit does not work, use another async loop
11
+
12
+ lock = threading.Lock()
13
+ in_queue = set()
14
+
15
+ loop: asyncio.AbstractEventLoop = None
16
+ async_exec: ThreadPoolExecutor = None
17
+
18
+ def preload(action: callable, log_key: str = None):
19
+ with AsyncExecutor.lock:
20
+ if not AsyncExecutor.loop:
21
+ AsyncExecutor.loop = asyncio.new_event_loop()
22
+ AsyncExecutor.async_exec = ThreadPoolExecutor(max_workers=6, thread_name_prefix='async')
23
+ AsyncExecutor.loop.set_default_executor(AsyncExecutor.async_exec)
24
+
25
+ async def a():
26
+ try:
27
+ arg_needed = len(action.__code__.co_varnames)
28
+
29
+ if log_key:
30
+ with log_timing(log_key):
31
+ r = action(None) if arg_needed else action()
32
+ else:
33
+ r = action(None) if arg_needed else action()
34
+ if inspect.isawaitable(r):
35
+ await r
36
+
37
+ AsyncExecutor.in_queue.remove(log_key)
38
+ except Exception as e:
39
+ log2('preloading error', e, inspect.getsourcelines(action)[0][0])
40
+ traceback.print_exc()
41
+
42
+ if log_key not in AsyncExecutor.in_queue:
43
+ AsyncExecutor.in_queue.add(log_key)
44
+ AsyncExecutor.async_exec.submit(lambda: AsyncExecutor.loop.run_until_complete(a()))