kaqing 2.0.214__py3-none-any.whl → 2.0.227__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kaqing might be problematic. Click here for more details.

Files changed (149) hide show
  1. adam/checks/compactionstats.py +2 -1
  2. adam/checks/cpu.py +2 -1
  3. adam/checks/disk.py +6 -5
  4. adam/checks/gossip.py +2 -1
  5. adam/checks/memory.py +2 -1
  6. adam/checks/status.py +2 -1
  7. adam/commands/app/app.py +2 -2
  8. adam/commands/app/app_ping.py +2 -2
  9. adam/commands/app/login.py +2 -2
  10. adam/commands/app/show_app_actions.py +2 -2
  11. adam/commands/app/show_app_id.py +2 -2
  12. adam/commands/app/show_app_queues.py +2 -2
  13. adam/commands/app/show_login.py +2 -2
  14. adam/commands/audit/audit.py +3 -5
  15. adam/commands/audit/audit_repair_tables.py +2 -2
  16. adam/commands/audit/audit_run.py +2 -2
  17. adam/commands/audit/show_last10.py +2 -2
  18. adam/commands/audit/show_slow10.py +2 -2
  19. adam/commands/audit/show_top10.py +2 -2
  20. adam/commands/bash/bash.py +2 -2
  21. adam/commands/cassandra/download_cassandra_log.py +2 -2
  22. adam/commands/cassandra/restart_cluster.py +2 -2
  23. adam/commands/cassandra/restart_node.py +2 -2
  24. adam/commands/cassandra/restart_nodes.py +2 -2
  25. adam/commands/cassandra/rollout.py +2 -2
  26. adam/commands/cassandra/show_cassandra_repairs.py +2 -2
  27. adam/commands/cassandra/show_cassandra_status.py +2 -2
  28. adam/commands/cassandra/show_cassandra_version.py +2 -2
  29. adam/commands/cassandra/show_processes.py +6 -6
  30. adam/commands/cassandra/show_storage.py +2 -2
  31. adam/commands/cassandra/watch.py +2 -2
  32. adam/commands/cli/clipboard_copy.py +2 -2
  33. adam/commands/cli/show_cli_commands.py +3 -3
  34. adam/commands/code.py +2 -2
  35. adam/commands/command.py +32 -5
  36. adam/commands/config/param_get.py +2 -2
  37. adam/commands/config/param_set.py +2 -2
  38. adam/commands/config/show_params.py +2 -2
  39. adam/commands/cql/alter_tables.py +2 -2
  40. adam/commands/cql/cqlsh.py +2 -2
  41. adam/commands/cql/utils_cql.py +13 -3
  42. adam/commands/debug/debug_completes.py +2 -2
  43. adam/commands/debug/debug_timings.py +2 -2
  44. adam/commands/debug/show_offloaded_completes.py +2 -2
  45. adam/commands/deploy/code_start.py +2 -2
  46. adam/commands/deploy/code_stop.py +2 -2
  47. adam/commands/deploy/deploy_frontend.py +2 -2
  48. adam/commands/deploy/deploy_pg_agent.py +2 -2
  49. adam/commands/deploy/deploy_pod.py +2 -2
  50. adam/commands/deploy/undeploy_frontend.py +2 -2
  51. adam/commands/deploy/undeploy_pg_agent.py +2 -2
  52. adam/commands/deploy/undeploy_pod.py +2 -2
  53. adam/commands/devices/device.py +7 -7
  54. adam/commands/devices/device_app.py +6 -6
  55. adam/commands/devices/device_auit_log.py +2 -2
  56. adam/commands/devices/device_cass.py +6 -6
  57. adam/commands/devices/device_export.py +2 -2
  58. adam/commands/devices/device_postgres.py +6 -6
  59. adam/commands/diag/check.py +2 -2
  60. adam/commands/diag/generate_report.py +2 -2
  61. adam/commands/diag/issues.py +3 -2
  62. adam/commands/exit.py +2 -2
  63. adam/commands/export/clean_up_all_export_sessions.py +2 -2
  64. adam/commands/export/clean_up_export_sessions.py +2 -2
  65. adam/commands/export/download_export_session.py +4 -5
  66. adam/commands/export/drop_export_database.py +2 -2
  67. adam/commands/export/drop_export_databases.py +2 -2
  68. adam/commands/export/export.py +3 -3
  69. adam/commands/export/export_databases.py +3 -0
  70. adam/commands/export/export_select.py +2 -2
  71. adam/commands/export/export_sessions.py +10 -9
  72. adam/commands/export/export_use.py +3 -3
  73. adam/commands/export/export_x_select.py +2 -2
  74. adam/commands/export/exporter.py +11 -11
  75. adam/commands/export/import_files.py +3 -7
  76. adam/commands/export/import_session.py +2 -2
  77. adam/commands/export/importer.py +6 -7
  78. adam/commands/export/show_column_counts.py +2 -3
  79. adam/commands/export/show_export_databases.py +3 -4
  80. adam/commands/export/show_export_session.py +4 -4
  81. adam/commands/export/show_export_sessions.py +3 -3
  82. adam/commands/export/utils_export.py +25 -33
  83. adam/commands/fs/cat.py +2 -2
  84. adam/commands/fs/cat_local.py +2 -2
  85. adam/commands/fs/cd.py +2 -2
  86. adam/commands/fs/download_file.py +2 -2
  87. adam/commands/fs/find_files.py +3 -3
  88. adam/commands/fs/find_processes.py +12 -20
  89. adam/commands/fs/head.py +4 -4
  90. adam/commands/fs/head_local.py +46 -0
  91. adam/commands/fs/ls.py +2 -2
  92. adam/commands/fs/ls_local.py +2 -2
  93. adam/commands/fs/pwd.py +2 -2
  94. adam/commands/fs/rm.py +2 -2
  95. adam/commands/fs/rm_downloads.py +2 -2
  96. adam/commands/fs/rm_logs.py +13 -7
  97. adam/commands/fs/rm_logs_local.py +38 -0
  98. adam/commands/fs/shell.py +2 -2
  99. adam/commands/fs/show_adam.py +2 -2
  100. adam/commands/fs/show_host.py +2 -2
  101. adam/commands/fs/show_last_results.py +39 -0
  102. adam/commands/fs/tail.py +36 -0
  103. adam/commands/fs/tail_local.py +46 -0
  104. adam/commands/fs/utils_fs.py +192 -0
  105. adam/commands/help.py +2 -2
  106. adam/commands/kubectl.py +2 -2
  107. adam/commands/medusa/medusa_backup.py +2 -2
  108. adam/commands/medusa/medusa_restore.py +2 -2
  109. adam/commands/medusa/medusa_show_backupjobs.py +2 -2
  110. adam/commands/medusa/medusa_show_restorejobs.py +2 -2
  111. adam/commands/nodetool/nodetool.py +30 -7
  112. adam/commands/nodetool/utils_nodetool.py +44 -0
  113. adam/commands/postgres/postgres.py +3 -6
  114. adam/commands/postgres/postgres_ls.py +2 -2
  115. adam/commands/postgres/postgres_preview.py +2 -2
  116. adam/commands/preview_table.py +2 -3
  117. adam/commands/reaper/reaper_forward.py +2 -2
  118. adam/commands/reaper/reaper_forward_stop.py +2 -2
  119. adam/commands/reaper/reaper_restart.py +2 -2
  120. adam/commands/reaper/reaper_run_abort.py +2 -2
  121. adam/commands/reaper/reaper_runs.py +14 -12
  122. adam/commands/reaper/reaper_runs_abort.py +2 -2
  123. adam/commands/reaper/reaper_schedule_activate.py +2 -2
  124. adam/commands/reaper/reaper_schedule_start.py +2 -2
  125. adam/commands/reaper/reaper_schedule_stop.py +2 -2
  126. adam/commands/reaper/reaper_schedules.py +2 -2
  127. adam/commands/reaper/reaper_status.py +2 -2
  128. adam/commands/reaper/utils_reaper.py +31 -5
  129. adam/commands/repair/repair_log.py +2 -2
  130. adam/commands/repair/repair_run.py +2 -2
  131. adam/commands/repair/repair_scan.py +2 -2
  132. adam/commands/repair/repair_stop.py +2 -2
  133. adam/embedded_params.py +1 -1
  134. adam/repl.py +2 -1
  135. adam/repl_commands.py +25 -10
  136. adam/repl_session.py +10 -3
  137. adam/sql/qingl.lark +58 -59
  138. adam/utils.py +48 -8
  139. adam/utils_async_job.py +73 -0
  140. adam/utils_k8s/cassandra_clusters.py +15 -7
  141. adam/utils_k8s/cassandra_nodes.py +5 -4
  142. adam/utils_k8s/pods.py +152 -51
  143. adam/version.py +1 -1
  144. {kaqing-2.0.214.dist-info → kaqing-2.0.227.dist-info}/METADATA +1 -1
  145. kaqing-2.0.227.dist-info/RECORD +280 -0
  146. kaqing-2.0.214.dist-info/RECORD +0 -272
  147. {kaqing-2.0.214.dist-info → kaqing-2.0.227.dist-info}/WHEEL +0 -0
  148. {kaqing-2.0.214.dist-info → kaqing-2.0.227.dist-info}/entry_points.txt +0 -0
  149. {kaqing-2.0.214.dist-info → kaqing-2.0.227.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,192 @@
1
+ import itertools
2
+ import os
3
+ import re
4
+
5
+ from adam.commands.devices.devices import Devices
6
+ from adam.config import Config
7
+ from adam.repl_state import ReplState
8
+ from adam.utils import Color, PodLogFile, log2, log_dir, log_to_pods, pod_log_dir, tabulize
9
+ from adam.utils_k8s.pod_exec_result import PodExecResult
10
+ from adam.utils_k8s.pods import Pods
11
+ from adam.utils_local import find_local_files
12
+ from adam.utils_async_job import AsyncJobs
13
+
14
+ def show_last_results(state: ReplState):
15
+ last_id, last_command = AsyncJobs.last_command()
16
+ logs: list[str] = find_local_files(f'{log_dir()}/{last_id}*')
17
+
18
+ # /tmp/qing-db/q/logs/16145959.repair-0.err
19
+ # /tmp/qing-db/q/logs/16145959.repair-0.log
20
+
21
+ logs_by_n: dict[str, LogLine] = {}
22
+ for l in logs:
23
+ size = str(os.path.getsize(l))
24
+ n = l[len(f'{log_dir()}/{last_id}'):]
25
+ if n.startswith('.'):
26
+ n = n[1:]
27
+
28
+ if n.endswith('.log'):
29
+ n = n[:-4]
30
+ key = 'out'
31
+ else:
32
+ n = n[:-4]
33
+ key = 'err'
34
+
35
+ n = n.split('-')[-1]
36
+
37
+ if n not in logs_by_n:
38
+ logs_by_n[n] = LogLine(n, file=l.replace('.log', '.err'))
39
+
40
+ if key == 'out':
41
+ logs_by_n[n].out = size
42
+ else:
43
+ logs_by_n[n].err = size
44
+
45
+ if last_command:
46
+ keywords = last_command.strip(' &').split(' ')
47
+ if keywords and keywords[0] == 'nodetool':
48
+ # nodetool -u cs-a7b13e29bd-superuser -pw lDed6uXQAQP72kHOYuML repair &
49
+ keywords = keywords[-1:]
50
+
51
+ for ps in find_pids_for_pod(state.pod, Devices.of(state).default_container(state), state.namespace, keywords, match_last_arg=True):
52
+ n = ps.pod.split('-')[-1]
53
+ if n in logs_by_n:
54
+ logs_by_n[n].merge(ps)
55
+
56
+ log2(f'[{last_id}] {last_command}')
57
+ log2()
58
+ tabulize(sorted(logs_by_n.keys()), fn=lambda n: logs_by_n[n].table_line(), header=LogLine.header, separator='\t')
59
+
60
+ def show_last_pod_results(state: ReplState):
61
+ last_id, last_command = AsyncJobs.last_command()
62
+
63
+ container = Devices.of(state).default_container(state)
64
+
65
+ if last_command:
66
+ keywords = last_command.strip(' &').split(' ')
67
+ if keywords and keywords[0] == 'nodetool':
68
+ # nodetool -u cs-a7b13e29bd-superuser -pw lDed6uXQAQP72kHOYuML repair &
69
+ keywords = keywords[-1:]
70
+
71
+ action = 'find-files'
72
+ msg = 'd`Running|Ran ' + action + ' onto {size} pods'
73
+ pods = Devices.of(state).pod_names(state)
74
+ with Pods.parallelize(pods, len(pods), msg=msg, action=action) as exec:
75
+ results: list[LogLine] = exec.map(lambda pod: log_line_for_pod(pod, container, state.namespace, pod_log_dir(), last_id, log_to_pods(), keywords))
76
+
77
+ log2(f'[{last_id}] {last_command}')
78
+ log2()
79
+ tabulize(sorted(results, key=lambda l: l.ordinal), fn=lambda l: l.table_line(), header=LogLine.header, separator='\t')
80
+
81
+ def log_line_for_pod(pod: str, container: str, namespace: str, dir: str, last_id: str, remote: bool, keywords: list[str]):
82
+ logs: list[PodLogFile] = Pods.find_files(pod, container, namespace, f'{dir}/{last_id}*', remote=remote)
83
+
84
+ line = LogLine()
85
+
86
+ procs = []
87
+ if keywords:
88
+ procs = find_pids_for_pod(pod, container, namespace, keywords)
89
+
90
+ for log in logs:
91
+ l = str(log)
92
+ if l.endswith('.log'):
93
+ key = 'out'
94
+ else:
95
+ key = 'err'
96
+
97
+ line.ordinal = log.pod.split('-')[-1]
98
+ line.file = l.replace('.log', '.err')
99
+
100
+ if key == 'out':
101
+ line.out = log.size
102
+ else:
103
+ line.err = log.size
104
+ for proc in procs:
105
+ line.merge(proc)
106
+
107
+ return line
108
+
109
+ def find_pids_for_cluster(state: ReplState, keywords: list[str], match_last_arg = False) -> list['ProcessInfo']:
110
+ container = Devices.of(state).default_container(state)
111
+
112
+ action = 'find-procs'
113
+ msg = 'd`Running|Ran ' + action + ' onto {size} pods'
114
+ pods = Devices.of(state).pod_names(state)
115
+ with Pods.parallelize(pods, len(pods), msg=msg, action=action) as exec:
116
+ r: list[list[ProcessInfo]] = exec.map(lambda pod: find_pids_for_pod(pod, container, state.namespace, keywords, match_last_arg=match_last_arg))
117
+
118
+ return list(itertools.chain.from_iterable(r))
119
+
120
+ def find_pids_for_pod(pod: str, container: str, namespace: str, keywords: list[str], match_last_arg = False) -> list['ProcessInfo']:
121
+ r: PodExecResult = Pods.exec(pod, container, namespace, _find_procs_command(keywords), text_color=Color.gray)
122
+
123
+ return ProcessInfo.from_find_process_results(r, last_arg = keywords[-1] if match_last_arg else None)
124
+
125
+ def _find_procs_command(keywords: list[str]):
126
+ regex_pattern = re.compile(r'[^\w\s]')
127
+
128
+ greps = []
129
+ for a in keywords:
130
+ a = a.strip('"\'').strip(' ')
131
+
132
+ if a and not regex_pattern.search(a):
133
+ greps.append(f'grep -- {a}')
134
+
135
+ awk = "awk '{ print $1, $2, $8, $NF }'"
136
+
137
+ return f"ps -ef | grep -v grep | {' | '.join(greps)} | {awk}"
138
+
139
+ class ProcessInfo:
140
+ header = 'POD\tUSER\tPID\tCMD\tLAST_ARG'
141
+
142
+ def __init__(self, user: str, pid: str, cmd: str, last_arg: str, pod: str = None):
143
+ self.user = user
144
+ self.pid = pid
145
+ self.cmd = cmd
146
+ self.last_arg = last_arg
147
+ self.pod = pod
148
+
149
+ def from_find_process_results(rs: PodExecResult, last_arg: str = None):
150
+ processes: list[ProcessInfo] = []
151
+
152
+ for l in rs.stdout.split('\n'):
153
+ l = l.strip(' \t\r\n')
154
+ if not l:
155
+ continue
156
+
157
+ tokens = l.split(' ')
158
+ if last_arg and tokens[3] != last_arg:
159
+ continue
160
+
161
+ processes.append(ProcessInfo(tokens[0], tokens[1], tokens[2], tokens[3], pod=rs.pod))
162
+
163
+ return processes
164
+
165
+ def table_line(self):
166
+ return '\t'.join([self.pod, self.user, self.pid, self.cmd, self.last_arg])
167
+
168
+ def tabulize(processes: list['ProcessInfo']):
169
+ tabulize(processes, lambda p: p.table_line(), header = ProcessInfo.header, separator='\t')
170
+
171
+ class LogLine(ProcessInfo):
172
+ header='ORDINAL\tPID\tCMD\tLAST_ARG\tOUT_SIZE\tERR_SIZE\tLOG(ERR)_FILES'
173
+
174
+ def __init__(self, ordinal: str = '-', out: str = '-', err: str = '-', file: str = '-', user: str = '-', pid: str = '-', cmd: str = '-', last_arg: str = '-', pod: str = '-'):
175
+ super().__init__(user, pid, cmd, last_arg, pod)
176
+ self.ordinal = ordinal
177
+ self.out = out
178
+ self.err = err
179
+ self.file = file
180
+
181
+ def __repr__(self):
182
+ return f"LogLine({', '.join([self.ordinal, self.pid, self.cmd, self.last_arg, self.out, self.err, self.file])})"
183
+
184
+ def table_line(self):
185
+ return '\t'.join([self.ordinal, self.pid, self.cmd, self.last_arg, self.out, self.err, self.file if self.err not in ['-', '0'] else self.file.replace('.err', '.log')])
186
+
187
+ def merge(self, process: ProcessInfo):
188
+ self.user = process.user
189
+ self.pid = process.pid
190
+ self.cmd = process.cmd
191
+ self.last_arg = process.last_arg
192
+ self.pod = process.pod
adam/commands/help.py CHANGED
@@ -28,7 +28,7 @@ class Help(Command):
28
28
 
29
29
  lines = []
30
30
  lines.append('NAVIGATION')
31
- lines.append(' a: | c: | l: | p: | x:\t switch to another operational device: App, Cassandra, Audit, Postgres or Export')
31
+ lines.append(' a: | c: | l: | p: | x:\tswitch to another operational device: App, Cassandra, Audit, Postgres or Export')
32
32
  lines.extend(section(ReplCommands.navigation()))
33
33
  lines.append('CASSANDRA')
34
34
  lines.extend(section(ReplCommands.cassandra_ops()))
@@ -52,5 +52,5 @@ class Help(Command):
52
52
  def completion(self, _: ReplState):
53
53
  return {Help.COMMAND: None}
54
54
 
55
- def help(self, _: ReplState):
55
+ def help(self, state: ReplState):
56
56
  return None
adam/commands/kubectl.py CHANGED
@@ -34,5 +34,5 @@ class Kubectl(Command):
34
34
  return super().completion(state)
35
35
 
36
36
 
37
- def help(self, _: ReplState):
38
- return f'{Kubectl.COMMAND} \t run a kubectl command'
37
+ def help(self, state: ReplState):
38
+ return super().help(state, 'run a kubectl command', args='[kubectl-args]')
@@ -50,5 +50,5 @@ class MedusaBackup(Command):
50
50
  def completion(self, state: ReplState):
51
51
  return super().completion(state)
52
52
 
53
- def help(self, _: ReplState):
54
- return f'{MedusaBackup.COMMAND}\t start a backup job'
53
+ def help(self, state: ReplState):
54
+ return super().help(state, 'start a backup job')
@@ -68,5 +68,5 @@ class MedusaRestore(Command):
68
68
  def completion(self, state: ReplState):
69
69
  return super().completion(state, lambda: {id: None for id in medusa_backup_names(state)}, auto_key='medusa.backups')
70
70
 
71
- def help(self, _: ReplState):
72
- return f'{MedusaRestore.COMMAND}\t start a restore job'
71
+ def help(self, state: ReplState):
72
+ return super().help(state, 'start a restore job')
@@ -47,5 +47,5 @@ class MedusaShowBackupJobs(Command):
47
47
  def completion(self, state: ReplState):
48
48
  return super().completion(state)
49
49
 
50
- def help(self, _: ReplState):
51
- return f'{MedusaShowBackupJobs.COMMAND}\t show Medusa backups'
50
+ def help(self, state: ReplState):
51
+ return super().help(state, 'show Medusa backups')
@@ -43,5 +43,5 @@ class MedusaShowRestoreJobs(Command):
43
43
  def completion(self, state: ReplState):
44
44
  return super().completion(state)
45
45
 
46
- def help(self, _: ReplState):
47
- return f'{MedusaShowRestoreJobs.COMMAND}\t show Medusa restores'
46
+ def help(self, state: ReplState):
47
+ return super().help(state, 'show Medusa restores')
@@ -1,13 +1,16 @@
1
+ import time
1
2
  import click
2
3
 
4
+ from adam.commands import extract_options
3
5
  from adam.commands.command import Command
4
6
  from adam.commands.command_helpers import ClusterOrPodCommandHelper
5
7
  from adam.commands.cql.utils_cql import cassandra
6
8
  from adam.commands.devices.devices import Devices
7
9
  from adam.commands.nodetool.nodetool_commands import NODETOOL_COMMANDS
10
+ from adam.commands.nodetool.utils_nodetool import abort_nodetool_tasks, find_running_nodetool_tasks
8
11
  from adam.config import Config
9
12
  from adam.repl_state import ReplState, RequiredState
10
- from adam.utils import log
13
+ from adam.utils import log, log2, tabulize
11
14
 
12
15
  class NodeTool(Command):
13
16
  COMMAND = 'nodetool'
@@ -32,16 +35,36 @@ class NodeTool(Command):
32
35
  return super().run(cmd, state)
33
36
 
34
37
  with self.validate(args, state) as (args, state):
35
- with cassandra(state) as pods:
36
- pods.nodetool(' '.join(args), status=(args[0] == 'status'))
38
+ with extract_options(args, '--force') as (args, forced):
39
+ with cassandra(state) as pods:
40
+ if subcommand := args[0]:
41
+ if subcommand in ['compact', 'repair']:
42
+ ps = find_running_nodetool_tasks(subcommand, state)
43
+ if ps:
44
+ tabulize(ps, lambda p: '\t'.join(p), header='POD\tCMD\tID/PID\tLAST_ARG\tREAPER_RUN_STATE', separator='\t')
45
+ log2()
37
46
 
38
- return state
47
+ if forced:
48
+ log2(f"* Found running instances of 'nodetool {subcommand}', aborting existing ones...")
49
+ abort_nodetool_tasks(state, subcommand, ps)
50
+
51
+ wait_duration = Config().get('nodetool.grace-period-after-abort', 10)
52
+ log2(f"* Scheduling new 'nodetool {subcommand}' in {wait_duration} secs...")
53
+ time.sleep(wait_duration)
54
+ else:
55
+ log2(f"* Found running instances of 'nodetool {subcommand}', add --force to abort existing ones.")
56
+
57
+ return state
58
+
59
+ pods.nodetool(' '.join(args), status=(args[0] == 'status'))
60
+
61
+ return state
39
62
 
40
63
  def completion(self, state: ReplState):
41
- return super().completion(state, {c: {'&': None} for c in NODETOOL_COMMANDS}, pods=Devices.of(state).pods(state, '-'))
64
+ return super().completion(state, {c: {'--force': {'&': None}, '&': None} for c in NODETOOL_COMMANDS}, pods=Devices.of(state).pods(state, '-'))
42
65
 
43
- def help(self, _: ReplState):
44
- return f'{NodeTool.COMMAND} <sub-command> [&]\t run nodetool with arguments'
66
+ def help(self, state: ReplState):
67
+ return super().help(state, 'run nodetool with arguments', args='<sub-command> [&]')
45
68
 
46
69
  class NodeToolCommandHelper(click.Command):
47
70
  def get_help(self, ctx: click.Context):
@@ -0,0 +1,44 @@
1
+ from adam.commands.devices.devices import Devices
2
+ from adam.commands.export.utils_export import state_with_pod
3
+ from adam.commands.fs.utils_fs import find_pids_for_cluster
4
+ from adam.commands.reaper.utils_reaper import reaper
5
+ from adam.config import Config
6
+ from adam.repl_state import ReplState
7
+ from adam.utils import log2
8
+
9
+ def find_running_nodetool_tasks(subcommand: str, state: ReplState) -> list[list[str]]:
10
+ lines = []
11
+ processes = find_pids_for_cluster(state, [subcommand])
12
+ for p in processes:
13
+ l = [p.pod, p.cmd, p.pid, p.last_arg]
14
+ lines.append(l)
15
+
16
+ if subcommand == 'repair':
17
+ with reaper(state) as http:
18
+ response = http.get('repair_run?state=RUNNING', params={
19
+ 'cluster_name': 'all',
20
+ 'limit': Config().get('reaper.show-runs-batch', 10)
21
+ })
22
+
23
+ runs = response.json()
24
+ if runs:
25
+ for r in runs:
26
+ l = ['reaper', 'reaper', r['id'], '', r['state']]
27
+ lines.append(l)
28
+
29
+ return lines
30
+
31
+ def abort_nodetool_tasks(state: ReplState, subcommand: str, processes: list[list[str]]):
32
+ for p in processes:
33
+ pod = p[0]
34
+ cmd = p[1]
35
+ id = p[2]
36
+
37
+ if pod == 'reaper':
38
+ with reaper(state) as http:
39
+ http.put(f'repair_run/{id}/state/ABORTED')
40
+ elif cmd == subcommand:
41
+ log2(f'@{pod} bash kill -9 {id}')
42
+
43
+ with state_with_pod(state, pod) as state1:
44
+ Devices.of(state).bash(state, state1, ['kill', '-9', id])
@@ -67,8 +67,8 @@ class Postgres(IntermediateCommand):
67
67
 
68
68
  return {}
69
69
 
70
- def help(self, _: ReplState):
71
- return f'<sql-statements> [&]\t run queries on Postgres databases'
70
+ def help(self, state: ReplState):
71
+ return super().help(state, 'run queries on Postgres databases', command='[pg] <sql-statements>')
72
72
 
73
73
  class PostgresCommandHelper(click.Command):
74
74
  def get_help(self, ctx: click.Context):
@@ -83,7 +83,4 @@ class PostgresPg(Command):
83
83
  COMMAND = 'pg'
84
84
 
85
85
  def command(self):
86
- return PostgresPg.COMMAND
87
-
88
- def help(self, _: ReplState):
89
- return f'pg <sql-statements>\t run queries on Postgres databases'
86
+ return PostgresPg.COMMAND
@@ -37,5 +37,5 @@ class PostgresLs(Command):
37
37
 
38
38
  return {}
39
39
 
40
- def help(self, _: ReplState):
41
- return f'{PostgresLs.COMMAND}\t list postgres hosts, databases or tables'
40
+ def help(self, state: ReplState):
41
+ return super().help(state, 'list postgres hosts, databases or tables')
@@ -37,5 +37,5 @@ class PostgresPreview(Command):
37
37
 
38
38
  return {}
39
39
 
40
- def help(self, _: ReplState):
41
- return f'{PostgresPreview.COMMAND}\t preview postgres table'
40
+ def help(self, state: ReplState):
41
+ return super().help(state, 'preview postgres table')
@@ -32,8 +32,7 @@ class PreviewTable(Command):
32
32
  return state
33
33
 
34
34
  def completion(self, _: ReplState):
35
- # taken care of by the sql completer
36
35
  return {}
37
36
 
38
- def help(self, _: ReplState):
39
- return f'{PreviewTable.COMMAND} TABLE\t preview table'
37
+ def help(self, state: ReplState):
38
+ return super().help(state, 'preview table', args='TABLE')
@@ -89,5 +89,5 @@ class ReaperForward(Command):
89
89
  def completion(self, state: ReplState):
90
90
  return super().completion(state)
91
91
 
92
- def help(self, _: ReplState):
93
- return f'{ReaperForward.COMMAND}\t port-forward to reaper'
92
+ def help(self, state: ReplState):
93
+ return super().help(state, 'port-forward to reaper')
@@ -39,5 +39,5 @@ class ReaperForwardStop(Command):
39
39
  def completion(self, state: ReplState):
40
40
  return super().completion(state)
41
41
 
42
- def help(self, _: ReplState):
43
- return f'{ReaperForwardStop.COMMAND}\t stop port-forward to reaper'
42
+ def help(self, state: ReplState):
43
+ return super().help(state, 'stop port-forward to reaper')
@@ -36,5 +36,5 @@ class ReaperRestart(Command):
36
36
  def completion(self, state: ReplState):
37
37
  return super().completion(state)
38
38
 
39
- def help(self, _: ReplState):
40
- return f'{ReaperRestart.COMMAND}\t restart reaper'
39
+ def help(self, state: ReplState):
40
+ return super().help(state, 'restart reaper')
@@ -36,5 +36,5 @@ class ReaperRunAbort(Command):
36
36
  def completion(self, state: ReplState):
37
37
  return super().completion(state)
38
38
 
39
- def help(self, _: ReplState):
40
- return f'{ReaperRunAbort.COMMAND} <run-id>\t abort reaper run'
39
+ def help(self, state: ReplState):
40
+ return super().help(state, 'abort reaper run', args='<run-id>')
@@ -1,8 +1,8 @@
1
1
  from adam.commands.command import Command
2
- from adam.commands.reaper.utils_reaper import reaper
2
+ from adam.commands.reaper.utils_reaper import reaper, Reapers
3
3
  from adam.config import Config
4
4
  from adam.repl_state import ReplState, RequiredState
5
- from adam.utils import convert_seconds, epoch, tabulize, log, log2
5
+ from adam.utils import convert_seconds, epoch, log2
6
6
 
7
7
  class ReaperRuns(Command):
8
8
  COMMAND = 'reaper show runs'
@@ -52,10 +52,11 @@ class ReaperRuns(Command):
52
52
  'limit': Config().get('reaper.show-runs-batch', 10)
53
53
  })
54
54
 
55
- runs = response.json()
56
- if runs:
57
- tabulize(sorted([line(run) for run in runs], reverse=True), header=header, separator=",")
58
- else:
55
+ if not Reapers.tabulize_runs(state, response):
56
+ # runs = response.json()
57
+ # if runs:
58
+ # tabulize(sorted([line(run) for run in runs], reverse=True), header=header, separator=",")
59
+ # else:
59
60
  log2('No running runs found.')
60
61
  log2()
61
62
 
@@ -64,10 +65,11 @@ class ReaperRuns(Command):
64
65
  'limit': Config().get('reaper.show-runs-batch', 10)
65
66
  })
66
67
 
67
- runs = response.json()
68
- if runs:
69
- tabulize(sorted([line(run) for run in runs], reverse=True), header=header, separator=",")
70
- else:
68
+ if not Reapers.tabulize_runs(state, response):
69
+ # runs = response.json()
70
+ # if runs:
71
+ # tabulize(sorted([line(run) for run in runs], reverse=True), header=header, separator=",")
72
+ # else:
71
73
  log2('No runs found.')
72
74
 
73
75
  return state
@@ -78,5 +80,5 @@ class ReaperRuns(Command):
78
80
 
79
81
  return {}
80
82
 
81
- def help(self, _: ReplState):
82
- return f'{ReaperRuns.COMMAND}\t show reaper runs'
83
+ def help(self, state: ReplState):
84
+ return super().help(state, 'show reaper runs')
@@ -59,5 +59,5 @@ class ReaperRunsAbort(Command):
59
59
  def completion(self, state: ReplState):
60
60
  return super().completion(state)
61
61
 
62
- def help(self, _: ReplState):
63
- return f'{ReaperRunsAbort.COMMAND}\t abort all running reaper runs'
62
+ def help(self, state: ReplState):
63
+ return super().help(state, 'abort all running reaper runs')
@@ -41,5 +41,5 @@ class ReaperScheduleActivate(Command):
41
41
  def completion(self, state: ReplState):
42
42
  return super().completion(state, lambda: {id: None for id in Reapers.cached_schedule_ids(state)}, auto_key='reaper.schedules')
43
43
 
44
- def help(self, _: ReplState):
45
- return f'{ReaperScheduleActivate.COMMAND} <schedule-id>\t resume reaper schedule'
44
+ def help(self, state: ReplState):
45
+ return super().help(state, 'resume reaper schedule', args='<schedule-id>')
@@ -36,5 +36,5 @@ class ReaperScheduleStart(Command):
36
36
  def completion(self, state: ReplState):
37
37
  return super().completion(state, lambda: {id: None for id in Reapers.cached_schedule_ids(state)}, auto_key='reaper.schedules')
38
38
 
39
- def help(self, _: ReplState):
40
- return f'{ReaperScheduleStart.COMMAND} <schedule-id>\t start reaper runs for schedule'
39
+ def help(self, state: ReplState):
40
+ return super().help(state, 'start reaper runs for schedule', args='<schedule-id>')
@@ -36,5 +36,5 @@ class ReaperScheduleStop(Command):
36
36
  def completion(self, state: ReplState):
37
37
  return super().completion(state, lambda: {id: None for id in Reapers.cached_schedule_ids(state)}, auto_key='reaper.schedules')
38
38
 
39
- def help(self, _: ReplState):
40
- return f'{ReaperScheduleStop.COMMAND} <schedule-id>\t pause reaper schedule'
39
+ def help(self, state: ReplState):
40
+ return super().help(state, 'pause reaper schedule', args='<schedule-id>')
@@ -32,5 +32,5 @@ class ReaperSchedules(Command):
32
32
  def completion(self, state: ReplState):
33
33
  return super().completion(state)
34
34
 
35
- def help(self, _: ReplState):
36
- return f'{ReaperSchedules.COMMAND}\t show reaper schedules'
35
+ def help(self, state: ReplState):
36
+ return super().help(state, 'show reaper schedules')
@@ -52,5 +52,5 @@ class ReaperStatus(Command):
52
52
  def completion(self, state: ReplState):
53
53
  return super().completion(state)
54
54
 
55
- def help(self, _: ReplState):
56
- return f'{ReaperStatus.COMMAND}\t show reaper status'
55
+ def help(self, state: ReplState):
56
+ return super().help(state, 'show reaper status')
@@ -7,7 +7,7 @@ import re
7
7
  import requests
8
8
  from adam.config import Config
9
9
  from adam.repl_state import ReplState
10
- from adam.utils import tabulize, log2, wait_log
10
+ from adam.utils import Color, convert_seconds, epoch, tabulize, log2, wait_log
11
11
  from adam.utils_k8s.k8s import port_forwarding
12
12
 
13
13
  class ReaperService:
@@ -41,7 +41,7 @@ class ReaperLogginHandler:
41
41
  self.svc.headers = Reapers.cookie_header(self.svc.state, self.svc.local_addr, self.svc.remote_addr, show_output=self.svc.show_out)
42
42
 
43
43
  if self.svc.show_out and self.method:
44
- log2(f'{self.method} {self.svc.remote_addr}/{self.path}')
44
+ log2(f'{self.method} {self.svc.remote_addr}/{self.path}', text_color=Color.gray)
45
45
 
46
46
  return (f'http://{self.svc.local_addr}/{self.path}', self.svc.headers)
47
47
 
@@ -139,12 +139,12 @@ class Reapers:
139
139
  'username':user,
140
140
  'password':pw})
141
141
  if show_output:
142
- log2(f'POST {remote_addr}/login')
143
- log2(f' username={user}&password={pw}')
142
+ log2(f'POST {remote_addr}/login', text_color=Color.gray)
143
+ log2(f' username={user}&password={pw}', text_color=Color.gray)
144
144
 
145
145
  if int(response.status_code / 100) != 2:
146
146
  if show_output:
147
- log2("login failed")
147
+ log2("login failed", text_color=Color.gray)
148
148
  return None
149
149
 
150
150
  return response.headers['Set-Cookie']
@@ -201,3 +201,29 @@ class Reapers:
201
201
  leaf = {id: None for id in ids()}
202
202
 
203
203
  return (leaf, auto == 'lazy')
204
+
205
+ def tabulize_runs(state: ReplState, response: dict) -> dict[str, any]:
206
+ header = 'ID,START,DURATION,STATE,CLUSTER,KEYSPACE,TABLES,REPAIRED'
207
+
208
+ def line(run):
209
+ id = run['id']
210
+ state = run['state']
211
+ start_time = run['start_time']
212
+ end_time = run['end_time']
213
+ duration = '-'
214
+ if state == 'DONE' and end_time:
215
+ hours, minutes, seconds = convert_seconds(epoch(end_time) - epoch(start_time))
216
+ if hours:
217
+ duration = f"{hours:2d}h {minutes:2d}m {seconds:2d}s"
218
+ elif minutes:
219
+ duration = f"{minutes:2d}m {seconds:2d}s"
220
+ else:
221
+ duration = f"{seconds:2d}s"
222
+
223
+ return f"{id},{start_time},{duration},{state},{run['cluster_name']},{run['keyspace_name']},{len(run['column_families'])},{run['segments_repaired']}/{run['total_segments']}"
224
+
225
+ runs = response.json()
226
+ if runs:
227
+ tabulize(sorted([line(run) for run in runs], reverse=True), header=header, separator=",")
228
+
229
+ return runs
@@ -33,5 +33,5 @@ class RepairLog(Command):
33
33
  def completion(self, state: ReplState):
34
34
  return super().completion(state)
35
35
 
36
- def help(self, _: ReplState):
37
- return f'{RepairLog.COMMAND}\t get repair job logs'
36
+ def help(self, state: ReplState):
37
+ return super().help(state, 'get repair job logs')
@@ -61,5 +61,5 @@ class RepairRun(Command):
61
61
  def completion(self, state: ReplState):
62
62
  return super().completion(state)
63
63
 
64
- def help(self, _: ReplState):
65
- return f'{RepairRun.COMMAND} [replace]\t start a repair job, default not replacing'
64
+ def help(self, state: ReplState):
65
+ return super().help(state, 'start a repair job, default not replacing', args='[replace]')
@@ -62,5 +62,5 @@ class RepairScan(Command):
62
62
  def completion(self, state: ReplState):
63
63
  return super().completion(state)
64
64
 
65
- def help(self, _: ReplState):
66
- return f'{RepairScan.COMMAND} [n]\t scan last n days repair log, default 7 days'
65
+ def help(self, state: ReplState):
66
+ return super().help(state, 'scan last n days repair log, default 7 days')