kaqing 2.0.184__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/app_session.py +1 -1
- adam/batch.py +15 -15
- 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 +4 -4
- adam/commands/app/app_ping.py +2 -2
- adam/commands/{login.py → app/login.py} +2 -2
- adam/commands/app/show_app_actions.py +3 -3
- adam/commands/app/show_app_id.py +2 -2
- adam/commands/app/show_app_queues.py +2 -2
- adam/commands/{show → app}/show_login.py +3 -3
- adam/commands/app/utils_app.py +9 -1
- adam/commands/audit/audit.py +8 -24
- adam/commands/audit/audit_repair_tables.py +3 -3
- adam/commands/audit/audit_run.py +3 -3
- adam/commands/audit/completions_l.py +15 -0
- adam/commands/audit/show_last10.py +2 -3
- adam/commands/audit/show_slow10.py +2 -2
- adam/commands/audit/show_top10.py +2 -2
- adam/commands/bash/bash.py +3 -3
- adam/commands/bash/utils_bash.py +1 -1
- adam/commands/cassandra/download_cassandra_log.py +45 -0
- adam/commands/cassandra/restart_cluster.py +47 -0
- adam/commands/cassandra/restart_node.py +51 -0
- adam/commands/cassandra/restart_nodes.py +47 -0
- adam/commands/{rollout.py → cassandra/rollout.py} +3 -3
- adam/commands/{show → cassandra}/show_cassandra_repairs.py +7 -5
- adam/commands/{show → cassandra}/show_cassandra_status.py +24 -17
- adam/commands/{show → cassandra}/show_cassandra_version.py +2 -2
- adam/commands/cassandra/show_processes.py +50 -0
- adam/commands/cassandra/show_storage.py +44 -0
- adam/commands/{watch.py → cassandra/watch.py} +2 -2
- adam/commands/cli/__init__.py +0 -0
- adam/commands/{cli_commands.py → cli/cli_commands.py} +6 -1
- adam/commands/{clipboard_copy.py → cli/clipboard_copy.py} +4 -4
- adam/commands/{show/show_commands.py → cli/show_cli_commands.py} +5 -5
- adam/commands/code.py +2 -2
- adam/commands/command.py +54 -14
- adam/commands/commands_utils.py +14 -6
- adam/commands/config/__init__.py +0 -0
- adam/commands/{param_get.py → config/param_get.py} +2 -2
- adam/commands/{param_set.py → config/param_set.py} +2 -2
- adam/commands/{show → config}/show_params.py +3 -3
- adam/commands/{alter_tables.py → cql/alter_tables.py} +3 -3
- adam/commands/cql/completions_c.py +29 -0
- adam/commands/cql/cqlsh.py +4 -8
- adam/commands/cql/utils_cql.py +36 -17
- adam/commands/debug/__init__.py +0 -0
- adam/commands/debug/debug.py +22 -0
- adam/commands/debug/debug_completes.py +35 -0
- adam/commands/debug/debug_timings.py +35 -0
- adam/commands/debug/show_offloaded_completes.py +45 -0
- 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 +37 -11
- adam/commands/devices/device_app.py +7 -7
- adam/commands/devices/device_auit_log.py +2 -2
- adam/commands/devices/device_cass.py +6 -6
- adam/commands/devices/device_export.py +7 -4
- adam/commands/devices/device_postgres.py +19 -9
- adam/commands/devices/devices.py +1 -1
- adam/commands/diag/__init__.py +0 -0
- adam/commands/{check.py → diag/check.py} +3 -3
- adam/commands/diag/generate_report.py +52 -0
- adam/commands/{issues.py → 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/completions_x.py +11 -0
- adam/commands/export/download_export_session.py +5 -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 -19
- adam/commands/export/export_databases.py +20 -11
- adam/commands/export/export_select.py +9 -34
- adam/commands/export/export_sessions.py +13 -11
- adam/commands/export/export_use.py +6 -6
- adam/commands/export/export_x_select.py +48 -0
- adam/commands/export/exporter.py +140 -53
- adam/commands/export/import_files.py +3 -7
- adam/commands/export/import_session.py +2 -6
- adam/commands/export/importer.py +12 -13
- adam/commands/export/importer_athena.py +15 -35
- adam/commands/export/importer_sqlite.py +19 -8
- adam/commands/export/show_column_counts.py +11 -12
- adam/commands/export/show_export_databases.py +4 -4
- adam/commands/export/show_export_session.py +5 -5
- adam/commands/export/show_export_sessions.py +4 -4
- adam/commands/export/utils_export.py +40 -25
- adam/commands/fs/__init__.py +0 -0
- adam/commands/{cat.py → fs/cat.py} +4 -4
- adam/commands/fs/cat_local.py +42 -0
- adam/commands/{cd.py → fs/cd.py} +4 -4
- adam/commands/{download_file.py → fs/download_file.py} +7 -7
- adam/commands/{find_files.py → fs/find_files.py} +7 -7
- adam/commands/{find_processes.py → fs/find_processes.py} +14 -22
- adam/commands/{head.py → fs/head.py} +5 -5
- adam/commands/fs/head_local.py +46 -0
- adam/commands/{ls.py → fs/ls.py} +4 -4
- adam/commands/fs/ls_local.py +40 -0
- adam/commands/{pwd.py → fs/pwd.py} +2 -2
- adam/commands/fs/rm.py +18 -0
- adam/commands/fs/rm_downloads.py +39 -0
- adam/commands/fs/rm_logs.py +44 -0
- adam/commands/fs/rm_logs_local.py +38 -0
- adam/commands/{shell.py → fs/shell.py} +2 -2
- adam/commands/{show → fs}/show_adam.py +3 -3
- adam/commands/{show → 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/intermediate_command.py +3 -0
- adam/commands/kubectl.py +2 -2
- adam/commands/medusa/medusa_backup.py +2 -2
- adam/commands/medusa/medusa_restore.py +4 -18
- adam/commands/medusa/medusa_show_backupjobs.py +2 -2
- adam/commands/medusa/medusa_show_restorejobs.py +2 -2
- adam/commands/medusa/utils_medusa.py +15 -0
- adam/commands/nodetool/__init__.py +0 -0
- adam/commands/nodetool/nodetool.py +87 -0
- adam/commands/nodetool/utils_nodetool.py +44 -0
- adam/commands/postgres/completions_p.py +22 -0
- adam/commands/postgres/postgres.py +10 -20
- adam/commands/postgres/postgres_databases.py +3 -3
- adam/commands/postgres/postgres_ls.py +3 -3
- adam/commands/postgres/postgres_preview.py +2 -2
- adam/commands/postgres/utils_postgres.py +12 -2
- adam/commands/preview_table.py +3 -4
- 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 +8 -4
- adam/commands/reaper/reaper_schedule_start.py +3 -4
- adam/commands/reaper/reaper_schedule_stop.py +3 -4
- adam/commands/reaper/reaper_schedules.py +2 -2
- adam/commands/reaper/reaper_status.py +2 -2
- adam/commands/reaper/utils_reaper.py +41 -6
- adam/commands/repair/repair_log.py +2 -2
- adam/commands/repair/repair_run.py +2 -2
- adam/commands/repair/repair_scan.py +2 -4
- adam/commands/repair/repair_stop.py +2 -3
- adam/commands/{show/show.py → show.py} +12 -11
- adam/config.py +4 -5
- adam/embedded_params.py +1 -1
- adam/repl.py +24 -10
- adam/repl_commands.py +68 -45
- adam/repl_session.py +16 -1
- adam/repl_state.py +16 -1
- adam/sql/async_executor.py +62 -0
- adam/sql/lark_completer.py +286 -0
- adam/sql/lark_parser.py +604 -0
- adam/sql/qingl.lark +1075 -0
- adam/sso/cred_cache.py +2 -5
- adam/utils.py +259 -82
- adam/utils_async_job.py +73 -0
- adam/utils_k8s/app_clusters.py +11 -4
- adam/utils_k8s/app_pods.py +10 -5
- adam/utils_k8s/cassandra_clusters.py +19 -7
- adam/utils_k8s/cassandra_nodes.py +16 -6
- adam/utils_k8s/k8s.py +9 -0
- adam/utils_k8s/kube_context.py +1 -4
- adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +8 -2
- adam/utils_k8s/pods.py +189 -29
- adam/utils_k8s/statefulsets.py +5 -2
- adam/utils_local.py +78 -2
- adam/utils_repl/appendable_completer.py +6 -0
- adam/utils_repl/repl_completer.py +51 -4
- adam/utils_sqlite.py +3 -8
- adam/version.py +1 -1
- {kaqing-2.0.184.dist-info → kaqing-2.0.227.dist-info}/METADATA +1 -1
- kaqing-2.0.227.dist-info/RECORD +280 -0
- kaqing-2.0.227.dist-info/top_level.txt +2 -0
- teddy/__init__.py +0 -0
- teddy/lark_parser.py +436 -0
- teddy/lark_parser2.py +618 -0
- adam/commands/cql/cql_completions.py +0 -32
- adam/commands/export/export_select_x.py +0 -54
- adam/commands/logs.py +0 -37
- adam/commands/nodetool.py +0 -69
- adam/commands/postgres/psql_completions.py +0 -11
- adam/commands/report.py +0 -61
- adam/commands/restart.py +0 -60
- adam/commands/show/show_processes.py +0 -49
- adam/commands/show/show_storage.py +0 -42
- kaqing-2.0.184.dist-info/RECORD +0 -244
- kaqing-2.0.184.dist-info/top_level.txt +0 -1
- /adam/commands/{show → cassandra}/__init__.py +0 -0
- /adam/commands/{nodetool_commands.py → nodetool/nodetool_commands.py} +0 -0
- {kaqing-2.0.184.dist-info → kaqing-2.0.227.dist-info}/WHEEL +0 -0
- {kaqing-2.0.184.dist-info → kaqing-2.0.227.dist-info}/entry_points.txt +0 -0
adam/utils_async_job.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import re
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
from adam.utils import log_dir
|
|
6
|
+
|
|
7
|
+
class AsyncJobs:
|
|
8
|
+
_last_id: str = None
|
|
9
|
+
_last_command: str = None
|
|
10
|
+
|
|
11
|
+
def log_file(command: str, pod_name: str = None, job_id: str = None, pod_suffix: str = None, err = False, dir: str = None):
|
|
12
|
+
try:
|
|
13
|
+
if not job_id:
|
|
14
|
+
job_id = AsyncJobs.new_id()
|
|
15
|
+
|
|
16
|
+
AsyncJobs._last_id = job_id
|
|
17
|
+
AsyncJobs._last_command = command
|
|
18
|
+
|
|
19
|
+
AsyncJobs.write_last_command(job_id, command)
|
|
20
|
+
|
|
21
|
+
cmd_name = ''
|
|
22
|
+
if command.startswith('nodetool '):
|
|
23
|
+
command = command.strip(' &')
|
|
24
|
+
cmd_name = f".{'_'.join(command.split(' ')[5:])}"
|
|
25
|
+
|
|
26
|
+
if pod_suffix is None:
|
|
27
|
+
pod_suffix = '{pod}'
|
|
28
|
+
if pod_name:
|
|
29
|
+
pod_suffix = pod_name
|
|
30
|
+
if groups := re.match(r'.*-(.*)', pod_name):
|
|
31
|
+
pod_suffix = f'-{groups[1]}'
|
|
32
|
+
|
|
33
|
+
if not dir:
|
|
34
|
+
dir = log_dir()
|
|
35
|
+
|
|
36
|
+
return f'{dir}/{job_id}{cmd_name}{pod_suffix}.{"err" if err else "log"}'
|
|
37
|
+
except:
|
|
38
|
+
traceback.print_exc()
|
|
39
|
+
|
|
40
|
+
def new_id(dt: datetime = None):
|
|
41
|
+
if not dt:
|
|
42
|
+
dt = datetime.now()
|
|
43
|
+
|
|
44
|
+
id = dt.strftime("%d%H%M%S")
|
|
45
|
+
AsyncJobs._last_id = id
|
|
46
|
+
|
|
47
|
+
return id
|
|
48
|
+
|
|
49
|
+
def last_command():
|
|
50
|
+
last_id = AsyncJobs._last_id
|
|
51
|
+
last_command = AsyncJobs._last_command
|
|
52
|
+
|
|
53
|
+
if not last_id or not last_command:
|
|
54
|
+
l0, l1 = AsyncJobs.read_last_command()
|
|
55
|
+
if not last_id:
|
|
56
|
+
last_id = l0
|
|
57
|
+
if not last_command:
|
|
58
|
+
last_command = l1
|
|
59
|
+
|
|
60
|
+
return last_id, last_command
|
|
61
|
+
|
|
62
|
+
def write_last_command(job_id: str, command: str):
|
|
63
|
+
with open(f'{log_dir()}/last', 'wt') as f:
|
|
64
|
+
f.write(job_id)
|
|
65
|
+
f.write('\n')
|
|
66
|
+
f.write(command)
|
|
67
|
+
|
|
68
|
+
def read_last_command():
|
|
69
|
+
with open(f'{log_dir()}/last', 'rt') as f:
|
|
70
|
+
job_id = f.readline().strip(' \r\n')
|
|
71
|
+
command = f.readline().strip(' \r\n')
|
|
72
|
+
|
|
73
|
+
return job_id, command
|
adam/utils_k8s/app_clusters.py
CHANGED
|
@@ -2,7 +2,7 @@ import sys
|
|
|
2
2
|
from typing import TypeVar
|
|
3
3
|
|
|
4
4
|
from adam.utils_k8s.app_pods import AppPods
|
|
5
|
-
from adam.pod_exec_result import PodExecResult
|
|
5
|
+
from adam.utils_k8s.pod_exec_result import PodExecResult
|
|
6
6
|
from adam.utils import log, log2
|
|
7
7
|
from adam.utils_k8s.pods import Pods
|
|
8
8
|
from .kube_context import KubeContext
|
|
@@ -11,8 +11,15 @@ T = TypeVar('T')
|
|
|
11
11
|
|
|
12
12
|
# utility collection on app clusters; methods are all static
|
|
13
13
|
class AppClusters:
|
|
14
|
-
def exec(pods: list[str],
|
|
15
|
-
|
|
14
|
+
def exec(pods: list[str],
|
|
15
|
+
namespace: str,
|
|
16
|
+
command: str,
|
|
17
|
+
action: str = 'action',
|
|
18
|
+
max_workers=0,
|
|
19
|
+
show_out=True,
|
|
20
|
+
on_any = False,
|
|
21
|
+
shell = '/bin/sh',
|
|
22
|
+
backgrounded = False) -> list[PodExecResult]:
|
|
16
23
|
samples = 1 if on_any else sys.maxsize
|
|
17
24
|
msg = 'd`Running|Ran ' + action + ' command onto {size} pods'
|
|
18
25
|
with Pods.parallelize(pods, max_workers, samples, msg, action=action) as exec:
|
|
@@ -23,6 +30,6 @@ class AppClusters:
|
|
|
23
30
|
if result.stdout:
|
|
24
31
|
log(result.stdout)
|
|
25
32
|
if result.stderr:
|
|
26
|
-
log2(result.stderr
|
|
33
|
+
log2(result.stderr)
|
|
27
34
|
|
|
28
35
|
return results
|
adam/utils_k8s/app_pods.py
CHANGED
|
@@ -4,7 +4,7 @@ from kubernetes import client
|
|
|
4
4
|
|
|
5
5
|
from adam.config import Config
|
|
6
6
|
from adam.utils_k8s.pods import Pods
|
|
7
|
-
from adam.pod_exec_result import PodExecResult
|
|
7
|
+
from adam.utils_k8s.pod_exec_result import PodExecResult
|
|
8
8
|
from adam.repl_session import ReplSession
|
|
9
9
|
|
|
10
10
|
# utility collection on app pods; methods are all static
|
|
@@ -25,12 +25,17 @@ class AppPods:
|
|
|
25
25
|
|
|
26
26
|
return v1.list_namespaced_pod(namespace, label_selector=label_selector).items
|
|
27
27
|
|
|
28
|
-
def exec(pod_name: str,
|
|
28
|
+
def exec(pod_name: str,
|
|
29
|
+
namespace: str,
|
|
30
|
+
command: str,
|
|
31
|
+
show_out = True,
|
|
32
|
+
throw_err = False,
|
|
33
|
+
shell = '/bin/sh',
|
|
34
|
+
backgrounded = False) -> PodExecResult:
|
|
29
35
|
container = Config().get('app.container-name', 'c3-server')
|
|
30
36
|
r = Pods.exec(pod_name, container, namespace, command, show_out = show_out, throw_err = throw_err, shell = shell, backgrounded = backgrounded)
|
|
31
37
|
|
|
32
|
-
if r and
|
|
33
|
-
|
|
34
|
-
ReplSession().prompt_session.history.append_string(f'@{r.pod} cat {r.log_file}')
|
|
38
|
+
if r and r.log_file:
|
|
39
|
+
ReplSession().append_history(f':cat {r.log_file}')
|
|
35
40
|
|
|
36
41
|
return r
|
|
@@ -2,9 +2,10 @@ import sys
|
|
|
2
2
|
from typing import TypeVar
|
|
3
3
|
|
|
4
4
|
from adam.config import Config
|
|
5
|
+
from adam.utils_async_job import AsyncJobs
|
|
5
6
|
from adam.utils_k8s.cassandra_nodes import CassandraNodes
|
|
6
|
-
from adam.pod_exec_result import PodExecResult
|
|
7
|
-
from adam.utils import log, log2
|
|
7
|
+
from adam.utils_k8s.pod_exec_result import PodExecResult
|
|
8
|
+
from adam.utils import log, log2, log_to_pods, pod_log_dir
|
|
8
9
|
from adam.utils_k8s.pods import Pods
|
|
9
10
|
from adam.utils_k8s.statefulsets import StatefulSets
|
|
10
11
|
|
|
@@ -21,20 +22,31 @@ class CassandraClusters:
|
|
|
21
22
|
on_any = False,
|
|
22
23
|
shell = '/bin/sh',
|
|
23
24
|
backgrounded = False,
|
|
24
|
-
log_file = None
|
|
25
|
+
log_file = None,
|
|
26
|
+
history=True,
|
|
27
|
+
text_color: str = None) -> list[PodExecResult]:
|
|
25
28
|
|
|
26
29
|
pods = StatefulSets.pod_names(sts, namespace)
|
|
27
30
|
samples = 1 if on_any else sys.maxsize
|
|
31
|
+
if (backgrounded or command.endswith(' &')) and not log_file:
|
|
32
|
+
pod_suffix = None
|
|
33
|
+
dir = None
|
|
34
|
+
if log_to_pods():
|
|
35
|
+
pod_suffix = ''
|
|
36
|
+
dir = pod_log_dir()
|
|
37
|
+
|
|
38
|
+
log_file = AsyncJobs.log_file(command, job_id=AsyncJobs.new_id(), pod_suffix=pod_suffix, dir=dir)
|
|
39
|
+
|
|
28
40
|
msg = 'd`Running|Ran ' + action + ' command onto {size} pods'
|
|
29
41
|
with Pods.parallelize(pods, max_workers, samples, msg, action=action) as exec:
|
|
30
|
-
results: list[PodExecResult] = exec.map(lambda pod: CassandraNodes.exec(pod, namespace, command, False, False, shell, backgrounded, log_file))
|
|
42
|
+
results: list[PodExecResult] = exec.map(lambda pod: CassandraNodes.exec(pod, namespace, command, False, False, shell, backgrounded, log_file, history, text_color))
|
|
31
43
|
for result in results:
|
|
32
44
|
if show_out and not Config().is_debug():
|
|
33
|
-
log(result.command)
|
|
45
|
+
log(result.command, text_color=text_color)
|
|
34
46
|
if result.stdout:
|
|
35
|
-
log(result.stdout)
|
|
47
|
+
log(result.stdout, text_color=text_color)
|
|
36
48
|
if result.stderr:
|
|
37
|
-
log2(result.stderr,
|
|
49
|
+
log2(result.stderr, text_color=text_color)
|
|
38
50
|
|
|
39
51
|
return results
|
|
40
52
|
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
from adam.config import Config
|
|
2
2
|
from adam.utils_k8s.pods import Pods
|
|
3
3
|
from adam.utils_k8s.secrets import Secrets
|
|
4
|
-
from adam.pod_exec_result import PodExecResult
|
|
4
|
+
from adam.utils_k8s.pod_exec_result import PodExecResult
|
|
5
5
|
from adam.repl_session import ReplSession
|
|
6
6
|
|
|
7
7
|
# utility collection on cassandra nodes; methods are all static
|
|
8
8
|
class CassandraNodes:
|
|
9
|
-
def exec(pod_name: str,
|
|
10
|
-
|
|
9
|
+
def exec(pod_name: str,
|
|
10
|
+
namespace: str,
|
|
11
|
+
command: str,
|
|
12
|
+
show_out = True,
|
|
13
|
+
throw_err = False,
|
|
14
|
+
shell = '/bin/sh',
|
|
15
|
+
backgrounded = False,
|
|
16
|
+
log_file = None,
|
|
17
|
+
history = True,
|
|
18
|
+
text_color: str = None) -> PodExecResult:
|
|
19
|
+
r: PodExecResult = Pods.exec(pod_name, "cassandra", namespace, command, show_out = show_out, throw_err = throw_err, shell = shell, backgrounded = backgrounded, log_file=log_file, text_color=text_color)
|
|
11
20
|
|
|
12
|
-
if r and
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
if history and r and r.log_file:
|
|
22
|
+
# entry = f':tail {r.log_file}'
|
|
23
|
+
|
|
24
|
+
ReplSession().append_history(r.log_file)
|
|
15
25
|
|
|
16
26
|
return r
|
|
17
27
|
|
adam/utils_k8s/k8s.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
+
import inspect
|
|
2
3
|
import re
|
|
3
4
|
import portforward
|
|
4
5
|
|
|
@@ -49,6 +50,14 @@ class PortForwardHandler:
|
|
|
49
50
|
raise InvalidStateException(state)
|
|
50
51
|
|
|
51
52
|
self.pod = pod
|
|
53
|
+
|
|
54
|
+
# pf = portforward.forward(state.namespace, pod, self.local_port + 1, self.target_port, log_level=portforward.LogLevel.DEBUG)
|
|
55
|
+
# print(inspect.getsource(pf.__enter__))
|
|
56
|
+
# print('test portforward START', state.namespace, pod, self.local_port + 1, self.target_port, pf.__enter__)
|
|
57
|
+
# with pf:
|
|
58
|
+
# print('test portforward BODY')
|
|
59
|
+
# print('test portforward OK')
|
|
60
|
+
|
|
52
61
|
self.forward_connection = portforward.forward(state.namespace, pod, self.local_port, self.target_port)
|
|
53
62
|
if self.inc_connection_cnt() == 1:
|
|
54
63
|
self.forward_connection.__enter__()
|
adam/utils_k8s/kube_context.py
CHANGED
|
@@ -102,7 +102,4 @@ class KubeContext:
|
|
|
102
102
|
return name if re.match(r"^(?!pg-).*-k8spg-.*$", name) else None
|
|
103
103
|
|
|
104
104
|
def show_out(s: bool):
|
|
105
|
-
return s or Config().is_debug()
|
|
106
|
-
|
|
107
|
-
def show_parallelism():
|
|
108
|
-
return Config().get('debugs.show-parallelism', False)
|
|
105
|
+
return s or Config().is_debug()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import yaml
|
|
2
2
|
|
|
3
|
-
from adam.utils import log_exc
|
|
3
|
+
from adam.utils import ExecResult, log_exc
|
|
4
4
|
|
|
5
|
-
class PodExecResult:
|
|
5
|
+
class PodExecResult(ExecResult):
|
|
6
6
|
# {
|
|
7
7
|
# 'metadata': {},
|
|
8
8
|
# 'status': 'Failure',
|
|
@@ -34,6 +34,12 @@ class PodExecResult:
|
|
|
34
34
|
|
|
35
35
|
return code
|
|
36
36
|
|
|
37
|
+
def cat_log_file_cmd(self):
|
|
38
|
+
if self.pod and self.log_file:
|
|
39
|
+
return f'@{self.pod} cat {self.log_file}'
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
37
43
|
def __str__(self):
|
|
38
44
|
return f'{"OK" if self.exit_code() == 0 else self.exit_code()} {self.command}'
|
|
39
45
|
|
adam/utils_k8s/pods.py
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
import os
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
4
5
|
import sys
|
|
5
6
|
import time
|
|
6
7
|
from typing import TypeVar
|
|
7
8
|
from kubernetes import client
|
|
8
9
|
from kubernetes.stream import stream
|
|
9
10
|
from kubernetes.stream.ws_client import ERROR_CHANNEL, WSClient
|
|
11
|
+
from prompt_toolkit import print_formatted_text, HTML
|
|
10
12
|
|
|
11
13
|
from adam.config import Config
|
|
14
|
+
from adam.repl_session import ReplSession
|
|
12
15
|
from adam.utils_k8s.volumes import ConfigMapMount
|
|
13
|
-
from adam.pod_exec_result import PodExecResult
|
|
14
|
-
from adam.utils import GeneratorStream, ParallelMapHandler, log2, debug, log_exc
|
|
15
|
-
from adam.utils_local import
|
|
16
|
+
from adam.utils_k8s.pod_exec_result import PodExecResult
|
|
17
|
+
from adam.utils import GeneratorStream, ParallelMapHandler, PodLogFile, log2, debug, log_exc, log_to_pods, pod_log_dir
|
|
18
|
+
from adam.utils_local import local_downloads_dir, local_exec
|
|
19
|
+
from adam.utils_async_job import AsyncJobs
|
|
16
20
|
from .kube_context import KubeContext
|
|
17
21
|
|
|
18
22
|
from websocket._core import WebSocket
|
|
@@ -48,7 +52,7 @@ class Pods:
|
|
|
48
52
|
if samples == sys.maxsize:
|
|
49
53
|
samples = Config().action_node_samples(action, sys.maxsize)
|
|
50
54
|
|
|
51
|
-
return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg)
|
|
55
|
+
return ParallelMapHandler(collection, max_workers, samples = samples, msg = msg, name=action)
|
|
52
56
|
|
|
53
57
|
def exec(pod_name: str,
|
|
54
58
|
container: str,
|
|
@@ -60,12 +64,19 @@ class Pods:
|
|
|
60
64
|
backgrounded = False,
|
|
61
65
|
log_file = None,
|
|
62
66
|
interaction: Callable[[any, list[str]], any] = None,
|
|
63
|
-
env_prefix: str = None
|
|
67
|
+
env_prefix: str = None,
|
|
68
|
+
text_color: str = None):
|
|
64
69
|
if _TEST_POD_EXEC_OUTS:
|
|
65
70
|
return _TEST_POD_EXEC_OUTS
|
|
66
71
|
|
|
67
72
|
show_out = KubeContext.show_out(show_out)
|
|
68
73
|
|
|
74
|
+
if backgrounded or command.endswith(' &'):
|
|
75
|
+
if log_to_pods():
|
|
76
|
+
return Pods.exec_backgrounded_logging_to_pod(pod_name, container, namespace, command, show_out, shell, log_file, env_prefix, text_color)
|
|
77
|
+
else:
|
|
78
|
+
return Pods.exec_backgrounded(pod_name, container, command, show_out, shell, log_file, env_prefix, text_color)
|
|
79
|
+
|
|
69
80
|
api = client.CoreV1Api()
|
|
70
81
|
|
|
71
82
|
tty = True
|
|
@@ -73,25 +84,28 @@ class Pods:
|
|
|
73
84
|
if env_prefix:
|
|
74
85
|
exec_command = [shell, '-c', f'{env_prefix} {command}']
|
|
75
86
|
|
|
76
|
-
if backgrounded or command.endswith(' &'):
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
# if backgrounded or command.endswith(' &'):
|
|
88
|
+
# # should be false for starting a background process
|
|
89
|
+
# tty = False
|
|
90
|
+
|
|
91
|
+
# if Config().get('repl.background-process.auto-nohup', True):
|
|
92
|
+
# command = command.strip(' &')
|
|
93
|
+
# cmd_name = ''
|
|
94
|
+
# if command.startswith('nodetool '):
|
|
95
|
+
# cmd_name = f".{'_'.join(command.split(' ')[5:])}"
|
|
96
|
+
|
|
97
|
+
# if not log_file:
|
|
98
|
+
# log_file = f'{log_prefix()}-{datetime.now().strftime("%d%H%M%S")}{cmd_name}.log'
|
|
99
|
+
# command = f"nohup {command} > {log_file} 2>&1 &"
|
|
100
|
+
# if env_prefix:
|
|
101
|
+
# command = f'{env_prefix} {command}'
|
|
102
|
+
# exec_command = [shell, '-c', command]
|
|
92
103
|
|
|
93
104
|
k_command = f'kubectl exec {pod_name} -c {container} -n {namespace} -- {shell} -c "{command}"'
|
|
94
|
-
|
|
105
|
+
if Config().is_debug():
|
|
106
|
+
debug(k_command)
|
|
107
|
+
elif show_out:
|
|
108
|
+
log2(k_command, text_color=text_color)
|
|
95
109
|
|
|
96
110
|
resp: WSClient = stream(
|
|
97
111
|
api.connect_get_namespaced_pod_exec,
|
|
@@ -116,14 +130,22 @@ class Pods:
|
|
|
116
130
|
if resp.peek_stdout():
|
|
117
131
|
frag = resp.read_stdout()
|
|
118
132
|
stdout.append(frag)
|
|
119
|
-
if show_out:
|
|
133
|
+
if show_out:
|
|
134
|
+
if text_color:
|
|
135
|
+
print_formatted_text(HTML(f'<ansi{text_color}>{frag}</ansi{text_color}>'), end="")
|
|
136
|
+
else:
|
|
137
|
+
print(frag, end="")
|
|
120
138
|
|
|
121
139
|
if interaction:
|
|
122
140
|
interaction(resp, stdout)
|
|
123
141
|
if resp.peek_stderr():
|
|
124
142
|
frag = resp.read_stderr()
|
|
125
143
|
stderr.append(frag)
|
|
126
|
-
if show_out:
|
|
144
|
+
if show_out:
|
|
145
|
+
if text_color:
|
|
146
|
+
print_formatted_text(HTML(f'<ansi{text_color}>{frag}</ansi{text_color}>'), end="")
|
|
147
|
+
else:
|
|
148
|
+
print(frag, end="")
|
|
127
149
|
|
|
128
150
|
with log_exc():
|
|
129
151
|
# get the exit code from server
|
|
@@ -132,7 +154,7 @@ class Pods:
|
|
|
132
154
|
if throw_err:
|
|
133
155
|
raise e
|
|
134
156
|
else:
|
|
135
|
-
log2(e)
|
|
157
|
+
log2(e, text_color=text_color)
|
|
136
158
|
finally:
|
|
137
159
|
resp.close()
|
|
138
160
|
if s and s.sock and Pods._TEST_POD_CLOSE_SOCKET:
|
|
@@ -141,6 +163,96 @@ class Pods:
|
|
|
141
163
|
|
|
142
164
|
return PodExecResult("".join(stdout), "".join(stderr), k_command, error_output, pod=pod_name, log_file=log_file)
|
|
143
165
|
|
|
166
|
+
def exec_backgrounded(pod_name: str,
|
|
167
|
+
container: str,
|
|
168
|
+
command: str,
|
|
169
|
+
show_out = True,
|
|
170
|
+
shell = '/bin/sh',
|
|
171
|
+
log_file = None,
|
|
172
|
+
env_prefix: str = None,
|
|
173
|
+
text_color: str = None):
|
|
174
|
+
# nohup kubectl exec cs-a7b13e29bd-cs-a7b13e29bd-default-sts-0 -c cassandra -- /bin/sh -c "nohup nodetool -u cs-a7b13e29bd-superuser -pw ... repair > /tmp/qing-db/q/logs/19230320.repair-0.log 2> /tmp/qing-db/q/logs/19230320.repair-0.err &" &
|
|
175
|
+
|
|
176
|
+
command = command.strip(' &')
|
|
177
|
+
|
|
178
|
+
log_pod_file = None
|
|
179
|
+
if log_file:
|
|
180
|
+
log_pod_file = Pods.log_file_from_template(log_file, pod_name=pod_name)
|
|
181
|
+
else:
|
|
182
|
+
log_pod_file = AsyncJobs.log_file(command, pod_name=pod_name)
|
|
183
|
+
|
|
184
|
+
if env_prefix:
|
|
185
|
+
command = f'{env_prefix} {command}'
|
|
186
|
+
|
|
187
|
+
log_err_file = log_pod_file.replace('.log', '.err')
|
|
188
|
+
|
|
189
|
+
command = command.replace('"', '\\"')
|
|
190
|
+
# nohup kubectl exec cs-a7b13e29bd-cs-a7b13e29bd-default-sts-0 -c cassandra -- /bin/sh -c "nodetool -u cs-a7b13e29bd-superuser -pw ... repair &"
|
|
191
|
+
# > /tmp/qing-db/q/logs/19080002.repair-0.log 2> /tmp/qing-db/q/logs/19080002.repair-0.err &
|
|
192
|
+
cmd = f'nohup kubectl exec {pod_name} -c {container} -- {shell} -c "{command} &" > {log_pod_file} 2> {log_err_file}'
|
|
193
|
+
cmd = f'{cmd} &'
|
|
194
|
+
|
|
195
|
+
if show_out:
|
|
196
|
+
log2(cmd, text_color=text_color)
|
|
197
|
+
|
|
198
|
+
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
|
|
199
|
+
return PodExecResult(result.stdout, result.stderr, cmd, None, pod=pod_name, log_file=log_pod_file)
|
|
200
|
+
|
|
201
|
+
def exec_backgrounded_logging_to_pod(pod_name: str,
|
|
202
|
+
container: str,
|
|
203
|
+
namespace: str,
|
|
204
|
+
command: str,
|
|
205
|
+
show_out = True,
|
|
206
|
+
shell = '/bin/sh',
|
|
207
|
+
log_file = None,
|
|
208
|
+
env_prefix: str = None,
|
|
209
|
+
text_color: str = None):
|
|
210
|
+
command = command.strip(' &')
|
|
211
|
+
|
|
212
|
+
log_pod_file = None
|
|
213
|
+
if log_file:
|
|
214
|
+
log_pod_file = log_file
|
|
215
|
+
else:
|
|
216
|
+
pod_suffix = None
|
|
217
|
+
dir = None
|
|
218
|
+
if log_to_pods():
|
|
219
|
+
pod_suffix = ''
|
|
220
|
+
dir = pod_log_dir()
|
|
221
|
+
|
|
222
|
+
log_pod_file = AsyncJobs.log_file(command, job_id=AsyncJobs.new_id(), pod_suffix=pod_suffix, dir=dir)
|
|
223
|
+
|
|
224
|
+
if env_prefix:
|
|
225
|
+
command = f'{env_prefix} {command}'
|
|
226
|
+
|
|
227
|
+
log_err_file = log_pod_file.replace('.log', '.err')
|
|
228
|
+
|
|
229
|
+
command = command.replace('"', '\\"')
|
|
230
|
+
# nohup kubectl exec sts-0 -c cassandra -- /bin/sh -c "nohup nodetool -u cs-superuser -pw ... repair > /tmp/qing-db/q/logs/19230320.repair-0.log 2> /tmp/qing-db/q/logs/19230320.repair-0.err &" &
|
|
231
|
+
cmd = f'nohup kubectl exec {pod_name} -c {container} -- {shell} -c "nohup {command} > {log_pod_file} 2> {log_err_file} &"'
|
|
232
|
+
cmd = f'{cmd} &'
|
|
233
|
+
|
|
234
|
+
if show_out:
|
|
235
|
+
log2(cmd, text_color=text_color)
|
|
236
|
+
|
|
237
|
+
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
|
|
238
|
+
return PodExecResult(result.stdout, result.stderr, cmd, None, pod=pod_name, log_file=PodLogFile(log_pod_file, pod=pod_name))
|
|
239
|
+
|
|
240
|
+
def log_file_from_template(log_file: str, pod_name: str):
|
|
241
|
+
if not log_file:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
pod_suffix = pod_name
|
|
245
|
+
if pod_name:
|
|
246
|
+
if groups := re.match(r'.*-(.*)', pod_name):
|
|
247
|
+
pod_suffix = f'-{groups[1]}'
|
|
248
|
+
elif groups := re.match(r'.*_(.*)', pod_name):
|
|
249
|
+
pod_suffix = f'-{groups[1]}'
|
|
250
|
+
|
|
251
|
+
if not pod_suffix.startswith('-'):
|
|
252
|
+
pod_suffix = f'-{pod_suffix}'
|
|
253
|
+
|
|
254
|
+
return log_file.replace('{pod}', pod_suffix)
|
|
255
|
+
|
|
144
256
|
def read_file(pod_name: str, container: str, namespace: str, file_path: str):
|
|
145
257
|
v1 = client.CoreV1Api()
|
|
146
258
|
|
|
@@ -175,13 +287,15 @@ class Pods:
|
|
|
175
287
|
|
|
176
288
|
def download_file(pod_name: str, container: str, namespace: str, from_path: str, to_path: str = None):
|
|
177
289
|
if not to_path:
|
|
178
|
-
to_path = f'{
|
|
290
|
+
to_path = f'{local_downloads_dir()}/{os.path.basename(from_path)}'
|
|
179
291
|
|
|
180
292
|
bytes = Pods.read_file(pod_name, container, namespace, from_path)
|
|
181
293
|
with open(to_path, 'wb') as f:
|
|
182
294
|
for item in GeneratorStream(bytes):
|
|
183
295
|
f.write(item)
|
|
184
296
|
|
|
297
|
+
ReplSession().append_history(f':cat {to_path}')
|
|
298
|
+
|
|
185
299
|
return to_path
|
|
186
300
|
|
|
187
301
|
def get_container(namespace: str, pod_name: str, container_name: str):
|
|
@@ -294,5 +408,51 @@ class Pods:
|
|
|
294
408
|
def completed(namespace: str, pod_name: str):
|
|
295
409
|
return Pods.get(namespace, pod_name).status.phase in ['Succeeded', 'Failed']
|
|
296
410
|
|
|
297
|
-
|
|
298
|
-
|
|
411
|
+
_dirs_created = set()
|
|
412
|
+
|
|
413
|
+
def creating_dir(pod_name: str,
|
|
414
|
+
container: str,
|
|
415
|
+
namespace: str,
|
|
416
|
+
dir: str,
|
|
417
|
+
show_out = False):
|
|
418
|
+
key = f'{dir}@{pod_name}'
|
|
419
|
+
if key not in Pods._dirs_created:
|
|
420
|
+
Pods._dirs_created.add(key)
|
|
421
|
+
Pods.exec(pod_name, container, namespace, f'mkdir -p {dir}', show_out=show_out, shell='bash')
|
|
422
|
+
|
|
423
|
+
return dir
|
|
424
|
+
|
|
425
|
+
def find_files(pod: str, container: str, namespace: str, pattern: str, mmin: int = 0, remote = False):
|
|
426
|
+
stdout = ''
|
|
427
|
+
if not remote:
|
|
428
|
+
# find . -maxdepth 1 -type f -name '*'
|
|
429
|
+
dir = os.path.dirname(pattern)
|
|
430
|
+
base = os.path.basename(pattern)
|
|
431
|
+
cmd = ['find', dir, '-name', base]
|
|
432
|
+
if mmin:
|
|
433
|
+
cmd += ['-mmin', f'-{mmin}']
|
|
434
|
+
|
|
435
|
+
cmd += ["-exec", "stat", "-c", "'%n %s'", "{}", "\;"]
|
|
436
|
+
|
|
437
|
+
stdout = local_exec(cmd, show_out=Config().is_debug()).stdout
|
|
438
|
+
else:
|
|
439
|
+
cmd = f'find {pattern}'
|
|
440
|
+
if mmin:
|
|
441
|
+
cmd = f'{cmd} -mmin -{mmin}'
|
|
442
|
+
|
|
443
|
+
cmd += " -exec stat -c '%n %s' {} \;"
|
|
444
|
+
|
|
445
|
+
stdout = Pods.exec(pod, container, namespace, cmd, show_out=Config().is_debug(), shell='bash').stdout
|
|
446
|
+
|
|
447
|
+
log_files: list[PodLogFile] = []
|
|
448
|
+
for line in stdout.split('\n'):
|
|
449
|
+
line = line.strip(' \r')
|
|
450
|
+
if line:
|
|
451
|
+
tokens = line.split(' ')
|
|
452
|
+
log_files.append(PodLogFile(tokens[0], pod, size=tokens[1]))
|
|
453
|
+
|
|
454
|
+
return log_files
|
|
455
|
+
|
|
456
|
+
def file_size(pod: str, container:str, namespace: str, file: str):
|
|
457
|
+
stdout = Pods.exec(pod, container, namespace, f'stat -c %s {file}', show_out=Config().is_debug(), shell='bash').stdout
|
|
458
|
+
return stdout.strip('\r\n ')
|
adam/utils_k8s/statefulsets.py
CHANGED
|
@@ -56,8 +56,11 @@ class StatefulSets:
|
|
|
56
56
|
return statefulset_pods
|
|
57
57
|
|
|
58
58
|
@functools.lru_cache()
|
|
59
|
-
def pod_names(
|
|
60
|
-
|
|
59
|
+
def pod_names(sts: str, ns: str):
|
|
60
|
+
if not sts:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
return [pod.metadata.name for pod in StatefulSets.pods(sts, ns)]
|
|
61
64
|
|
|
62
65
|
def restarted_at(ss: str, ns: str):
|
|
63
66
|
# returns timestamp and if being rolled out
|