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.
- adam/checks/compactionstats.py +2 -1
- adam/checks/cpu.py +2 -1
- adam/checks/disk.py +6 -5
- adam/checks/gossip.py +2 -1
- adam/checks/memory.py +2 -1
- adam/checks/status.py +2 -1
- adam/commands/app/app.py +2 -2
- adam/commands/app/app_ping.py +2 -2
- adam/commands/app/login.py +2 -2
- adam/commands/app/show_app_actions.py +2 -2
- adam/commands/app/show_app_id.py +2 -2
- adam/commands/app/show_app_queues.py +2 -2
- adam/commands/app/show_login.py +2 -2
- adam/commands/audit/audit.py +3 -5
- adam/commands/audit/audit_repair_tables.py +2 -2
- adam/commands/audit/audit_run.py +2 -2
- adam/commands/audit/show_last10.py +2 -2
- adam/commands/audit/show_slow10.py +2 -2
- adam/commands/audit/show_top10.py +2 -2
- adam/commands/bash/bash.py +2 -2
- adam/commands/cassandra/download_cassandra_log.py +2 -2
- adam/commands/cassandra/restart_cluster.py +2 -2
- adam/commands/cassandra/restart_node.py +2 -2
- adam/commands/cassandra/restart_nodes.py +2 -2
- adam/commands/cassandra/rollout.py +2 -2
- adam/commands/cassandra/show_cassandra_repairs.py +2 -2
- adam/commands/cassandra/show_cassandra_status.py +2 -2
- adam/commands/cassandra/show_cassandra_version.py +2 -2
- adam/commands/cassandra/show_processes.py +6 -6
- adam/commands/cassandra/show_storage.py +2 -2
- adam/commands/cassandra/watch.py +2 -2
- adam/commands/cli/clipboard_copy.py +2 -2
- adam/commands/cli/show_cli_commands.py +3 -3
- adam/commands/code.py +2 -2
- adam/commands/command.py +32 -5
- adam/commands/config/param_get.py +2 -2
- adam/commands/config/param_set.py +2 -2
- adam/commands/config/show_params.py +2 -2
- adam/commands/cql/alter_tables.py +2 -2
- adam/commands/cql/cqlsh.py +2 -2
- adam/commands/cql/utils_cql.py +13 -3
- adam/commands/debug/debug_completes.py +2 -2
- adam/commands/debug/debug_timings.py +2 -2
- adam/commands/debug/show_offloaded_completes.py +2 -2
- adam/commands/deploy/code_start.py +2 -2
- adam/commands/deploy/code_stop.py +2 -2
- adam/commands/deploy/deploy_frontend.py +2 -2
- adam/commands/deploy/deploy_pg_agent.py +2 -2
- adam/commands/deploy/deploy_pod.py +2 -2
- adam/commands/deploy/undeploy_frontend.py +2 -2
- adam/commands/deploy/undeploy_pg_agent.py +2 -2
- adam/commands/deploy/undeploy_pod.py +2 -2
- adam/commands/devices/device.py +7 -7
- adam/commands/devices/device_app.py +6 -6
- adam/commands/devices/device_auit_log.py +2 -2
- adam/commands/devices/device_cass.py +6 -6
- adam/commands/devices/device_export.py +2 -2
- adam/commands/devices/device_postgres.py +6 -6
- adam/commands/diag/check.py +2 -2
- adam/commands/diag/generate_report.py +2 -2
- adam/commands/diag/issues.py +3 -2
- adam/commands/exit.py +2 -2
- adam/commands/export/clean_up_all_export_sessions.py +2 -2
- adam/commands/export/clean_up_export_sessions.py +2 -2
- adam/commands/export/download_export_session.py +4 -5
- adam/commands/export/drop_export_database.py +2 -2
- adam/commands/export/drop_export_databases.py +2 -2
- adam/commands/export/export.py +3 -3
- adam/commands/export/export_databases.py +3 -0
- adam/commands/export/export_select.py +2 -2
- adam/commands/export/export_sessions.py +10 -9
- adam/commands/export/export_use.py +3 -3
- adam/commands/export/export_x_select.py +2 -2
- adam/commands/export/exporter.py +11 -11
- adam/commands/export/import_files.py +3 -7
- adam/commands/export/import_session.py +2 -2
- adam/commands/export/importer.py +6 -7
- adam/commands/export/show_column_counts.py +2 -3
- adam/commands/export/show_export_databases.py +3 -4
- adam/commands/export/show_export_session.py +4 -4
- adam/commands/export/show_export_sessions.py +3 -3
- adam/commands/export/utils_export.py +25 -33
- adam/commands/fs/cat.py +2 -2
- adam/commands/fs/cat_local.py +2 -2
- adam/commands/fs/cd.py +2 -2
- adam/commands/fs/download_file.py +2 -2
- adam/commands/fs/find_files.py +3 -3
- adam/commands/fs/find_processes.py +12 -20
- adam/commands/fs/head.py +4 -4
- adam/commands/fs/head_local.py +46 -0
- adam/commands/fs/ls.py +2 -2
- adam/commands/fs/ls_local.py +2 -2
- adam/commands/fs/pwd.py +2 -2
- adam/commands/fs/rm.py +2 -2
- adam/commands/fs/rm_downloads.py +2 -2
- adam/commands/fs/rm_logs.py +13 -7
- adam/commands/fs/rm_logs_local.py +38 -0
- adam/commands/fs/shell.py +2 -2
- adam/commands/fs/show_adam.py +2 -2
- adam/commands/fs/show_host.py +2 -2
- adam/commands/fs/show_last_results.py +39 -0
- adam/commands/fs/tail.py +36 -0
- adam/commands/fs/tail_local.py +46 -0
- adam/commands/fs/utils_fs.py +192 -0
- adam/commands/help.py +2 -2
- adam/commands/kubectl.py +2 -2
- adam/commands/medusa/medusa_backup.py +2 -2
- adam/commands/medusa/medusa_restore.py +2 -2
- adam/commands/medusa/medusa_show_backupjobs.py +2 -2
- adam/commands/medusa/medusa_show_restorejobs.py +2 -2
- adam/commands/nodetool/nodetool.py +30 -7
- adam/commands/nodetool/utils_nodetool.py +44 -0
- adam/commands/postgres/postgres.py +3 -6
- adam/commands/postgres/postgres_ls.py +2 -2
- adam/commands/postgres/postgres_preview.py +2 -2
- adam/commands/preview_table.py +2 -3
- adam/commands/reaper/reaper_forward.py +2 -2
- adam/commands/reaper/reaper_forward_stop.py +2 -2
- adam/commands/reaper/reaper_restart.py +2 -2
- adam/commands/reaper/reaper_run_abort.py +2 -2
- adam/commands/reaper/reaper_runs.py +14 -12
- adam/commands/reaper/reaper_runs_abort.py +2 -2
- adam/commands/reaper/reaper_schedule_activate.py +2 -2
- adam/commands/reaper/reaper_schedule_start.py +2 -2
- adam/commands/reaper/reaper_schedule_stop.py +2 -2
- adam/commands/reaper/reaper_schedules.py +2 -2
- adam/commands/reaper/reaper_status.py +2 -2
- adam/commands/reaper/utils_reaper.py +31 -5
- adam/commands/repair/repair_log.py +2 -2
- adam/commands/repair/repair_run.py +2 -2
- adam/commands/repair/repair_scan.py +2 -2
- adam/commands/repair/repair_stop.py +2 -2
- adam/embedded_params.py +1 -1
- adam/repl.py +2 -1
- adam/repl_commands.py +25 -10
- adam/repl_session.py +10 -3
- adam/sql/qingl.lark +58 -59
- adam/utils.py +48 -8
- adam/utils_async_job.py +73 -0
- adam/utils_k8s/cassandra_clusters.py +15 -7
- adam/utils_k8s/cassandra_nodes.py +5 -4
- adam/utils_k8s/pods.py +152 -51
- adam/version.py +1 -1
- {kaqing-2.0.214.dist-info → kaqing-2.0.227.dist-info}/METADATA +1 -1
- kaqing-2.0.227.dist-info/RECORD +280 -0
- kaqing-2.0.214.dist-info/RECORD +0 -272
- {kaqing-2.0.214.dist-info → kaqing-2.0.227.dist-info}/WHEEL +0 -0
- {kaqing-2.0.214.dist-info → kaqing-2.0.227.dist-info}/entry_points.txt +0 -0
- {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:\
|
|
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,
|
|
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,
|
|
38
|
-
return
|
|
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,
|
|
54
|
-
return
|
|
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,
|
|
72
|
-
return
|
|
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,
|
|
51
|
-
return
|
|
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,
|
|
47
|
-
return
|
|
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
|
|
36
|
-
|
|
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
|
-
|
|
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,
|
|
44
|
-
return
|
|
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,
|
|
71
|
-
return
|
|
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,
|
|
41
|
-
return
|
|
40
|
+
def help(self, state: ReplState):
|
|
41
|
+
return super().help(state, 'list postgres hosts, databases or tables')
|
adam/commands/preview_table.py
CHANGED
|
@@ -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,
|
|
39
|
-
return
|
|
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,
|
|
93
|
-
return
|
|
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,
|
|
43
|
-
return
|
|
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,
|
|
40
|
-
return
|
|
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,
|
|
40
|
-
return
|
|
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,
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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,
|
|
82
|
-
return
|
|
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,
|
|
63
|
-
return
|
|
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,
|
|
45
|
-
return
|
|
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,
|
|
40
|
-
return
|
|
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,
|
|
40
|
-
return
|
|
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,
|
|
36
|
-
return
|
|
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,
|
|
56
|
-
return
|
|
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'
|
|
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,
|
|
37
|
-
return
|
|
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,
|
|
65
|
-
return
|
|
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,
|
|
66
|
-
return
|
|
65
|
+
def help(self, state: ReplState):
|
|
66
|
+
return super().help(state, 'scan last n days repair log, default 7 days')
|