kaqing 2.0.98__py3-none-any.whl → 2.0.171__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 (194) hide show
  1. adam/__init__.py +0 -2
  2. adam/app_session.py +9 -7
  3. adam/batch.py +4 -18
  4. adam/checks/check_utils.py +14 -46
  5. adam/checks/cpu.py +7 -1
  6. adam/checks/cpu_metrics.py +52 -0
  7. adam/columns/columns.py +3 -1
  8. adam/columns/cpu.py +3 -1
  9. adam/columns/cpu_metrics.py +22 -0
  10. adam/commands/__init__.py +15 -0
  11. adam/commands/alter_tables.py +50 -61
  12. adam/commands/app_cmd.py +38 -0
  13. adam/commands/app_ping.py +8 -14
  14. adam/commands/audit/audit.py +43 -30
  15. adam/commands/audit/audit_repair_tables.py +26 -46
  16. adam/commands/audit/audit_run.py +50 -0
  17. adam/commands/audit/show_last10.py +48 -0
  18. adam/commands/audit/show_slow10.py +47 -0
  19. adam/commands/audit/show_top10.py +45 -0
  20. adam/commands/audit/utils_show_top10.py +59 -0
  21. adam/commands/bash/__init__.py +5 -0
  22. adam/commands/bash/bash.py +36 -0
  23. adam/commands/bash/bash_completer.py +93 -0
  24. adam/commands/bash/utils_bash.py +16 -0
  25. adam/commands/cat.py +50 -0
  26. adam/commands/cd.py +15 -91
  27. adam/commands/check.py +23 -18
  28. adam/commands/cli_commands.py +2 -3
  29. adam/commands/code.py +57 -0
  30. adam/commands/command.py +96 -40
  31. adam/commands/commands_utils.py +9 -19
  32. adam/commands/cp.py +33 -39
  33. adam/commands/cql/cql_completions.py +30 -8
  34. adam/commands/cql/cqlsh.py +12 -27
  35. adam/commands/cql/utils_cql.py +343 -0
  36. adam/commands/deploy/code_start.py +7 -10
  37. adam/commands/deploy/code_stop.py +4 -21
  38. adam/commands/deploy/code_utils.py +3 -3
  39. adam/commands/deploy/deploy.py +4 -21
  40. adam/commands/deploy/deploy_frontend.py +14 -17
  41. adam/commands/deploy/deploy_pg_agent.py +3 -6
  42. adam/commands/deploy/deploy_pod.py +67 -73
  43. adam/commands/deploy/deploy_utils.py +14 -24
  44. adam/commands/deploy/undeploy.py +4 -21
  45. adam/commands/deploy/undeploy_frontend.py +4 -7
  46. adam/commands/deploy/undeploy_pg_agent.py +6 -8
  47. adam/commands/deploy/undeploy_pod.py +11 -12
  48. adam/commands/devices/device.py +118 -0
  49. adam/commands/devices/device_app.py +173 -0
  50. adam/commands/devices/device_auit_log.py +49 -0
  51. adam/commands/devices/device_cass.py +185 -0
  52. adam/commands/devices/device_export.py +86 -0
  53. adam/commands/devices/device_postgres.py +144 -0
  54. adam/commands/devices/devices.py +25 -0
  55. adam/commands/exit.py +1 -4
  56. adam/commands/export/__init__.py +0 -0
  57. adam/commands/export/clean_up_all_export_sessions.py +37 -0
  58. adam/commands/export/clean_up_export_sessions.py +51 -0
  59. adam/commands/export/drop_export_database.py +55 -0
  60. adam/commands/export/drop_export_databases.py +43 -0
  61. adam/commands/export/export.py +53 -0
  62. adam/commands/export/export_databases.py +170 -0
  63. adam/commands/export/export_handlers.py +71 -0
  64. adam/commands/export/export_select.py +81 -0
  65. adam/commands/export/export_select_x.py +54 -0
  66. adam/commands/export/export_use.py +52 -0
  67. adam/commands/export/exporter.py +352 -0
  68. adam/commands/export/import_session.py +40 -0
  69. adam/commands/export/importer.py +67 -0
  70. adam/commands/export/importer_athena.py +80 -0
  71. adam/commands/export/importer_sqlite.py +47 -0
  72. adam/commands/export/show_column_counts.py +54 -0
  73. adam/commands/export/show_export_databases.py +36 -0
  74. adam/commands/export/show_export_session.py +48 -0
  75. adam/commands/export/show_export_sessions.py +44 -0
  76. adam/commands/export/utils_export.py +314 -0
  77. adam/commands/help.py +10 -6
  78. adam/commands/intermediate_command.py +49 -0
  79. adam/commands/issues.py +14 -40
  80. adam/commands/kubectl.py +38 -0
  81. adam/commands/login.py +28 -24
  82. adam/commands/logs.py +4 -6
  83. adam/commands/ls.py +11 -116
  84. adam/commands/medusa/medusa.py +4 -22
  85. adam/commands/medusa/medusa_backup.py +20 -24
  86. adam/commands/medusa/medusa_restore.py +30 -32
  87. adam/commands/medusa/medusa_show_backupjobs.py +16 -17
  88. adam/commands/medusa/medusa_show_restorejobs.py +12 -17
  89. adam/commands/nodetool.py +11 -17
  90. adam/commands/param_get.py +11 -12
  91. adam/commands/param_set.py +9 -10
  92. adam/commands/postgres/postgres.py +43 -36
  93. adam/commands/postgres/{postgres_session.py → postgres_context.py} +80 -46
  94. adam/commands/postgres/postgres_ls.py +4 -8
  95. adam/commands/postgres/postgres_preview.py +5 -9
  96. adam/commands/postgres/psql_completions.py +2 -2
  97. adam/commands/postgres/utils_postgres.py +66 -0
  98. adam/commands/preview_table.py +8 -61
  99. adam/commands/pwd.py +14 -44
  100. adam/commands/reaper/reaper.py +4 -24
  101. adam/commands/reaper/reaper_forward.py +48 -55
  102. adam/commands/reaper/reaper_forward_session.py +6 -0
  103. adam/commands/reaper/reaper_forward_stop.py +10 -16
  104. adam/commands/reaper/reaper_restart.py +7 -14
  105. adam/commands/reaper/reaper_run_abort.py +11 -30
  106. adam/commands/reaper/reaper_runs.py +42 -57
  107. adam/commands/reaper/reaper_runs_abort.py +29 -49
  108. adam/commands/reaper/reaper_schedule_activate.py +11 -30
  109. adam/commands/reaper/reaper_schedule_start.py +10 -29
  110. adam/commands/reaper/reaper_schedule_stop.py +10 -29
  111. adam/commands/reaper/reaper_schedules.py +4 -14
  112. adam/commands/reaper/reaper_status.py +8 -16
  113. adam/commands/reaper/utils_reaper.py +196 -0
  114. adam/commands/repair/repair.py +4 -22
  115. adam/commands/repair/repair_log.py +4 -7
  116. adam/commands/repair/repair_run.py +27 -29
  117. adam/commands/repair/repair_scan.py +31 -34
  118. adam/commands/repair/repair_stop.py +4 -7
  119. adam/commands/report.py +25 -21
  120. adam/commands/restart.py +25 -26
  121. adam/commands/rollout.py +19 -24
  122. adam/commands/shell.py +5 -4
  123. adam/commands/show/show.py +6 -19
  124. adam/commands/show/show_app_actions.py +26 -22
  125. adam/commands/show/show_app_id.py +8 -11
  126. adam/commands/show/show_app_queues.py +7 -10
  127. adam/commands/show/{show_repairs.py → show_cassandra_repairs.py} +8 -17
  128. adam/commands/show/show_cassandra_status.py +29 -33
  129. adam/commands/show/show_cassandra_version.py +4 -14
  130. adam/commands/show/show_commands.py +19 -21
  131. adam/commands/show/show_host.py +1 -1
  132. adam/commands/show/show_login.py +26 -24
  133. adam/commands/show/show_processes.py +16 -18
  134. adam/commands/show/show_storage.py +10 -20
  135. adam/commands/watch.py +26 -29
  136. adam/config.py +5 -14
  137. adam/embedded_params.py +1 -1
  138. adam/pod_exec_result.py +7 -1
  139. adam/repl.py +95 -131
  140. adam/repl_commands.py +48 -20
  141. adam/repl_state.py +270 -61
  142. adam/sql/sql_completer.py +105 -63
  143. adam/sql/sql_state_machine.py +618 -0
  144. adam/sql/term_completer.py +3 -0
  145. adam/sso/authn_ad.py +6 -5
  146. adam/sso/authn_okta.py +3 -3
  147. adam/sso/cred_cache.py +3 -2
  148. adam/sso/idp.py +3 -3
  149. adam/utils.py +439 -3
  150. adam/utils_app.py +98 -0
  151. adam/utils_athena.py +140 -87
  152. adam/utils_audits.py +106 -0
  153. adam/utils_issues.py +32 -0
  154. adam/utils_k8s/app_clusters.py +28 -0
  155. adam/utils_k8s/app_pods.py +33 -0
  156. adam/utils_k8s/cassandra_clusters.py +22 -20
  157. adam/utils_k8s/cassandra_nodes.py +4 -4
  158. adam/utils_k8s/custom_resources.py +5 -0
  159. adam/utils_k8s/ingresses.py +2 -2
  160. adam/utils_k8s/k8s.py +87 -0
  161. adam/utils_k8s/pods.py +77 -68
  162. adam/utils_k8s/secrets.py +4 -4
  163. adam/utils_k8s/service_accounts.py +5 -4
  164. adam/utils_k8s/services.py +2 -2
  165. adam/utils_k8s/statefulsets.py +1 -12
  166. adam/utils_net.py +4 -4
  167. adam/utils_repl/__init__.py +0 -0
  168. adam/utils_repl/automata_completer.py +48 -0
  169. adam/utils_repl/repl_completer.py +46 -0
  170. adam/utils_repl/state_machine.py +173 -0
  171. adam/utils_sqlite.py +109 -0
  172. adam/version.py +1 -1
  173. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/METADATA +1 -1
  174. kaqing-2.0.171.dist-info/RECORD +236 -0
  175. adam/commands/app.py +0 -67
  176. adam/commands/bash.py +0 -92
  177. adam/commands/cql/cql_table_completer.py +0 -8
  178. adam/commands/cql/cql_utils.py +0 -115
  179. adam/commands/describe/describe.py +0 -47
  180. adam/commands/describe/describe_keyspace.py +0 -60
  181. adam/commands/describe/describe_keyspaces.py +0 -49
  182. adam/commands/describe/describe_schema.py +0 -49
  183. adam/commands/describe/describe_table.py +0 -60
  184. adam/commands/describe/describe_tables.py +0 -49
  185. adam/commands/devices.py +0 -118
  186. adam/commands/postgres/postgres_utils.py +0 -31
  187. adam/commands/postgres/psql_table_completer.py +0 -11
  188. adam/commands/reaper/reaper_session.py +0 -159
  189. adam/sql/state_machine.py +0 -460
  190. kaqing-2.0.98.dist-info/RECORD +0 -191
  191. /adam/commands/{describe → devices}/__init__.py +0 -0
  192. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/WHEEL +0 -0
  193. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/entry_points.txt +0 -0
  194. {kaqing-2.0.98.dist-info → kaqing-2.0.171.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,7 @@
1
+ from adam.commands import extract_options
1
2
  from adam.commands.command import Command
2
3
  from adam.commands.commands_utils import show_table
4
+ from adam.commands.cql.utils_cql import cassandra
3
5
  from adam.config import Config
4
6
  from adam.utils_k8s.statefulsets import StatefulSets
5
7
  from adam.repl_state import ReplState, RequiredState
@@ -26,28 +28,24 @@ class ShowProcesses(Command):
26
28
  if not(args := self.args(cmd)):
27
29
  return super().run(cmd, state)
28
30
 
29
- state, args = self.apply_state(args, state)
30
- if not self.validate_state(state):
31
- return state
31
+ with self.validate(args, state) as (args, state):
32
+ with extract_options(args, ['-s', '--show']) as (args, show_out):
33
+ cols = Config().get('processes.columns', 'pod,cpu-metrics,mem')
34
+ header = Config().get('processes.header', 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT')
32
35
 
33
- args, show_output = Command.extract_options(args, ['-s', '--show'])
36
+ qing_args = ['qing', 'recipe', 'with']
37
+ args, _, recipe_qing = Command.extract_options(args, options=qing_args)
38
+ if set(recipe_qing) == set(qing_args):
39
+ cols = Config().get('processes-qing.columns', 'pod,cpu,mem')
40
+ header = Config().get('processes-qing.header', 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT')
34
41
 
35
- cols = Config().get('processes.columns', 'pod,cpu,mem')
36
- header = Config().get('processes.header', 'POD_NAME,CPU,MEM/LIMIT')
42
+ with cassandra(state) as pods:
43
+ pods.show_table(cols, header, show_out=show_out)
37
44
 
38
- if state.pod:
39
- show_table(state, [state.pod], cols, header, show_output=show_output)
40
- elif state.sts:
41
- pod_names = [pod.metadata.name for pod in StatefulSets.pods(state.sts, state.namespace)]
42
- show_table(state, pod_names, cols, header, show_output=show_output)
43
-
44
- return state
45
+ return state
45
46
 
46
47
  def completion(self, state: ReplState):
47
- if not state.sts:
48
- return {}
49
-
50
- return super().completion(state)
48
+ return super().completion(state, {'with': {'recipe': {'metrics': {'-s': None}, 'qing': {'-s': None}}}, '-s': None})
51
49
 
52
50
  def help(self, _: ReplState):
53
- return f'{ShowProcesses.COMMAND} [-s]\t show process overview -s show commands on nodes'
51
+ return f'{ShowProcesses.COMMAND} [with recipe qing|metrics] [-s]\t show process overview -s show commands on nodes'
@@ -1,7 +1,7 @@
1
+ from adam.commands import extract_options
1
2
  from adam.commands.command import Command
2
- from adam.commands.commands_utils import show_table
3
+ from adam.commands.cql.utils_cql import cassandra
3
4
  from adam.config import Config
4
- from adam.utils_k8s.statefulsets import StatefulSets
5
5
  from adam.repl_state import ReplState, RequiredState
6
6
 
7
7
  class ShowStorage(Command):
@@ -26,27 +26,17 @@ class ShowStorage(Command):
26
26
  if not(args := self.args(cmd)):
27
27
  return super().run(cmd, state)
28
28
 
29
- state, args = self.apply_state(args, state)
30
- if not self.validate_state(state):
31
- return state
29
+ with self.validate(args, state) as (args, state):
30
+ with extract_options(args, ['-s', '--show']) as (args, show_out):
31
+ cols = Config().get('storage.columns', 'pod,volume_root,volume_cassandra,snapshots,data,compactions')
32
+ header = Config().get('storage.header', 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS')
33
+ with cassandra(state) as pods:
34
+ pods.show_table(cols, header, show_out=show_out)
32
35
 
33
- args, show_output = Command.extract_options(args, ['-s', '--show'])
34
-
35
- cols = Config().get('storage.columns', 'pod,volume_root,volume_cassandra,snapshots,data,compactions')
36
- header = Config().get('storage.header', 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS')
37
- if state.pod:
38
- show_table(state, [state.pod], cols, header, show_output=show_output)
39
- elif state.sts:
40
- pod_names = [pod.metadata.name for pod in StatefulSets.pods(state.sts, state.namespace)]
41
- show_table(state, pod_names, cols, header, show_output=show_output)
42
-
43
- return state
36
+ return state
44
37
 
45
38
  def completion(self, state: ReplState):
46
- if not state.sts:
47
- return {}
48
-
49
- return super().completion(state)
39
+ return super().completion(state, {'-s': None})
50
40
 
51
41
  def help(self, _: ReplState):
52
42
  return f'{ShowStorage.COMMAND} [-s]\t show storage overview -s show commands on nodes'
adam/commands/watch.py CHANGED
@@ -8,10 +8,10 @@ from adam.commands.commands_utils import show_pods, show_rollout
8
8
  from adam.config import Config
9
9
  from adam.utils_k8s.statefulsets import StatefulSets
10
10
  from adam.repl_state import ReplState, RequiredState
11
- from adam.utils import convert_seconds, log2
11
+ from adam.utils import log2
12
12
 
13
13
  class Watch(Command):
14
- COMMAND = 'watch'
14
+ COMMAND = 'watch cassandra pods'
15
15
 
16
16
  # the singleton pattern
17
17
  def __new__(cls, *args, **kwargs):
@@ -26,37 +26,34 @@ class Watch(Command):
26
26
  return Watch.COMMAND
27
27
 
28
28
  def required(self):
29
- return RequiredState.CLUSTER_OR_POD
29
+ return RequiredState.NAMESPACE
30
30
 
31
31
  def run(self, cmd: str, state: ReplState):
32
32
  if not(args := self.args(cmd)):
33
33
  return super().run(cmd, state)
34
34
 
35
- state, args = self.apply_state(args, state)
36
- if not self.validate_state(state):
37
- return state
38
-
39
- pods = StatefulSets.pods(state.sts, state.namespace)
40
- if not pods:
41
- log2("No pods are found.")
42
- return state
35
+ with self.validate(args, state) as (args, state):
36
+ pods = StatefulSets.pods(state.sts, state.namespace)
37
+ if not pods:
38
+ log2("No pods are found.")
39
+ return state
43
40
 
44
- stop_event = threading.Event()
45
- thread = threading.Thread(target=self.loop, args=(stop_event, state.sts, pods, state.namespace), daemon=True)
46
- thread.start()
41
+ stop_event = threading.Event()
42
+ thread = threading.Thread(target=self.loop, args=(stop_event, state.sts, pods, state.namespace), daemon=True)
43
+ thread.start()
47
44
 
48
- try:
49
- log2(f"Press Ctrl+C to break.")
45
+ try:
46
+ log2(f"Press Ctrl+C to break.")
50
47
 
51
- time.sleep(Config().get('watch.timeout', 3600 * 1))
52
- except KeyboardInterrupt:
53
- pass
48
+ time.sleep(Config().get('watch.timeout', 3600 * 1))
49
+ except KeyboardInterrupt:
50
+ pass
54
51
 
55
- log2("Stopping watch...")
56
- stop_event.set()
57
- thread.join()
52
+ log2("Stopping watch...")
53
+ stop_event.set()
54
+ thread.join()
58
55
 
59
- return state
56
+ return state
60
57
 
61
58
  def loop(self, stop_flag: threading.Event, sts: str, pods: List[client.V1Pod], ns: str):
62
59
  show_pods(pods, ns)
@@ -73,13 +70,13 @@ class Watch(Command):
73
70
  cnt = Config().get('watch.interval', 10)
74
71
 
75
72
  def completion(self, state: ReplState):
76
- if state.pod:
77
- return {}
73
+ if sc := super().completion(state):
74
+ if state.sts:
75
+ return sc
78
76
 
79
- if not state.sts:
80
- return {Watch.COMMAND: {n: None for n in StatefulSets.list_sts_names()}}
77
+ return super().completion(state, {n: None for n in StatefulSets.list_sts_names()})
81
78
 
82
- return {Watch.COMMAND: None}
79
+ return {}
83
80
 
84
81
  def help(self, _: ReplState):
85
- return f'{Watch.COMMAND}\t watch pod changes'
82
+ return f'{Watch.COMMAND}\t watch Cassandra pod changes'
adam/config.py CHANGED
@@ -3,13 +3,16 @@ from typing import TypeVar, cast
3
3
  import yaml
4
4
 
5
5
  from . import __version__
6
- from adam.utils import copy_config_file, get_deep_keys, log2
6
+ from adam.utils import LogConfig, copy_config_file, get_deep_keys, log2
7
7
 
8
8
  T = TypeVar('T')
9
9
 
10
10
  class Config:
11
11
  EMBEDDED_PARAMS = {}
12
12
 
13
+ LogConfig.is_debug = lambda: Config().is_debug()
14
+ LogConfig.is_debug_timing = lambda: Config().get('debugs.timings', False)
15
+
13
16
  # the singleton pattern
14
17
  def __new__(cls, *args, **kwargs):
15
18
  if not hasattr(cls, 'instance'): cls.instance = super(Config, cls).__new__(cls)
@@ -41,10 +44,6 @@ class Config:
41
44
  def is_debug(self):
42
45
  return os.getenv('QING_DEV', 'false').lower() == 'true' or Config().get('debug', False)
43
46
 
44
- def debug(self, s: None):
45
- if self.is_debug():
46
- log2(f'DEBUG {s}')
47
-
48
47
  def get(self, key: str, default: T) -> T:
49
48
  # params['nodetool']['status']['max-nodes']
50
49
  d = self.params
@@ -85,12 +84,4 @@ class Config:
85
84
  log2(f'incorrect path: {key}')
86
85
  return None
87
86
 
88
- return v if v else 'false'
89
-
90
- def wait_log(self, msg: str):
91
- if hasattr(self, 'wait_log_flag') and not self.wait_log_flag:
92
- log2(msg)
93
- self.wait_log_flag = True
94
-
95
- def clear_wait_log_flag(self):
96
- self.wait_log_flag = False
87
+ return v if v else 'false'
adam/embedded_params.py CHANGED
@@ -1,2 +1,2 @@
1
1
  def config():
2
- return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results'}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'medusa': {'restore-auto-complete': False}, 'nodetool': {'workers': 32, 'samples': 3, 'commands_in_line': 40}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,CPU,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'a', 'auto-enter-app': 'c3/c3', 'auto-enter-only-cluster': 'cluster', 'history': {'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'debug': False, 'debugs': {'timings': False, 'exit-on-error': False, 'show-parallelism': False}}
2
+ return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'container-name': 'c3-server', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'repair-partition-tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results', 'repair-cluster-tables': 'cluster'}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'export': {'workers': 8, 'csv_dir': '/c3/cassandra/tmp', 'column_counts_query': 'select id, count(id) as columns from {table} group by id order by columns desc limit 10', 'default-importer': 'sqlite', 'sqlite': {'workers': 8, 'columns': '<row-key>', 'local-db-dir': '/tmp/qing-db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-prefix': '/tmp/qing', 'medusa': {'restore-auto-complete': False}, 'nodetool': {'workers': 32, 'samples': 3, 'commands_in_line': 40}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu-metrics,mem', 'header': 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT'}, 'processes-qing': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'c', 'a': {'auto-enter': 'c3/c3/*'}, 'c': {'auto-enter': 'cluster'}, 'x': {'auto-enter': 'latest'}, 'history': {'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'debug': False, 'debugs': {'timings': False, 'exit-on-error': False, 'show-parallelism': False}}
adam/pod_exec_result.py CHANGED
@@ -32,4 +32,10 @@ class PodExecResult:
32
32
  except:
33
33
  pass
34
34
 
35
- return code
35
+ return code
36
+
37
+ def __str__(self):
38
+ return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
39
+
40
+ def __audit_extra__(self):
41
+ return self.log_file if self.log_file else None
adam/repl.py CHANGED
@@ -1,29 +1,25 @@
1
- import getpass
2
1
  import os
3
- import re
4
2
  import time
5
3
  import traceback
4
+ from typing import cast
6
5
  import click
7
- import concurrent
8
- from prompt_toolkit.completion import NestedCompleter
9
6
  from prompt_toolkit.key_binding import KeyBindings
10
- import requests
11
7
 
12
8
  from adam.cli_group import cli
13
- from adam.commands.command import Command
9
+ from adam.commands.command import Command, InvalidState
14
10
  from adam.commands.command_helpers import ClusterCommandHelper
11
+ from adam.commands.devices.devices import Devices
15
12
  from adam.commands.help import Help
16
- from adam.commands.postgres.postgres_session import PostgresSession
17
13
  from adam.config import Config
14
+ from adam.utils_audits import Audits
18
15
  from adam.utils_k8s.kube_context import KubeContext
19
- from adam.utils_k8s.statefulsets import StatefulSets
20
16
  from adam.log import Log
21
17
  from adam.repl_commands import ReplCommands
22
18
  from adam.repl_session import ReplSession
23
19
  from adam.repl_state import ReplState
24
- from adam.utils import deep_merge_dicts, deep_sort_dict, lines_to_tabular, log2
20
+ from adam.utils import clear_wait_log_flag, debug, deep_merge_dicts, deep_sort_dict, lines_to_tabular, log2, log_timing
25
21
  from adam.apps import Apps
26
- from adam.utils_net import get_my_host
22
+ from adam.utils_repl.repl_completer import ReplCompleter
27
23
  from . import __version__
28
24
 
29
25
  def enter_repl(state: ReplState):
@@ -37,65 +33,13 @@ def enter_repl(state: ReplState):
37
33
  session = ReplSession().prompt_session
38
34
 
39
35
  def prompt_msg():
40
- msg = ''
41
- if state.device == ReplState.P:
42
- msg = f'{ReplState.P}:'
43
- pg = PostgresSession(state.namespace, state.pg_path) if state.pg_path else None
44
- if pg and pg.db:
45
- msg += pg.db
46
- elif pg and pg.host:
47
- msg += pg.host
48
- elif state.device == ReplState.A:
49
- msg = f'{ReplState.A}:'
50
- if state.app_env:
51
- msg += state.app_env
52
- if state.app_app:
53
- msg += f'/{state.app_app}'
54
- elif state.device == ReplState.L:
55
- msg = f'{ReplState.L}:'
56
- else:
57
- msg = f'{ReplState.C}:'
58
- if state.pod:
59
- # cs-d0767a536f-cs-d0767a536f-default-sts-0
60
- group = re.match(r".*?-.*?-(.*)", state.pod)
61
- msg += group[1]
62
- elif state.sts:
63
- # cs-d0767a536f-cs-d0767a536f-default-sts
64
- group = re.match(r".*?-.*?-(.*)", state.sts)
65
- msg += group[1]
36
+ msg = state.__str__()
66
37
 
67
38
  return f"{msg}$ " if state.bash_session else f"{msg}> "
68
39
 
69
40
  Log.log2(f'kaqing {__version__}')
70
41
 
71
- if state.device == ReplState.C:
72
- auto_enter = Config().get('repl.auto-enter-only-cluster', 'cluster')
73
- ss = StatefulSets.list_sts_name_and_ns()
74
- if not ss:
75
- raise Exception("no Cassandra clusters found")
76
- elif not state.sts and len(ss) == 1 and auto_enter in ['cluster', 'first-pod']:
77
- cluster = ss[0]
78
- state.sts = cluster[0]
79
- state.namespace = cluster[1]
80
- if auto_enter == 'first-pod':
81
- state.pod = f'{state.sts}-0'
82
- if KubeContext().in_cluster_namespace:
83
- Config().wait_log(f'Moving to the only Cassandra cluster: {state.sts}...')
84
- else:
85
- Config().wait_log(f'Moving to the only Cassandra cluster: {state.sts}@{state.namespace}...')
86
- elif state.device == ReplState.A:
87
- if not state.app_env:
88
- if app := Config().get('repl.auto-enter-app', 'c3/c3'):
89
- if app != 'no':
90
- ea = app.split('/')
91
- state.app_env = ea[0]
92
- if len(ea) > 1:
93
- state.app_app = ea[1]
94
- Config().wait_log(f'Moving to {state.app_env}/{state.app_app}...')
95
- else:
96
- Config().wait_log(f'Moving to {state.app_env}...')
97
- elif state.device == ReplState.P:
98
- Config().wait_log('Inspecting postgres database instances...')
42
+ Devices.device(state).enter(state)
99
43
 
100
44
  kb = KeyBindings()
101
45
 
@@ -103,31 +47,34 @@ def enter_repl(state: ReplState):
103
47
  def _(event):
104
48
  event.app.current_buffer.text = ''
105
49
 
106
- with concurrent.futures.ThreadPoolExecutor(max_workers=Config().get('audit.workers', 3)) as executor:
50
+ with Audits.offload() as exec:
107
51
  # warm up AWS lambda - this log line may timeout and get lost, which is fine
108
- executor.submit(audit_log, 'entering kaqing repl', state)
52
+ exec.submit(Audits.log, 'entering kaqing repl', state.namespace, 'z', 0.0)
53
+
54
+ s0 = time.time()
109
55
 
110
56
  # use sorted command list only for auto-completion
111
57
  sorted_cmds = sorted(cmd_list, key=lambda cmd: cmd.command())
112
58
  while True:
59
+ cmd: str = None
60
+ result = None
113
61
  try:
114
- completer = NestedCompleter.from_nested_dict({})
62
+ completer = ReplCompleter.from_nested_dict({})
115
63
  if not state.bash_session:
116
- completions = {}
117
- # app commands are available only on a: drive
118
- if state.device == ReplState.A and state.app_app:
119
- completions = Apps(path='apps.yaml').commands()
120
-
121
- for cmd in sorted_cmds:
122
- s1 = time.time()
123
- try:
124
- completions = deep_sort_dict(deep_merge_dicts(completions, cmd.completion(state)))
125
- finally:
126
- if Config().get('debugs.timings', False):
127
- log2(f'Timing auto-completion-calc {cmd.command()}: {time.time() - s1:.2f}')
128
-
129
- # print(json.dumps(completions, indent=4))
130
- completer = NestedCompleter.from_nested_dict(completions)
64
+ with log_timing('completion-calcs'):
65
+ completions = {}
66
+ # app commands are available only on a: drive
67
+ if state.device == ReplState.A and state.app_app:
68
+ completions = log_timing('actions', lambda: Apps(path='apps.yaml').commands())
69
+
70
+ for c in sorted_cmds:
71
+ try:
72
+ completions = log_timing(c.command(), lambda: deep_sort_dict(deep_merge_dicts(completions, c.completion(state))))
73
+ except:
74
+ log2(f'* {c.command()} command returned None completions.')
75
+
76
+ # print(json.dumps(completions, indent=4))
77
+ completer = ReplCompleter.from_nested_dict(completions)
131
78
 
132
79
  cmd = session.prompt(prompt_msg(), completer=completer, key_bindings=kb)
133
80
  s0 = time.time()
@@ -139,33 +86,38 @@ def enter_repl(state: ReplState):
139
86
 
140
87
  cmd = f'bash {cmd}'
141
88
 
142
- if cmd and cmd.strip(' ') and not cmds.run(cmd, state):
143
- c_sql_tried = False
144
- if state.device == ReplState.P:
145
- pg = PostgresSession(state.namespace, state.pg_path)
146
- if pg.db:
147
- c_sql_tried = True
148
- cmd = f'pg {cmd}'
149
- cmds.run(cmd, state)
150
- elif state.device == ReplState.A:
151
- if state.app_app:
152
- c_sql_tried = True
153
- cmd = f'app {cmd}'
154
- cmds.run(cmd, state)
155
- elif state.device == ReplState.L:
156
- c_sql_tried = True
157
- cmd = f'audit {cmd}'
158
- cmds.run(cmd, state)
89
+ def targetted(state: ReplState, cmd: str):
90
+ if not (cmd.startswith('@') and len(arry := cmd.split(' ')) > 1):
91
+ return state, cmd
92
+
93
+ if state.device == ReplState.A and state.app_app or state.device == ReplState.P:
94
+ state.push()
95
+
96
+ state.app_pod = arry[0].strip('@')
97
+ cmd = ' '.join(arry[1:])
98
+ elif state.device == ReplState.P:
99
+ state.push()
100
+
101
+ state.app_pod = arry[0].strip('@')
102
+ cmd = ' '.join(arry[1:])
159
103
  elif state.sts:
160
- c_sql_tried = True
161
- cmd = f'cql {cmd}'
162
- cmds.run(cmd, state)
163
-
164
- if not c_sql_tried:
165
- log2(f'* Invalid command: {cmd}')
166
- log2()
167
- lines = [c.help(state) for c in cmd_list if c.help(state)]
168
- log2(lines_to_tabular(lines, separator='\t'))
104
+ state.push()
105
+
106
+ state.pod = arry[0].strip('@')
107
+ cmd = ' '.join(arry[1:])
108
+
109
+ return (state, cmd)
110
+
111
+ target, cmd = targetted(state, cmd)
112
+ try:
113
+ if cmd and cmd.strip(' ') and not (result := cmds.run(cmd, target)):
114
+ result = try_device_default_action(target, cmds, cmd_list, cmd)
115
+ except InvalidState:
116
+ pass
117
+
118
+ if result and type(result) is ReplState and (s := cast(ReplState, result).export_session) != state.export_session:
119
+ state.export_session = s
120
+
169
121
  except EOFError: # Handle Ctrl+D (EOF) for graceful exit
170
122
  break
171
123
  except Exception as e:
@@ -173,33 +125,45 @@ def enter_repl(state: ReplState):
173
125
  raise e
174
126
  else:
175
127
  log2(e)
176
- Config().debug(traceback.format_exc())
128
+ debug(traceback.format_exc())
177
129
  finally:
178
- Config().clear_wait_log_flag()
179
- if Config().get('debugs.timings', False) and 'cmd' in locals() and 's0' in locals():
180
- log2(f'Timing command {cmd}: {time.time() - s0:.2f}')
130
+ if not state.bash_session:
131
+ state.pop()
132
+
133
+ clear_wait_log_flag()
134
+ if cmd:
135
+ log_timing(f'command {cmd}', s0=s0)
181
136
 
182
137
  # offload audit logging
183
138
  if cmd and (state.device != ReplState.L or Config().get('audit.log-audit-queries', False)):
184
- executor.submit(audit_log, cmd, state)
185
-
186
- def audit_log(cmd: str, state: ReplState):
187
- payload = {
188
- 'cluster': state.namespace if state.namespace else 'NA',
189
- 'ts': time.time(),
190
- 'host': get_my_host(),
191
- 'user': getpass.getuser(),
192
- 'line': cmd.replace('"', '""').replace('\n', ' '),
193
- }
194
- audit_endpoint = Config().get("audit.endpoint", "https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/")
195
- try:
196
- response = requests.post(audit_endpoint, json=payload, timeout=Config().get("audit.timeout", 10))
197
- if response.status_code in [200, 201]:
198
- Config().debug(response.text)
199
- else:
200
- log2(f"Error: {response.status_code} {response.text}")
201
- except requests.exceptions.Timeout as e:
202
- log2(f"Timeout occurred: {e}")
139
+ exec.submit(Audits.log, cmd, state.namespace, state.device, time.time() - s0, get_audit_extra(result))
140
+
141
+ def try_device_default_action(state: ReplState, cmds: Command, cmd_list: list[Command], cmd: str):
142
+ action_taken, result = Devices.device(state).try_fallback_action(cmds, state, cmd)
143
+
144
+ if not action_taken:
145
+ log2(f'* Invalid command: {cmd}')
146
+ log2()
147
+ lines = [c.help(state) for c in cmd_list if c.help(state)]
148
+ log2(lines_to_tabular(lines, separator='\t'))
149
+
150
+ return result
151
+
152
+ def get_audit_extra(result: any):
153
+ if not result:
154
+ return None
155
+
156
+ if type(result) is list:
157
+ extras = set()
158
+
159
+ for r in result:
160
+ if hasattr(r, '__audit_extra__') and (x := r.__audit_extra__()):
161
+ extras.add(x)
162
+
163
+ return ','.join(list(extras))
164
+
165
+ if hasattr(result, '__audit_extra__') and (x := result.__audit_extra__()):
166
+ return x
203
167
 
204
168
  @cli.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True), cls=ClusterCommandHelper, help="Enter interactive shell.")
205
169
  @click.option('--kubeconfig', '-k', required=False, metavar='path', help='path to kubeconfig file')