kaqing 2.0.110__py3-none-any.whl → 2.0.184__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/__init__.py +0 -2
- adam/app_session.py +9 -12
- adam/apps.py +18 -4
- adam/batch.py +5 -5
- adam/checks/check_utils.py +16 -46
- adam/checks/cpu.py +7 -1
- adam/checks/cpu_metrics.py +52 -0
- adam/checks/disk.py +2 -3
- adam/columns/columns.py +3 -1
- adam/columns/cpu.py +3 -1
- adam/columns/cpu_metrics.py +22 -0
- adam/columns/memory.py +3 -4
- adam/commands/__init__.py +24 -0
- adam/commands/alter_tables.py +33 -48
- adam/commands/app/__init__.py +0 -0
- adam/commands/app/app.py +38 -0
- adam/commands/{app_ping.py → app/app_ping.py} +7 -13
- adam/commands/app/show_app_actions.py +49 -0
- adam/commands/{show → app}/show_app_id.py +8 -11
- adam/commands/{show → app}/show_app_queues.py +7 -14
- adam/commands/app/utils_app.py +98 -0
- adam/commands/audit/audit.py +27 -31
- adam/commands/audit/audit_repair_tables.py +14 -18
- adam/commands/audit/audit_run.py +16 -23
- adam/commands/audit/show_last10.py +4 -17
- adam/commands/audit/show_slow10.py +4 -17
- adam/commands/audit/show_top10.py +4 -16
- adam/commands/audit/utils_show_top10.py +15 -3
- adam/commands/bash/__init__.py +5 -0
- adam/commands/bash/bash.py +36 -0
- adam/commands/bash/bash_completer.py +93 -0
- adam/commands/bash/utils_bash.py +16 -0
- adam/commands/cat.py +36 -0
- adam/commands/cd.py +11 -95
- adam/commands/check.py +15 -24
- adam/commands/cli_commands.py +2 -3
- adam/commands/clipboard_copy.py +86 -0
- adam/commands/code.py +57 -0
- adam/commands/command.py +198 -40
- adam/commands/commands_utils.py +12 -27
- adam/commands/cql/cql_completions.py +27 -10
- adam/commands/cql/cqlsh.py +12 -30
- adam/commands/cql/utils_cql.py +297 -0
- adam/commands/deploy/code_start.py +7 -10
- adam/commands/deploy/code_stop.py +4 -21
- adam/commands/deploy/code_utils.py +3 -3
- adam/commands/deploy/deploy.py +4 -27
- adam/commands/deploy/deploy_frontend.py +14 -17
- adam/commands/deploy/deploy_pg_agent.py +3 -6
- adam/commands/deploy/deploy_pod.py +65 -73
- adam/commands/deploy/deploy_utils.py +14 -24
- adam/commands/deploy/undeploy.py +4 -27
- adam/commands/deploy/undeploy_frontend.py +4 -7
- adam/commands/deploy/undeploy_pg_agent.py +6 -8
- adam/commands/deploy/undeploy_pod.py +11 -12
- adam/commands/devices/__init__.py +0 -0
- adam/commands/devices/device.py +123 -0
- adam/commands/devices/device_app.py +163 -0
- adam/commands/devices/device_auit_log.py +49 -0
- adam/commands/devices/device_cass.py +179 -0
- adam/commands/devices/device_export.py +84 -0
- adam/commands/devices/device_postgres.py +150 -0
- adam/commands/devices/devices.py +25 -0
- adam/commands/download_file.py +47 -0
- adam/commands/exit.py +1 -4
- adam/commands/export/__init__.py +0 -0
- adam/commands/export/clean_up_all_export_sessions.py +37 -0
- adam/commands/export/clean_up_export_sessions.py +39 -0
- adam/commands/export/download_export_session.py +39 -0
- adam/commands/export/drop_export_database.py +39 -0
- adam/commands/export/drop_export_databases.py +37 -0
- adam/commands/export/export.py +53 -0
- adam/commands/export/export_databases.py +245 -0
- adam/commands/export/export_select.py +59 -0
- adam/commands/export/export_select_x.py +54 -0
- adam/commands/export/export_sessions.py +209 -0
- adam/commands/export/export_use.py +49 -0
- adam/commands/export/exporter.py +332 -0
- adam/commands/export/import_files.py +44 -0
- adam/commands/export/import_session.py +44 -0
- adam/commands/export/importer.py +81 -0
- adam/commands/export/importer_athena.py +177 -0
- adam/commands/export/importer_sqlite.py +67 -0
- adam/commands/export/show_column_counts.py +45 -0
- adam/commands/export/show_export_databases.py +38 -0
- adam/commands/export/show_export_session.py +39 -0
- adam/commands/export/show_export_sessions.py +37 -0
- adam/commands/export/utils_export.py +343 -0
- adam/commands/find_files.py +51 -0
- adam/commands/find_processes.py +76 -0
- adam/commands/head.py +36 -0
- adam/commands/help.py +5 -3
- adam/commands/intermediate_command.py +49 -0
- adam/commands/issues.py +11 -43
- adam/commands/kubectl.py +38 -0
- adam/commands/login.py +22 -24
- adam/commands/logs.py +3 -6
- adam/commands/ls.py +11 -116
- adam/commands/medusa/medusa.py +4 -22
- adam/commands/medusa/medusa_backup.py +20 -27
- adam/commands/medusa/medusa_restore.py +38 -37
- adam/commands/medusa/medusa_show_backupjobs.py +16 -18
- adam/commands/medusa/medusa_show_restorejobs.py +13 -18
- adam/commands/nodetool.py +11 -17
- adam/commands/param_get.py +11 -14
- adam/commands/param_set.py +8 -12
- adam/commands/postgres/postgres.py +45 -46
- adam/commands/postgres/postgres_databases.py +269 -0
- adam/commands/postgres/postgres_ls.py +4 -8
- adam/commands/postgres/postgres_preview.py +5 -9
- adam/commands/postgres/psql_completions.py +4 -3
- adam/commands/postgres/utils_postgres.py +70 -0
- adam/commands/preview_table.py +8 -44
- adam/commands/pwd.py +14 -46
- adam/commands/reaper/reaper.py +4 -27
- adam/commands/reaper/reaper_forward.py +49 -56
- adam/commands/reaper/reaper_forward_session.py +6 -0
- adam/commands/reaper/reaper_forward_stop.py +10 -16
- adam/commands/reaper/reaper_restart.py +7 -14
- adam/commands/reaper/reaper_run_abort.py +8 -33
- adam/commands/reaper/reaper_runs.py +43 -58
- adam/commands/reaper/reaper_runs_abort.py +29 -49
- adam/commands/reaper/reaper_schedule_activate.py +9 -32
- adam/commands/reaper/reaper_schedule_start.py +9 -32
- adam/commands/reaper/reaper_schedule_stop.py +9 -32
- adam/commands/reaper/reaper_schedules.py +4 -14
- adam/commands/reaper/reaper_status.py +8 -16
- adam/commands/reaper/utils_reaper.py +194 -0
- adam/commands/repair/repair.py +4 -22
- adam/commands/repair/repair_log.py +5 -11
- adam/commands/repair/repair_run.py +27 -34
- adam/commands/repair/repair_scan.py +32 -38
- adam/commands/repair/repair_stop.py +5 -11
- adam/commands/report.py +27 -29
- adam/commands/restart.py +25 -26
- adam/commands/rollout.py +19 -24
- adam/commands/shell.py +12 -4
- adam/commands/show/show.py +10 -25
- adam/commands/show/show_adam.py +3 -3
- adam/commands/show/show_cassandra_repairs.py +35 -0
- adam/commands/show/show_cassandra_status.py +33 -51
- adam/commands/show/show_cassandra_version.py +5 -18
- adam/commands/show/show_commands.py +20 -25
- adam/commands/show/show_host.py +1 -1
- adam/commands/show/show_login.py +20 -27
- adam/commands/show/show_params.py +2 -5
- adam/commands/show/show_processes.py +15 -19
- adam/commands/show/show_storage.py +10 -20
- adam/commands/watch.py +26 -29
- adam/config.py +5 -14
- adam/embedded_params.py +1 -1
- adam/log.py +4 -4
- adam/pod_exec_result.py +6 -3
- adam/repl.py +69 -115
- adam/repl_commands.py +52 -19
- adam/repl_state.py +161 -40
- adam/sql/sql_completer.py +52 -27
- adam/sql/sql_state_machine.py +131 -19
- adam/sso/authn_ad.py +6 -8
- adam/sso/authn_okta.py +4 -6
- adam/sso/cred_cache.py +3 -5
- adam/sso/idp.py +9 -12
- adam/utils.py +511 -9
- adam/utils_athena.py +145 -0
- adam/utils_audits.py +12 -103
- adam/utils_issues.py +32 -0
- adam/utils_k8s/app_clusters.py +28 -0
- adam/utils_k8s/app_pods.py +36 -0
- adam/utils_k8s/cassandra_clusters.py +30 -19
- adam/utils_k8s/cassandra_nodes.py +3 -3
- adam/utils_k8s/custom_resources.py +16 -17
- adam/utils_k8s/ingresses.py +2 -2
- adam/utils_k8s/jobs.py +7 -11
- adam/utils_k8s/k8s.py +87 -0
- adam/utils_k8s/kube_context.py +2 -2
- adam/utils_k8s/pods.py +89 -78
- adam/utils_k8s/secrets.py +4 -4
- adam/utils_k8s/service_accounts.py +5 -4
- adam/utils_k8s/services.py +2 -2
- adam/utils_k8s/statefulsets.py +1 -12
- adam/utils_local.py +4 -0
- adam/utils_net.py +4 -4
- adam/utils_repl/__init__.py +0 -0
- adam/utils_repl/automata_completer.py +48 -0
- adam/utils_repl/repl_completer.py +46 -0
- adam/utils_repl/state_machine.py +173 -0
- adam/utils_sqlite.py +137 -0
- adam/version.py +1 -1
- {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/METADATA +1 -1
- kaqing-2.0.184.dist-info/RECORD +244 -0
- adam/commands/app.py +0 -67
- adam/commands/bash.py +0 -150
- adam/commands/cp.py +0 -95
- adam/commands/cql/cql_utils.py +0 -112
- adam/commands/devices.py +0 -118
- adam/commands/postgres/postgres_context.py +0 -239
- adam/commands/postgres/postgres_utils.py +0 -31
- adam/commands/reaper/reaper_session.py +0 -159
- adam/commands/show/show_app_actions.py +0 -56
- adam/commands/show/show_repairs.py +0 -47
- kaqing-2.0.110.dist-info/RECORD +0 -187
- {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/WHEEL +0 -0
- {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/entry_points.txt +0 -0
- {kaqing-2.0.110.dist-info → kaqing-2.0.184.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
|
|
3
1
|
from adam.commands.command import Command
|
|
4
|
-
from .
|
|
2
|
+
from adam.commands.reaper.utils_reaper import reaper
|
|
5
3
|
from adam.config import Config
|
|
6
4
|
from adam.repl_state import ReplState, RequiredState
|
|
7
5
|
from adam.utils import log2
|
|
8
6
|
|
|
9
7
|
class ReaperRunsAbort(Command):
|
|
10
8
|
COMMAND = 'reaper abort runs'
|
|
11
|
-
reaper_login = None
|
|
12
9
|
|
|
13
10
|
# the singleton pattern
|
|
14
11
|
def __new__(cls, *args, **kwargs):
|
|
@@ -29,55 +26,38 @@ class ReaperRunsAbort(Command):
|
|
|
29
26
|
if not(args := self.args(cmd)):
|
|
30
27
|
return super().run(cmd, state)
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
with self.validate(args, state) as (args, state):
|
|
30
|
+
with reaper(state) as http:
|
|
31
|
+
# PAUSED, RUNNING, ABORTED
|
|
32
|
+
aborted = 0
|
|
33
|
+
|
|
34
|
+
while True == True:
|
|
35
|
+
response = http.get('repair_run?state=RUNNING', params={
|
|
36
|
+
'cluster_name': 'all',
|
|
37
|
+
'limit': Config().get('reaper.abort-runs-batch', 10)
|
|
38
|
+
})
|
|
39
|
+
if not response:
|
|
40
|
+
break
|
|
41
|
+
|
|
42
|
+
runs = response.json()
|
|
43
|
+
if not runs:
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
for run in runs:
|
|
47
|
+
run_id = run['id']
|
|
48
|
+
# PUT /repair_run/{id}/state/{state}
|
|
49
|
+
http.put(f'repair_run/{run_id}/state/ABORTED')
|
|
50
|
+
aborted += 1
|
|
51
|
+
|
|
52
|
+
if aborted:
|
|
53
|
+
log2(f'Aborted {aborted} runs in total.')
|
|
54
|
+
else:
|
|
55
|
+
log2('No running repair runs found.')
|
|
35
56
|
|
|
36
|
-
if not(reaper := ReaperSession.create(state)):
|
|
37
57
|
return state
|
|
38
58
|
|
|
39
|
-
self.stop_runs(state, reaper)
|
|
40
|
-
|
|
41
|
-
return state
|
|
42
|
-
|
|
43
|
-
def stop_runs(self, state: ReplState, reaper: ReaperSession):
|
|
44
|
-
def body_list(uri: str, headers: dict[str, str]):
|
|
45
|
-
return requests.get(uri, headers=headers, params={
|
|
46
|
-
'cluster_name': 'all',
|
|
47
|
-
'limit': Config().get('reaper.abort-runs-batch', 10)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
def body_abort(uri: str, headers: dict[str, str]):
|
|
51
|
-
return requests.put(uri, headers=headers)
|
|
52
|
-
|
|
53
|
-
# PAUSED, RUNNING, ABORTED
|
|
54
|
-
aborted = 0
|
|
55
|
-
while True == True:
|
|
56
|
-
response = reaper.port_forwarded(state, 'repair_run?state=RUNNING', body_list, method='GET')
|
|
57
|
-
if not response:
|
|
58
|
-
break
|
|
59
|
-
|
|
60
|
-
runs = response.json()
|
|
61
|
-
if not runs:
|
|
62
|
-
break
|
|
63
|
-
|
|
64
|
-
for run in runs:
|
|
65
|
-
run_id = run['id']
|
|
66
|
-
# PUT /repair_run/{id}/state/{state}
|
|
67
|
-
reaper.port_forwarded(state, f'repair_run/{run_id}/state/ABORTED', body_abort, method='PUT')
|
|
68
|
-
log2(f'Aborted {len(runs)} runs.')
|
|
69
|
-
aborted += 1
|
|
70
|
-
|
|
71
|
-
if aborted:
|
|
72
|
-
log2(f'Aborted {aborted} runs in total.')
|
|
73
|
-
else:
|
|
74
|
-
log2('No running repair runs found.')
|
|
75
|
-
|
|
76
59
|
def completion(self, state: ReplState):
|
|
77
|
-
|
|
78
|
-
return super().completion(state)
|
|
79
|
-
|
|
80
|
-
return {}
|
|
60
|
+
return super().completion(state)
|
|
81
61
|
|
|
82
62
|
def help(self, _: ReplState):
|
|
83
63
|
return f'{ReaperRunsAbort.COMMAND}\t abort all running reaper runs'
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from adam.commands import validate_args
|
|
3
2
|
from adam.commands.command import Command
|
|
4
|
-
from .
|
|
3
|
+
from adam.commands.reaper.utils_reaper import Reapers, reaper
|
|
5
4
|
from adam.repl_state import ReplState, RequiredState
|
|
6
5
|
from adam.utils import log2
|
|
7
6
|
|
|
8
7
|
class ReaperScheduleActivate(Command):
|
|
9
8
|
COMMAND = 'reaper activate schedule'
|
|
10
|
-
reaper_login = None
|
|
11
9
|
|
|
12
10
|
# the singleton pattern
|
|
13
11
|
def __new__(cls, *args, **kwargs):
|
|
@@ -28,37 +26,16 @@ class ReaperScheduleActivate(Command):
|
|
|
28
26
|
if not(args := self.args(cmd)):
|
|
29
27
|
return super().run(cmd, state)
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
log2('Specify schedule to activate.')
|
|
37
|
-
|
|
38
|
-
return state
|
|
39
|
-
|
|
40
|
-
schedule_id = args[0]
|
|
41
|
-
if not(reaper := ReaperSession.create(state)):
|
|
42
|
-
return state
|
|
43
|
-
|
|
44
|
-
self.activate_schedule(state, reaper, schedule_id)
|
|
29
|
+
with self.validate(args, state) as (args, state):
|
|
30
|
+
with validate_args(args, state, name='schedule') as schedule_id:
|
|
31
|
+
with reaper(state) as http:
|
|
32
|
+
http.put(f'repair_schedule/{schedule_id}?state=ACTIVE')
|
|
33
|
+
Reapers.show_schedule(state, schedule_id)
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def activate_schedule(self, state: ReplState, reaper: ReaperSession, schedule_id: str):
|
|
49
|
-
def body(uri: str, headers: dict[str, str]):
|
|
50
|
-
return requests.put(uri, headers=headers)
|
|
51
|
-
|
|
52
|
-
reaper.port_forwarded(state, f'repair_schedule/{schedule_id}?state=ACTIVE', body, method='PUT')
|
|
53
|
-
reaper.show_schedule(state, schedule_id)
|
|
35
|
+
return schedule_id
|
|
54
36
|
|
|
55
37
|
def completion(self, state: ReplState):
|
|
56
|
-
|
|
57
|
-
leaf = {id: None for id in ReaperSession.cached_schedule_ids(state)}
|
|
58
|
-
|
|
59
|
-
return super().completion(state, leaf)
|
|
60
|
-
|
|
61
|
-
return {}
|
|
38
|
+
return super().completion(state, lambda: {id: None for id in Reapers.cached_schedule_ids(state)})
|
|
62
39
|
|
|
63
40
|
def help(self, _: ReplState):
|
|
64
41
|
return f'{ReaperScheduleActivate.COMMAND} <schedule-id>\t resume reaper schedule'
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from adam.commands import validate_args
|
|
3
2
|
from adam.commands.command import Command
|
|
4
|
-
from .
|
|
3
|
+
from adam.commands.reaper.utils_reaper import Reapers, reaper
|
|
5
4
|
from adam.repl_state import ReplState, RequiredState
|
|
6
5
|
from adam.utils import log2
|
|
7
6
|
|
|
8
7
|
class ReaperScheduleStart(Command):
|
|
9
8
|
COMMAND = 'reaper start schedule'
|
|
10
|
-
reaper_login = None
|
|
11
9
|
|
|
12
10
|
# the singleton pattern
|
|
13
11
|
def __new__(cls, *args, **kwargs):
|
|
@@ -28,37 +26,16 @@ class ReaperScheduleStart(Command):
|
|
|
28
26
|
if not(args := self.args(cmd)):
|
|
29
27
|
return super().run(cmd, state)
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
log2('Specify schedule to activate.')
|
|
37
|
-
|
|
38
|
-
return state
|
|
39
|
-
|
|
40
|
-
schedule_id = args[0]
|
|
41
|
-
if not(reaper := ReaperSession.create(state)):
|
|
42
|
-
return schedule_id
|
|
43
|
-
|
|
44
|
-
self.start_schedule(state, reaper, schedule_id)
|
|
29
|
+
with self.validate(args, state) as (args, state):
|
|
30
|
+
with validate_args(args, state, name='schedule') as schedule_id:
|
|
31
|
+
with reaper(state) as http:
|
|
32
|
+
http.post(f'repair_schedule/start/{schedule_id}')
|
|
33
|
+
Reapers.show_schedule(state, schedule_id)
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def start_schedule(self, state: ReplState, reaper: ReaperSession, schedule_id: str):
|
|
49
|
-
def body(uri: str, headers: dict[str, str]):
|
|
50
|
-
return requests.post(uri, headers=headers)
|
|
51
|
-
|
|
52
|
-
reaper.port_forwarded(state, f'repair_schedule/start/{schedule_id}', body, method='POST')
|
|
53
|
-
reaper.show_schedule(state, schedule_id)
|
|
35
|
+
return schedule_id
|
|
54
36
|
|
|
55
37
|
def completion(self, state: ReplState):
|
|
56
|
-
|
|
57
|
-
leaf = {id: None for id in ReaperSession.cached_schedule_ids(state)}
|
|
58
|
-
|
|
59
|
-
return super().completion(state, leaf)
|
|
60
|
-
|
|
61
|
-
return {}
|
|
38
|
+
return super().completion(state, lambda: {id: None for id in Reapers.cached_schedule_ids(state)})
|
|
62
39
|
|
|
63
40
|
def help(self, _: ReplState):
|
|
64
41
|
return f'{ReaperScheduleStart.COMMAND} <schedule-id>\t start reaper runs for schedule'
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from adam.commands import validate_args
|
|
3
2
|
from adam.commands.command import Command
|
|
4
|
-
from .
|
|
3
|
+
from adam.commands.reaper.utils_reaper import Reapers, reaper
|
|
5
4
|
from adam.repl_state import ReplState, RequiredState
|
|
6
5
|
from adam.utils import log2
|
|
7
6
|
|
|
8
7
|
class ReaperScheduleStop(Command):
|
|
9
8
|
COMMAND = 'reaper stop schedule'
|
|
10
|
-
reaper_login = None
|
|
11
9
|
|
|
12
10
|
# the singleton pattern
|
|
13
11
|
def __new__(cls, *args, **kwargs):
|
|
@@ -28,37 +26,16 @@ class ReaperScheduleStop(Command):
|
|
|
28
26
|
if not(args := self.args(cmd)):
|
|
29
27
|
return super().run(cmd, state)
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
log2('Specify run schedule to stop.')
|
|
37
|
-
|
|
38
|
-
return state
|
|
39
|
-
|
|
40
|
-
schedule_id = args[0]
|
|
41
|
-
if not(reaper := ReaperSession.create(state)):
|
|
42
|
-
return schedule_id
|
|
43
|
-
|
|
44
|
-
self.stop_schedule(state, reaper, schedule_id)
|
|
29
|
+
with self.validate(args, state) as (args, state):
|
|
30
|
+
with validate_args(args, state, name='schedule') as schedule_id:
|
|
31
|
+
with reaper(state) as http:
|
|
32
|
+
http.put(f'repair_schedule/{schedule_id}?state=PAUSED')
|
|
33
|
+
Reapers.show_schedule(state, schedule_id)
|
|
45
34
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def stop_schedule(self, state: ReplState, reaper: ReaperSession, schedule_id: str):
|
|
49
|
-
def body(uri: str, headers: dict[str, str]):
|
|
50
|
-
return requests.put(uri, headers=headers)
|
|
51
|
-
|
|
52
|
-
reaper.port_forwarded(state, f'repair_schedule/{schedule_id}?state=PAUSED', body, method='PUT')
|
|
53
|
-
reaper.show_schedule(state, schedule_id)
|
|
35
|
+
return schedule_id
|
|
54
36
|
|
|
55
37
|
def completion(self, state: ReplState):
|
|
56
|
-
|
|
57
|
-
leaf = {id: None for id in ReaperSession.cached_schedule_ids(state)}
|
|
58
|
-
|
|
59
|
-
return super().completion(state, leaf)
|
|
60
|
-
|
|
61
|
-
return {}
|
|
38
|
+
return super().completion(state, lambda: {id: None for id in Reapers.cached_schedule_ids(state)})
|
|
62
39
|
|
|
63
40
|
def help(self, _: ReplState):
|
|
64
41
|
return f'{ReaperScheduleStop.COMMAND} <schedule-id>\t pause reaper schedule'
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from adam.commands.command import Command
|
|
2
|
-
from .
|
|
2
|
+
from adam.commands.reaper.utils_reaper import Reapers
|
|
3
3
|
from adam.repl_state import ReplState, RequiredState
|
|
4
4
|
|
|
5
5
|
class ReaperSchedules(Command):
|
|
6
6
|
COMMAND = 'reaper show schedules'
|
|
7
|
-
reaper_login = None
|
|
8
7
|
|
|
9
8
|
# the singleton pattern
|
|
10
9
|
def __new__(cls, *args, **kwargs):
|
|
@@ -25,22 +24,13 @@ class ReaperSchedules(Command):
|
|
|
25
24
|
if not(args := self.args(cmd)):
|
|
26
25
|
return super().run(cmd, state)
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return state
|
|
27
|
+
with self.validate(args, state) as (args, state):
|
|
28
|
+
Reapers.show_schedules(state)
|
|
31
29
|
|
|
32
|
-
if not(reaper := ReaperSession.create(state)):
|
|
33
30
|
return state
|
|
34
31
|
|
|
35
|
-
reaper.show_schedules(state)
|
|
36
|
-
|
|
37
|
-
return state
|
|
38
|
-
|
|
39
32
|
def completion(self, state: ReplState):
|
|
40
|
-
|
|
41
|
-
return super().completion(state)
|
|
42
|
-
|
|
43
|
-
return {}
|
|
33
|
+
return super().completion(state)
|
|
44
34
|
|
|
45
35
|
def help(self, _: ReplState):
|
|
46
36
|
return f'{ReaperSchedules.COMMAND}\t show reaper schedules'
|
|
@@ -4,13 +4,11 @@ from kubernetes import client
|
|
|
4
4
|
|
|
5
5
|
from adam.commands.command import Command
|
|
6
6
|
from adam.commands.commands_utils import show_pods
|
|
7
|
-
from .
|
|
7
|
+
from adam.commands.reaper.utils_reaper import Reapers
|
|
8
8
|
from adam.repl_state import ReplState, RequiredState
|
|
9
|
-
from adam.utils import lines_to_tabular, log, log2
|
|
10
9
|
|
|
11
10
|
class ReaperStatus(Command):
|
|
12
11
|
COMMAND = 'reaper status'
|
|
13
|
-
reaper_login = None
|
|
14
12
|
|
|
15
13
|
# the singleton pattern
|
|
16
14
|
def __new__(cls, *args, **kwargs):
|
|
@@ -31,18 +29,15 @@ class ReaperStatus(Command):
|
|
|
31
29
|
if not(args := self.args(cmd)):
|
|
32
30
|
return super().run(cmd, state)
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if not(reaper := ReaperSession.create(state)):
|
|
39
|
-
return state
|
|
32
|
+
with self.validate(args, state) as (args, state):
|
|
33
|
+
if not Reapers.pod_name(state):
|
|
34
|
+
return state
|
|
40
35
|
|
|
41
|
-
|
|
36
|
+
pods = self.list_pods(state.sts, state.namespace)
|
|
42
37
|
|
|
43
|
-
|
|
38
|
+
show_pods(pods, state.namespace, show_host_id=False)
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
return state
|
|
46
41
|
|
|
47
42
|
def list_pods(self, sts_name: str, namespace: str) -> List[client.V1Pod]:
|
|
48
43
|
v1 = client.CoreV1Api()
|
|
@@ -55,10 +50,7 @@ class ReaperStatus(Command):
|
|
|
55
50
|
return cast(List[client.V1Pod], v1.list_namespaced_pod(namespace, label_selector=label_selector).items)
|
|
56
51
|
|
|
57
52
|
def completion(self, state: ReplState):
|
|
58
|
-
|
|
59
|
-
return super().completion(state)
|
|
60
|
-
|
|
61
|
-
return {}
|
|
53
|
+
return super().completion(state)
|
|
62
54
|
|
|
63
55
|
def help(self, _: ReplState):
|
|
64
56
|
return f'{ReaperStatus.COMMAND}\t show reaper status'
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import partial
|
|
3
|
+
from typing import List, cast
|
|
4
|
+
from kubernetes import client
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from adam.config import Config
|
|
9
|
+
from adam.repl_state import ReplState
|
|
10
|
+
from adam.utils import tabulize, log2, wait_log
|
|
11
|
+
from adam.utils_k8s.k8s import port_forwarding
|
|
12
|
+
|
|
13
|
+
class ReaperService:
|
|
14
|
+
def __init__(self, state: ReplState, local_addr: str, remote_addr: str, show_out = True):
|
|
15
|
+
self.state = state
|
|
16
|
+
self.local_addr = local_addr
|
|
17
|
+
self.remote_addr = remote_addr
|
|
18
|
+
self.show_out = show_out
|
|
19
|
+
self.headers = None
|
|
20
|
+
|
|
21
|
+
def get(self, path: str, params: dict[str, any] = {}):
|
|
22
|
+
with logging(self, 'GET', path) as (url, headers):
|
|
23
|
+
return requests.get(url, headers=headers, params=params)
|
|
24
|
+
|
|
25
|
+
def put(self, path: str, params: dict[str, any] = {}):
|
|
26
|
+
with logging(self, 'PUT', path) as (url, headers):
|
|
27
|
+
return requests.put(url, headers=headers, params=params)
|
|
28
|
+
|
|
29
|
+
def post(self, path: str, params: dict[str, any] = {}):
|
|
30
|
+
with logging(self, 'POST', path) as (url, headers):
|
|
31
|
+
return requests.post(url, headers=headers, params=params)
|
|
32
|
+
|
|
33
|
+
class ReaperLogginHandler:
|
|
34
|
+
def __init__(self, svc: ReaperService, method: str, path: str):
|
|
35
|
+
self.svc = svc
|
|
36
|
+
self.method = method
|
|
37
|
+
self.path = path
|
|
38
|
+
|
|
39
|
+
def __enter__(self) -> tuple[str, dict[str, any]]:
|
|
40
|
+
if not self.svc.headers:
|
|
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
|
+
|
|
43
|
+
if self.svc.show_out and self.method:
|
|
44
|
+
log2(f'{self.method} {self.svc.remote_addr}/{self.path}')
|
|
45
|
+
|
|
46
|
+
return (f'http://{self.svc.local_addr}/{self.path}', self.svc.headers)
|
|
47
|
+
|
|
48
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
49
|
+
if exc_val and isinstance(exc_val, requests.Response):
|
|
50
|
+
if int(exc_val.status_code / 100) != 2:
|
|
51
|
+
if self.svc.show_out:
|
|
52
|
+
log2(exc_val.status_code)
|
|
53
|
+
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
def logging(svc: ReaperService, method: str, path: str):
|
|
57
|
+
return ReaperLogginHandler(svc, method, path)
|
|
58
|
+
|
|
59
|
+
class ReaperHandler:
|
|
60
|
+
def __init__(self, state: ReplState, show_out = True):
|
|
61
|
+
self.state = state
|
|
62
|
+
self.show_out = show_out
|
|
63
|
+
self.headers = None
|
|
64
|
+
self.forwarding = None
|
|
65
|
+
|
|
66
|
+
def __enter__(self):
|
|
67
|
+
self.forwarding = port_forwarding(self.state, Reapers.local_port(), partial(Reapers.svc_or_pod, self.state), Reapers.target_port())
|
|
68
|
+
(local_addr, remote_addr) = self.forwarding.__enter__()
|
|
69
|
+
|
|
70
|
+
return ReaperService(self.state, local_addr, remote_addr, show_out=self.show_out)
|
|
71
|
+
|
|
72
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
73
|
+
if self.forwarding:
|
|
74
|
+
return self.forwarding.__exit__(exc_type, exc_val, exc_tb)
|
|
75
|
+
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
def reaper(state: ReplState, show_out = True):
|
|
79
|
+
return ReaperHandler(state, show_out=show_out)
|
|
80
|
+
|
|
81
|
+
class Reapers:
|
|
82
|
+
schedules_ids_by_cluster: dict[str, list[str]] = {}
|
|
83
|
+
|
|
84
|
+
def pod_name(state: ReplState):
|
|
85
|
+
pods = Reapers.list_reaper_pods(state.sts if state.sts else state.pod, state.namespace)
|
|
86
|
+
if pods:
|
|
87
|
+
return pods[0].metadata.name
|
|
88
|
+
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def show_schedule(state: ReplState, schedule_id: str):
|
|
92
|
+
def filter(schedules: list[dict]):
|
|
93
|
+
return [schedule for schedule in schedules if schedule['id'] == schedule_id]
|
|
94
|
+
|
|
95
|
+
Reapers.show_schedules(state, filter)
|
|
96
|
+
|
|
97
|
+
def show_schedules(state: ReplState, filter: Callable[[list[dict]], dict] = None):
|
|
98
|
+
schedules = Reapers.list_schedules(state, filter=filter)
|
|
99
|
+
|
|
100
|
+
# forced refresh of schedule list
|
|
101
|
+
if not filter:
|
|
102
|
+
Reapers.schedules_ids_by_cluster[state.sts] = [schedule['id'] for schedule in schedules]
|
|
103
|
+
|
|
104
|
+
tabulize(schedules, lambda s: f"{s['id']} {s['state']} {s['cluster_name']} {s['keyspace_name']}", header='ID STATE CLUSTER KEYSPACE', to=2)
|
|
105
|
+
|
|
106
|
+
def schedule_ids(state: ReplState, show_output = True, filter: Callable[[list[dict]], dict] = None):
|
|
107
|
+
schedules = Reapers.list_schedules(state, show_output=show_output, filter=filter)
|
|
108
|
+
return [schedule['id'] for schedule in schedules]
|
|
109
|
+
|
|
110
|
+
def list_schedules(state: ReplState, show_output = True, filter: Callable[[list[dict]], dict] = None) -> list[dict]:
|
|
111
|
+
with reaper(state, show_out=show_output) as requests:
|
|
112
|
+
if not (response := requests.get('repair_schedule')):
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
res = response.json()
|
|
116
|
+
if filter:
|
|
117
|
+
res = filter(res)
|
|
118
|
+
|
|
119
|
+
return res
|
|
120
|
+
|
|
121
|
+
def list_reaper_pods(sts_name: str, namespace: str) -> List[client.V1Pod]:
|
|
122
|
+
v1 = client.CoreV1Api()
|
|
123
|
+
|
|
124
|
+
# k8ssandra.io/reaper: cs-d0767a536f-cs-d0767a536f-reaper
|
|
125
|
+
groups = re.match(Config().get('reaper.pod.cluster-regex', r'(.*?-.*?-.*?-.*?)-.*'), sts_name)
|
|
126
|
+
label_selector = Config().get('reaper.pod.label-selector', 'k8ssandra.io/reaper={cluster}-reaper').replace('{cluster}', groups[1])
|
|
127
|
+
|
|
128
|
+
return cast(List[client.V1Pod], v1.list_namespaced_pod(namespace, label_selector=label_selector).items)
|
|
129
|
+
|
|
130
|
+
def cookie_header(state: ReplState, local_addr, remote_addr, show_output = True):
|
|
131
|
+
return {'Cookie': Reapers.login(state, local_addr, remote_addr, show_output=show_output)}
|
|
132
|
+
|
|
133
|
+
def login(state: ReplState, local_addr: str, remote_addr: str, show_output = True) -> str :
|
|
134
|
+
user, pw = state.user_pass(secret_path='reaper.secret')
|
|
135
|
+
|
|
136
|
+
response = requests.post(f'http://{local_addr}/login', headers={
|
|
137
|
+
'Accept': '*'
|
|
138
|
+
},data={
|
|
139
|
+
'username':user,
|
|
140
|
+
'password':pw})
|
|
141
|
+
if show_output:
|
|
142
|
+
log2(f'POST {remote_addr}/login')
|
|
143
|
+
log2(f' username={user}&password={pw}')
|
|
144
|
+
|
|
145
|
+
if int(response.status_code / 100) != 2:
|
|
146
|
+
if show_output:
|
|
147
|
+
log2("login failed")
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
return response.headers['Set-Cookie']
|
|
151
|
+
|
|
152
|
+
def reaper_spec(state: ReplState) -> dict[str, any]:
|
|
153
|
+
if not (pod := Reapers.pod_name(state)):
|
|
154
|
+
return {}
|
|
155
|
+
|
|
156
|
+
user, pw = state.user_pass(secret_path='reaper.secret')
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
'pod': pod,
|
|
160
|
+
'exec': f'kubectl exec -it {pod} -n {state.namespace} -- bash',
|
|
161
|
+
'forward': f'kubectl port-forward pods/{pod} -n {state.namespace} {Reapers.local_port()}:{Reapers.target_port()}',
|
|
162
|
+
'web-uri': f'http://localhost:{Reapers.local_port()}/webui',
|
|
163
|
+
'username': user,
|
|
164
|
+
'password': pw
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def cached_schedule_ids(state: ReplState) -> list[str]:
|
|
168
|
+
if state.sts in Reapers.schedules_ids_by_cluster:
|
|
169
|
+
return Reapers.schedules_ids_by_cluster[state.sts]
|
|
170
|
+
|
|
171
|
+
if pod := Reapers.pod_name(state):
|
|
172
|
+
wait_log('Inspecting Cassandra Reaper...')
|
|
173
|
+
|
|
174
|
+
schedules = Reapers.schedule_ids(state, show_output = False)
|
|
175
|
+
Reapers.schedules_ids_by_cluster[state.sts] = schedules
|
|
176
|
+
|
|
177
|
+
return schedules
|
|
178
|
+
|
|
179
|
+
return []
|
|
180
|
+
|
|
181
|
+
def svc_name():
|
|
182
|
+
return Config().get('reaper.service-name', 'reaper-service')
|
|
183
|
+
|
|
184
|
+
def local_port():
|
|
185
|
+
return Config().get('reaper.port-forward.local-port', 9001)
|
|
186
|
+
|
|
187
|
+
def target_port():
|
|
188
|
+
return 8080
|
|
189
|
+
|
|
190
|
+
def svc_or_pod(state: ReplState, is_service: bool):
|
|
191
|
+
if is_service:
|
|
192
|
+
return Reapers.svc_name()
|
|
193
|
+
else:
|
|
194
|
+
return Reapers.pod_name(state)
|
adam/commands/repair/repair.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
|
-
from adam.commands.
|
|
3
|
+
from adam.commands.intermediate_command import IntermediateCommand
|
|
4
4
|
from .repair_run import RepairRun
|
|
5
5
|
from .repair_scan import RepairScan
|
|
6
6
|
from .repair_stop import RepairStop
|
|
7
7
|
from .repair_log import RepairLog
|
|
8
|
-
from adam.repl_state import ReplState, RequiredState
|
|
9
8
|
|
|
10
|
-
class Repair(
|
|
9
|
+
class Repair(IntermediateCommand):
|
|
11
10
|
COMMAND = 'repair'
|
|
12
11
|
|
|
13
12
|
# the singleton pattern
|
|
@@ -16,29 +15,12 @@ class Repair(Command):
|
|
|
16
15
|
|
|
17
16
|
return cls.instance
|
|
18
17
|
|
|
19
|
-
def __init__(self, successor: Command=None):
|
|
20
|
-
super().__init__(successor)
|
|
21
|
-
|
|
22
18
|
def command(self):
|
|
23
19
|
return Repair.COMMAND
|
|
24
20
|
|
|
25
|
-
def
|
|
26
|
-
return RequiredState.CLUSTER
|
|
27
|
-
|
|
28
|
-
def run(self, cmd: str, state: ReplState):
|
|
29
|
-
if not(args := self.args(cmd)):
|
|
30
|
-
return super().run(cmd, state)
|
|
31
|
-
if not self.validate_state(state):
|
|
32
|
-
return state
|
|
33
|
-
|
|
34
|
-
return super().intermediate_run(cmd, state, args, Repair.cmd_list())
|
|
35
|
-
|
|
36
|
-
def cmd_list():
|
|
21
|
+
def cmd_list(self):
|
|
37
22
|
return [RepairRun(), RepairScan(), RepairStop(), RepairLog()]
|
|
38
23
|
|
|
39
|
-
def completion(self, state: ReplState):
|
|
40
|
-
return super().completion(state)
|
|
41
|
-
|
|
42
24
|
class RepairCommandHelper(click.Command):
|
|
43
25
|
def get_help(self, ctx: click.Context):
|
|
44
|
-
|
|
26
|
+
IntermediateCommand.intermediate_help(super().get_help(ctx), Repair.COMMAND, Repair().cmd_list(), show_cluster_help=True)
|
|
@@ -24,20 +24,14 @@ class RepairLog(Command):
|
|
|
24
24
|
if not(args := self.args(cmd)):
|
|
25
25
|
return super().run(cmd, state)
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
ns = state.namespace
|
|
32
|
-
Jobs.get_logs('cassrepair-'+state.sts, ns)
|
|
27
|
+
with self.validate(args, state) as (args, state):
|
|
28
|
+
ns = state.namespace
|
|
29
|
+
Jobs.get_logs('cassrepair-'+state.sts, ns)
|
|
33
30
|
|
|
34
|
-
|
|
31
|
+
return state
|
|
35
32
|
|
|
36
33
|
def completion(self, state: ReplState):
|
|
37
|
-
|
|
38
|
-
return super().completion(state)
|
|
39
|
-
|
|
40
|
-
return {}
|
|
34
|
+
return super().completion(state)
|
|
41
35
|
|
|
42
36
|
def help(self, _: ReplState):
|
|
43
37
|
return f'{RepairLog.COMMAND}\t get repair job logs'
|