kaqing 2.0.188__py3-none-any.whl → 2.0.200__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (72) hide show
  1. adam/batch.py +6 -6
  2. adam/commands/bash/bash.py +1 -1
  3. adam/commands/bash/utils_bash.py +1 -1
  4. adam/commands/cassandra/__init__.py +0 -0
  5. adam/commands/cassandra/download_cassandra_log.py +45 -0
  6. adam/commands/cassandra/nodetool.py +64 -0
  7. adam/commands/cassandra/nodetool_commands.py +120 -0
  8. adam/commands/cassandra/restart_cluster.py +47 -0
  9. adam/commands/cassandra/restart_node.py +51 -0
  10. adam/commands/cassandra/restart_nodes.py +47 -0
  11. adam/commands/cassandra/rollout.py +88 -0
  12. adam/commands/cat.py +2 -2
  13. adam/commands/cd.py +2 -2
  14. adam/commands/command.py +1 -1
  15. adam/commands/commands_utils.py +8 -13
  16. adam/commands/cql/alter_tables.py +66 -0
  17. adam/commands/cql/completions_c.py +1 -0
  18. adam/commands/cql/utils_cql.py +5 -5
  19. adam/commands/debug/__init__.py +0 -0
  20. adam/commands/debug/debug.py +22 -0
  21. adam/commands/debug/debug_completes.py +35 -0
  22. adam/commands/debug/debug_timings.py +35 -0
  23. adam/commands/devices/devices.py +1 -1
  24. adam/commands/download_cassandra_log.py +45 -0
  25. adam/commands/download_file.py +3 -3
  26. adam/commands/export/export_sessions.py +1 -1
  27. adam/commands/export/exporter.py +1 -1
  28. adam/commands/find_processes.py +2 -2
  29. adam/commands/generate_report.py +52 -0
  30. adam/commands/head.py +2 -2
  31. adam/commands/ls.py +2 -2
  32. adam/commands/medusa/medusa_restore.py +0 -16
  33. adam/commands/nodetool.py +1 -1
  34. adam/commands/os/__init__.py +0 -0
  35. adam/commands/os/cat.py +36 -0
  36. adam/commands/os/download_file.py +47 -0
  37. adam/commands/os/find_files.py +51 -0
  38. adam/commands/os/find_processes.py +76 -0
  39. adam/commands/os/head.py +36 -0
  40. adam/commands/os/shell.py +41 -0
  41. adam/commands/postgres/postgres_databases.py +2 -3
  42. adam/commands/preview_table.py +1 -1
  43. adam/commands/restart_cluster.py +47 -0
  44. adam/commands/restart_node.py +51 -0
  45. adam/commands/restart_nodes.py +47 -0
  46. adam/commands/show/show_cli_commands.py +1 -1
  47. adam/config.py +4 -6
  48. adam/embedded_params.py +1 -1
  49. adam/repl.py +5 -3
  50. adam/repl_commands.py +11 -6
  51. adam/repl_session.py +4 -3
  52. adam/repl_state.py +6 -0
  53. adam/sql/async_executor.py +44 -0
  54. adam/sql/lark_completer.py +6 -4
  55. adam/sql/qingl.lark +1076 -0
  56. adam/utils.py +95 -23
  57. adam/utils_k8s/app_clusters.py +1 -1
  58. adam/utils_k8s/app_pods.py +2 -3
  59. adam/utils_k8s/cassandra_clusters.py +7 -3
  60. adam/utils_k8s/cassandra_nodes.py +8 -5
  61. adam/utils_k8s/kube_context.py +1 -4
  62. adam/utils_k8s/pods.py +55 -1
  63. adam/utils_repl/repl_completer.py +4 -87
  64. adam/version.py +1 -1
  65. {kaqing-2.0.188.dist-info → kaqing-2.0.200.dist-info}/METADATA +1 -1
  66. {kaqing-2.0.188.dist-info → kaqing-2.0.200.dist-info}/RECORD +69 -45
  67. adam/commands/logs.py +0 -37
  68. adam/commands/report.py +0 -61
  69. adam/commands/restart.py +0 -60
  70. {kaqing-2.0.188.dist-info → kaqing-2.0.200.dist-info}/WHEEL +0 -0
  71. {kaqing-2.0.188.dist-info → kaqing-2.0.200.dist-info}/entry_points.txt +0 -0
  72. {kaqing-2.0.188.dist-info → kaqing-2.0.200.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,76 @@
1
+ from adam.commands import extract_options, validate_args
2
+ from adam.commands.command import Command
3
+ from adam.commands.devices.devices import Devices
4
+ from adam.commands.export.utils_export import state_with_pod
5
+ from adam.repl_state import ReplState, RequiredState
6
+ from adam.utils import log2, tabulize
7
+
8
+ class FindProcesses(Command):
9
+ COMMAND = 'find processes'
10
+
11
+ # the singleton pattern
12
+ def __new__(cls, *args, **kwargs):
13
+ if not hasattr(cls, 'instance'): cls.instance = super(FindProcesses, cls).__new__(cls)
14
+
15
+ return cls.instance
16
+
17
+ def __init__(self, successor: Command=None):
18
+ super().__init__(successor)
19
+
20
+ def command(self):
21
+ return FindProcesses.COMMAND
22
+
23
+ def required(self):
24
+ return [RequiredState.CLUSTER_OR_POD, RequiredState.APP_APP, ReplState.P]
25
+
26
+ def run(self, cmd: str, state: ReplState):
27
+ if not(args := self.args(cmd)):
28
+ return super().run(cmd, state)
29
+
30
+ with self.validate(args, state) as (args, state):
31
+ with extract_options(args, '-kill') as (args, kill):
32
+ with validate_args(args, state, name='words to look for'):
33
+ arg = ' | '.join([f'grep {a}' for a in args])
34
+ awk = "awk '{ print $1, $2, $8, $NF }'"
35
+ rs = Devices.of(state).bash(state, state, f"ps -ef | grep -v grep | {arg} | {awk}".split(' '))
36
+
37
+ lines: list[list[str]] = []
38
+ for r in rs:
39
+ for l in r.stdout.split('\n'):
40
+ l = l.strip(' \t\r\n')
41
+ if not l:
42
+ continue
43
+
44
+ tokens = [r.pod] + l.split(' ')
45
+ lines.append(tokens)
46
+
47
+ pids = []
48
+ for l in lines:
49
+ pids.append(f'{l[2]}@{l[0]}')
50
+
51
+ tabulize(lines, lambda l: '\t'.join(l), header = 'POD\tUSER\tPID\tCMD\tLAST_ARG', separator='\t')
52
+ log2()
53
+ log2(f'PIDS with {",".join(args)}: {",".join(pids)}')
54
+
55
+ if kill:
56
+ log2()
57
+ for pidp in pids:
58
+ pid_n_pod = pidp.split('@')
59
+ pid = pid_n_pod[0]
60
+ if len(pid_n_pod) < 2:
61
+ continue
62
+
63
+ pod = pid_n_pod[1]
64
+
65
+ log2(f'@{pod} bash kill -9 {pid}')
66
+
67
+ with state_with_pod(state, pod) as state1:
68
+ Devices.of(state).bash(state, state1, ['kill', '-9', pid])
69
+
70
+ return rs
71
+
72
+ def completion(self, state: ReplState):
73
+ return super().completion(state)
74
+
75
+ def help(self, _: ReplState):
76
+ return f'{FindProcesses.COMMAND} word... [-kill]\t find processes with words'
@@ -0,0 +1,36 @@
1
+ from adam.commands import validate_args
2
+ from adam.commands.command import Command
3
+ from adam.commands.devices.devices import Devices
4
+ from adam.repl_state import ReplState, RequiredState
5
+
6
+ class Head(Command):
7
+ COMMAND = 'head'
8
+
9
+ # the singleton pattern
10
+ def __new__(cls, *args, **kwargs):
11
+ if not hasattr(cls, 'instance'): cls.instance = super(Head, cls).__new__(cls)
12
+
13
+ return cls.instance
14
+
15
+ def __init__(self, successor: Command=None):
16
+ super().__init__(successor)
17
+
18
+ def command(self):
19
+ return Head.COMMAND
20
+
21
+ def required(self):
22
+ return [RequiredState.CLUSTER_OR_POD, RequiredState.APP_APP, ReplState.P]
23
+
24
+ def run(self, cmd: str, state: ReplState):
25
+ if not(args := self.args(cmd)):
26
+ return super().run(cmd, state)
27
+
28
+ with self.validate(args, state) as (args, state):
29
+ with validate_args(args, state, name='file'):
30
+ return Devices.of(state).bash(state, state, cmd.split(' '))
31
+
32
+ def completion(self, state: ReplState):
33
+ return super().completion(state, lambda: {f: None for f in Devices.of(state).files(state)}, pods=Devices.of(state).pods(state, '-'), auto='jit')
34
+
35
+ def help(self, _: ReplState):
36
+ return f'{Head.COMMAND} file [&]\t run head command on the pod'
@@ -0,0 +1,41 @@
1
+ import os
2
+
3
+ from adam.commands import validate_args
4
+ from adam.commands.command import Command
5
+ from adam.repl_state import ReplState
6
+ from adam.utils import log2
7
+
8
+ class Shell(Command):
9
+ COMMAND = ':sh'
10
+
11
+ # the singleton pattern
12
+ def __new__(cls, *args, **kwargs):
13
+ if not hasattr(cls, 'instance'): cls.instance = super(Shell, cls).__new__(cls)
14
+
15
+ return cls.instance
16
+
17
+ def __init__(self, successor: Command=None):
18
+ super().__init__(successor)
19
+
20
+ def command(self):
21
+ return Shell.COMMAND
22
+
23
+ def run(self, cmd: str, state: ReplState):
24
+ if not(args := self.args(cmd)):
25
+ return super().run(cmd, state)
26
+
27
+ with self.validate(args, state) as (args, _):
28
+ with validate_args(args, state, at_least=0) as args_str:
29
+ if args_str:
30
+ os.system(args_str)
31
+ log2()
32
+ else:
33
+ os.system('QING_DROPPED=true bash')
34
+
35
+ return state
36
+
37
+ def completion(self, state: ReplState):
38
+ return super().completion(state)
39
+
40
+ def help(self, _: ReplState):
41
+ return f'{Shell.COMMAND}\t drop down to shell'
@@ -144,9 +144,8 @@ class PostgresDatabases:
144
144
  env_prefix = f'PGPASSWORD="{password}"'
145
145
 
146
146
  r = Pods.exec(pod_name, container_name, state.namespace, cmd, show_out=show_out, backgrounded=backgrounded, env_prefix=env_prefix)
147
- if r and Config().get('repl.history.push-cat-remote-log-file', True):
148
- if r.log_file and ReplSession().prompt_session:
149
- ReplSession().prompt_session.history.append_string(f'@{r.pod} cat {r.log_file}')
147
+ if r and r.log_file:
148
+ ReplSession().append_history(f'@{r.pod} cat {r.log_file}')
150
149
 
151
150
  return r
152
151
 
@@ -27,7 +27,7 @@ class PreviewTable(Command):
27
27
 
28
28
  with self.validate(args, state) as (args, state):
29
29
  with validate_args(args, state, at_least=1) as table:
30
- Devices.device(state).preview(table, state)
30
+ Devices.of(state).preview(table, state)
31
31
 
32
32
  return state
33
33
 
@@ -0,0 +1,47 @@
1
+ from adam.commands import extract_options
2
+ from adam.commands.command import Command
3
+ from adam.utils_k8s.pods import Pods
4
+ from adam.utils_k8s.statefulsets import StatefulSets
5
+ from adam.repl_state import ReplState, RequiredState
6
+ from adam.utils import log2
7
+
8
+ class RestartCluster(Command):
9
+ COMMAND = 'restart cluster'
10
+
11
+ # the singleton pattern
12
+ def __new__(cls, *args, **kwargs):
13
+ if not hasattr(cls, 'instance'): cls.instance = super(RestartCluster, cls).__new__(cls)
14
+
15
+ return cls.instance
16
+
17
+ def __init__(self, successor: Command=None):
18
+ super().__init__(successor)
19
+
20
+ def command(self):
21
+ return RestartCluster.COMMAND
22
+
23
+ def required(self):
24
+ return RequiredState.CLUSTER
25
+
26
+ def run(self, cmd: str, state: ReplState):
27
+ if not(args := self.args(cmd)):
28
+ return super().run(cmd, state)
29
+
30
+ with self.validate(args, state) as (args, state):
31
+ with extract_options(args, '--force') as (args, forced):
32
+ if not forced:
33
+ log2('Please add --force for restarting all nodes in a cluster.')
34
+
35
+ return 'force-needed'
36
+
37
+ log2(f'Restarting all pods from {state.sts}...')
38
+ for pod_name in StatefulSets.pod_names(state.sts, state.namespace):
39
+ Pods.delete(pod_name, state.namespace)
40
+
41
+ return state
42
+
43
+ def completion(self, state: ReplState):
44
+ return super().completion(state, {'--force': None})
45
+
46
+ def help(self, _: ReplState):
47
+ return f"{RestartCluster.COMMAND} --force\t restart all the nodes in the cluster"
@@ -0,0 +1,51 @@
1
+ from adam.commands import extract_options
2
+ from adam.commands.command import Command
3
+ from adam.commands.devices.devices import Devices
4
+ from adam.utils_k8s.pods import Pods
5
+ from adam.repl_state import ReplState, RequiredState
6
+ from adam.utils import log2
7
+
8
+ class RestartNode(Command):
9
+ COMMAND = 'restart node'
10
+
11
+ # the singleton pattern
12
+ def __new__(cls, *args, **kwargs):
13
+ if not hasattr(cls, 'instance'): cls.instance = super(RestartNode, cls).__new__(cls)
14
+
15
+ return cls.instance
16
+
17
+ def __init__(self, successor: Command=None):
18
+ super().__init__(successor)
19
+
20
+ def command(self):
21
+ return RestartNode.COMMAND
22
+
23
+ def required(self):
24
+ return RequiredState.POD
25
+
26
+ def run(self, cmd: str, state: ReplState):
27
+ if not(args := self.args(cmd)):
28
+ return super().run(cmd, state)
29
+
30
+ with self.validate(args, state) as (args, state):
31
+ if not state.pod:
32
+ log2("'pod' is required")
33
+
34
+ return 'pod-needed'
35
+
36
+ with extract_options(args, '--force') as (args, forced):
37
+ if not forced:
38
+ log2('Please add --force for restarting pod.')
39
+
40
+ return 'force-needed'
41
+
42
+ log2(f'Restarting {state.pod}...')
43
+ Pods.delete(state.pod, state.namespace)
44
+
45
+ return state
46
+
47
+ def completion(self, state: ReplState):
48
+ return super().completion(state, {'--force': None}, pods=Devices.of(state).pods(state, '-'))
49
+
50
+ def help(self, _: ReplState):
51
+ return f"{RestartNode.COMMAND} --force\t restart the node"
@@ -0,0 +1,47 @@
1
+ from adam.commands import extract_options, validate_args
2
+ from adam.commands.command import Command
3
+ from adam.utils_k8s.pods import Pods
4
+ from adam.utils_k8s.statefulsets import StatefulSets
5
+ from adam.repl_state import ReplState, RequiredState
6
+ from adam.utils import log2
7
+
8
+ class RestartNodes(Command):
9
+ COMMAND = 'restart nodes'
10
+
11
+ # the singleton pattern
12
+ def __new__(cls, *args, **kwargs):
13
+ if not hasattr(cls, 'instance'): cls.instance = super(RestartNodes, cls).__new__(cls)
14
+
15
+ return cls.instance
16
+
17
+ def __init__(self, successor: Command=None):
18
+ super().__init__(successor)
19
+
20
+ def command(self):
21
+ return RestartNodes.COMMAND
22
+
23
+ def required(self):
24
+ return RequiredState.CLUSTER
25
+
26
+ def run(self, cmd: str, state: ReplState):
27
+ if not(args := self.args(cmd)):
28
+ return super().run(cmd, state)
29
+
30
+ with self.validate(args, state, apply=False) as (args, state):
31
+ with extract_options(args, '--force') as (args, forced):
32
+ with validate_args(args, state, name='pod name'):
33
+ if not forced:
34
+ log2('Please add --force for restarting nodes.')
35
+
36
+ return 'force-needed'
37
+
38
+ for arg in args:
39
+ Pods.delete(arg, state.namespace)
40
+
41
+ return state
42
+
43
+ def completion(self, state: ReplState):
44
+ return super().completion(state, lambda: {p: {'--force': None} for p in StatefulSets.pod_names(state.sts, state.namespace)})
45
+
46
+ def help(self, _: ReplState):
47
+ return f"{RestartNodes.COMMAND} <pod-name>... --force\t restart Cassandra nodes"
@@ -22,7 +22,7 @@ class ShowKubectlCommands(Command):
22
22
  return RequiredState.CLUSTER_OR_POD
23
23
 
24
24
  def run(self, cmd: str, state: ReplState):
25
- if not self.args(cmd):
25
+ if not (args := self.args(cmd)):
26
26
  return super().run(cmd, state)
27
27
 
28
28
  with self.validate(args, state) as (args, state):
adam/config.py CHANGED
@@ -3,17 +3,13 @@ from typing import TypeVar, cast
3
3
  import yaml
4
4
 
5
5
  from . import __version__
6
- from adam.utils import LogConfig, copy_config_file, get_deep_keys, log2
6
+ from adam.utils import ConfigHolder, ConfigReadable, copy_config_file, get_deep_keys, log2
7
7
 
8
8
  T = TypeVar('T')
9
9
 
10
- class Config:
10
+ class Config(ConfigReadable):
11
11
  EMBEDDED_PARAMS = {}
12
12
 
13
- LogConfig.is_debug = lambda: Config().is_debug()
14
- LogConfig.is_debug_complete = lambda: Config().get('debugs.complete', False)
15
- LogConfig.is_debug_timing = lambda: Config().get('debugs.timings', False)
16
-
17
13
  # the singleton pattern
18
14
  def __new__(cls, *args, **kwargs):
19
15
  if not hasattr(cls, 'instance'): cls.instance = super(Config, cls).__new__(cls)
@@ -29,6 +25,8 @@ class Config:
29
25
  except:
30
26
  with open(copy_config_file(f'params.yaml.{__version__}', 'adam.embedded_params', show_out=not is_user_entry)) as f:
31
27
  self.params = cast(dict[str, any], yaml.safe_load(f))
28
+
29
+ ConfigHolder().config = self
32
30
  elif not hasattr(self, 'params'):
33
31
  with open(copy_config_file(f'params.yaml.{__version__}', 'adam.embedded_params', show_out=not is_user_entry)) as f:
34
32
  self.params = cast(dict[str, any], yaml.safe_load(f))
adam/embedded_params.py CHANGED
@@ -1,2 +1,2 @@
1
1
  def config():
2
- return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'container-name': 'c3-server', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'repair-partition-tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results', 'repair-cluster-tables': 'cluster'}, 'queries': {'last10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY ts DESC LIMIT {limit}", 'slow10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY CAST(duration AS REAL) DESC LIMIT {limit}", 'top10': "SELECT min(c) AS cluster, line, COUNT(*) AS cnt, avg(CAST(duration AS REAL)) AS duration\nFROM audit WHERE drive <> 'z' and ({date_condition})\nGROUP BY line ORDER BY cnt DESC LIMIT {limit}"}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'download': {'workers': 8}, 'export': {'workers': 8, 'csv_dir': '/c3/cassandra/tmp', 'column_counts_query': 'select id, count(id) as columns from {table} group by id order by columns desc limit 10', 'default-importer': 'sqlite', 'sqlite': {'workers': 8, 'columns': '<row-key>', 'local-db-dir': '/tmp/qing-db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}, 'log-prefix': '/tmp/qing'}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'local-tmp-dir': '/tmp/qing-db', 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-prefix': '/tmp/qing', 'nodetool': {'workers': 96, 'commands_in_line': 40, 'status': {'workers': 32, 'samples': 3, 'commands_in_line': 40}}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu-metrics,mem', 'header': 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT'}, 'processes-qing': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'c', 'a': {'auto-enter': 'c3/c3'}, 'c': {'auto-enter': 'cluster'}, 'x': {'auto-enter': 'latest'}, 'history': {'push-cat-log-file': True, 'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'auto-complete': {'c': {'tables': 'lazy'}, 'x': {'tables': 'lazy'}, 'cli': {'cp': 'jit'}, 'export': {'databases': 'jit'}, 'medusa': {'backups': 'jit'}, 'reaper': {'schedules': 'lazy'}}, 'debug': False, 'debugs': {'timings': False, 'exit-on-error': False, 'show-parallelism': False}}
2
+ return {'app': {'console-endpoint': 'https://{host}/{env}/{app}/static/console/index.html', 'container-name': 'c3-server', 'cr': {'cluster-regex': '(.*?-.*?)-.*', 'group': 'ops.c3.ai', 'v': 'v2', 'plural': 'c3cassandras'}, 'label': 'c3__app_id-0', 'login': {'admin-group': '{host}/C3.ClusterAdmin', 'ingress': '{app_id}-k8singr-appleader-001', 'timeout': 5, 'session-check-url': 'https://{host}/{env}/{app}/api/8/C3/userSessionToken', 'cache-creds': True, 'cache-username': True, 'url': 'https://{host}/{env}/{app}', 'another': "You're logged in to {has}. However, for this app, you need to log in to {need}.", 'token-server-url': 'http://localhost:{port}', 'password-max-length': 128}, 'strip': '0'}, 'audit': {'endpoint': 'https://4psvtaxlcb.execute-api.us-west-2.amazonaws.com/prod/', 'workers': 3, 'timeout': 10, 'log-audit-queries': False, 'athena': {'auto-repair': {'elapsed_hours': 12}, 'region': 'us-west-2', 'catalog': 'AwsDataCatalog', 'database': 'audit', 'repair-partition-tables': 'audit', 'output': 's3://s3.ops--audit/ddl/results', 'repair-cluster-tables': 'cluster'}, 'queries': {'last10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY ts DESC LIMIT {limit}", 'slow10': "SELECT * FROM audit\nWHERE drive <> 'z' and ({date_condition})\nORDER BY CAST(duration AS REAL) DESC LIMIT {limit}", 'top10': "SELECT min(c) AS cluster, line, COUNT(*) AS cnt, avg(CAST(duration AS REAL)) AS duration\nFROM audit WHERE drive <> 'z' and ({date_condition})\nGROUP BY line ORDER BY cnt DESC LIMIT {limit}"}}, 'bash': {'workers': 32}, 'cassandra': {'service-name': 'all-pods-service'}, 'cql': {'workers': 32, 'samples': 3, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-superuser', 'password-item': 'password'}, 'alter-tables': {'excludes': 'system_auth,system_traces,reaper_db,system_distributed,system_views,system,system_schema,system_virtual_schema', 'gc-grace-periods': '3600,86400,864000,7776000', 'batching': True}}, 'checks': {'compactions-threshold': 250, 'cpu-busy-threshold': 98.0, 'cpu-threshold': 0.0, 'cassandra-data-path': '/c3/cassandra', 'root-disk-threshold': 50, 'cassandra-disk-threshold': 50, 'snapshot-size-cmd': "ls /c3/cassandra/data/data/*/*/snapshots | grep snapshots | sed 's/:$//g' | xargs -I {} du -sk {} | awk '{print $1}' | awk '{s+=$1} END {print s}'", 'snapshot-size-threshold': '40G', 'table-sizes-cmd': "ls -Al /c3/cassandra/data/data/ | awk '{print $9}' | sed 's/\\^r//g' | xargs -I {} du -sk /c3/cassandra/data/data/{}"}, 'download': {'workers': 8}, 'export': {'workers': 8, 'csv_dir': '/c3/cassandra/tmp', 'column_counts_query': 'select id, count(id) as columns from {table} group by id order by columns desc limit 10', 'default-importer': 'sqlite', 'sqlite': {'workers': 8, 'columns': '<row-key>', 'local-db-dir': '/tmp/qing-db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}, 'log-prefix': '/tmp/qing'}, 'get-host-id': {'workers': 32}, 'idps': {'ad': {'email-pattern': '.*@c3.ai', 'uri': 'https://login.microsoftonline.com/53ad779a-93e7-485c-ba20-ac8290d7252b/oauth2/v2.0/authorize?response_type=id_token&response_mode=form_post&client_id=00ff94a8-6b0a-4715-98e0-95490012d818&scope=openid+email+profile&redirect_uri=https%3A%2F%2Fplat.c3ci.cloud%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://login.microsoftonline.com/common/discovery/keys', 'contact': 'Please contact ted.tran@c3.ai.', 'whitelist-file': '/kaqing/members'}, 'okta': {'default': True, 'email-pattern': '.*@c3iot.com', 'uri': 'https://c3energy.okta.com/oauth2/v1/authorize?response_type=id_token&response_mode=form_post&client_id={client_id}&scope=openid+email+profile+groups&redirect_uri=https%3A%2F%2F{host}%2Fc3%2Fc3%2Foidc%2Flogin&nonce={nonce}&state=EMPTY', 'jwks-uri': 'https://c3energy.okta.com/oauth2/v1/keys'}}, 'issues': {'workers': 32}, 'local-tmp-dir': '/tmp/qing-db', 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-prefix': '/tmp/qing', 'nodetool': {'workers': 96, 'commands_in_line': 40, 'status': {'workers': 32, 'samples': 3, 'commands_in_line': 40}}, 'pg': {'name-pattern': '^{namespace}.*-k8spg-.*', 'excludes': '.helm., -admin-secret', 'agent': {'name': 'ops-pg-agent', 'just-in-time': False, 'timeout': 86400, 'image': 'seanahnsf/kaqing'}, 'default-db': 'postgres', 'default-schema': 'postgres', 'secret': {'endpoint-key': 'postgres-db-endpoint', 'port-key': 'postgres-db-port', 'username-key': 'postgres-admin-username', 'password-key': 'postgres-admin-password'}}, 'pod': {'name': 'ops', 'image': 'seanahnsf/kaqing-cloud', 'sa': {'name': 'ops', 'proto': 'c3', 'additional-cluster-roles': 'c3aiops-k8ssandra-operator'}, 'label-selector': 'run=ops'}, 'preview': {'rows': 10}, 'processes': {'columns': 'pod,cpu-metrics,mem', 'header': 'POD_NAME,M_CPU(USAGE/LIMIT),MEM/LIMIT'}, 'processes-qing': {'columns': 'pod,cpu,mem', 'header': 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT'}, 'reaper': {'service-name': 'reaper-service', 'port-forward': {'timeout': 86400, 'local-port': 9001}, 'abort-runs-batch': 10, 'show-runs-batch': 100, 'pod': {'cluster-regex': '(.*?-.*?-.*?-.*?)-.*', 'label-selector': 'k8ssandra.io/reaper={cluster}-reaper'}, 'secret': {'cluster-regex': '(.*?-.*?)-.*', 'name': '{cluster}-reaper-ui', 'password-item': 'password'}}, 'repair': {'log-path': '/home/cassrepair/logs/', 'image': 'ci-registry.c3iot.io/cloudops/cassrepair:2.0.14', 'secret': 'ciregistryc3iotio', 'env': {'interval': 24, 'timeout': 60, 'pr': False, 'runs': 1}}, 'repl': {'start-drive': 'c', 'a': {'auto-enter': 'c3/c3'}, 'c': {'auto-enter': 'cluster'}, 'x': {'auto-enter': 'latest'}, 'history': {'push-cat-log-file': True, 'push-cat-remote-log-file': True}, 'background-process': {'auto-nohup': True, 'via-sh': True}}, 'status': {'columns': 'status,address,load,tokens,owns,host_id,gossip,compactions', 'header': '--,Address,Load,Tokens,Owns,Host ID,GOSSIP,COMPACTIONS'}, 'storage': {'columns': 'pod,volume_root,volume_cassandra,snapshots,data,compactions', 'header': 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS'}, 'watch': {'auto': 'rollout', 'timeout': 3600, 'interval': 10}, 'auto-complete': {'c': {'tables': 'lazy'}, 'x': {'tables': 'lazy'}, 'cli': {'cp': 'jit'}, 'export': {'databases': 'jit'}, 'medusa': {'backups': 'jit'}, 'reaper': {'schedules': 'jit'}}, 'debug': False, 'debugs': {'complete': False, 'timings': False, 'exit-on-error': False}}
adam/repl.py CHANGED
@@ -16,7 +16,7 @@ from adam.log import Log
16
16
  from adam.repl_commands import ReplCommands
17
17
  from adam.repl_session import ReplSession
18
18
  from adam.repl_state import ReplState
19
- from adam.utils import clear_wait_log_flag, debug_trace, deep_sort_dict, tabulize, log2, log_exc, log_timing
19
+ from adam.utils import CommandLog, clear_wait_log_flag, debug_trace, deep_sort_dict, tabulize, log2, log_exc, log_timing
20
20
  from adam.apps import Apps
21
21
  from adam.utils_repl.repl_completer import ReplCompleter, merge_completions
22
22
  from . import __version__
@@ -43,7 +43,7 @@ def enter_repl(state: ReplState):
43
43
 
44
44
  Log.log2(f'kaqing {__version__}')
45
45
 
46
- Devices.device(state).enter(state)
46
+ Devices.of(state).enter(state)
47
47
 
48
48
  kb = KeyBindings()
49
49
 
@@ -142,8 +142,10 @@ def enter_repl(state: ReplState):
142
142
  if cmd and (state.device != ReplState.L or Config().get('audit.log-audit-queries', False)):
143
143
  exec.submit(Audits.log, cmd, state.namespace, state.device, time.time() - s0, get_audit_extra(result))
144
144
 
145
+ CommandLog.close_log_file()
146
+
145
147
  def try_device_default_action(state: ReplState, cmds: Command, cmd_list: list[Command], cmd: str):
146
- action_taken, result = Devices.device(state).try_fallback_action(cmds, state, cmd)
148
+ action_taken, result = Devices.of(state).try_fallback_action(cmds, state, cmd)
147
149
 
148
150
  if not action_taken:
149
151
  log2(f'* Invalid command: {cmd}')
adam/repl_commands.py CHANGED
@@ -7,6 +7,9 @@ from adam.commands.app.show_app_queues import ShowAppQueues
7
7
  from adam.commands.audit.audit import Audit
8
8
  from adam.commands.cat import Cat
9
9
  from adam.commands.code import Code
10
+ from adam.commands.debug.debug import Debug
11
+ from adam.commands.debug.debug_timings import DebugTimings
12
+ from adam.commands.download_cassandra_log import DownloadCassandraLog
10
13
  from adam.commands.download_file import DownloadFile
11
14
  from adam.commands.deploy.code_start import CodeStart
12
15
  from adam.commands.deploy.code_stop import CodeStop
@@ -42,6 +45,8 @@ from adam.commands.find_files import FindLocalFiles
42
45
  from adam.commands.find_processes import FindProcesses
43
46
  from adam.commands.head import Head
44
47
  from adam.commands.kubectl import Kubectl
48
+ from adam.commands.restart_cluster import RestartCluster
49
+ from adam.commands.restart_node import RestartNode
45
50
  from adam.commands.shell import Shell
46
51
  from adam.commands.clipboard_copy import ClipboardCopy
47
52
  from adam.commands.bash.bash import Bash
@@ -60,8 +65,8 @@ from adam.commands.preview_table import PreviewTable
60
65
  from adam.commands.pwd import Pwd
61
66
  from adam.commands.reaper.reaper import Reaper
62
67
  from adam.commands.repair.repair import Repair
63
- from adam.commands.report import Report
64
- from adam.commands.restart import Restart
68
+ from adam.commands.generate_report import GenerateReport
69
+ from adam.commands.restart_nodes import RestartNodes
65
70
  from adam.commands.rollout import RollOut
66
71
  from adam.commands.param_set import SetParam
67
72
  from adam.commands.show.show import Show
@@ -82,7 +87,7 @@ class ReplCommands:
82
87
  cmds: list[Command] = ReplCommands.navigation() + ReplCommands.cassandra_ops() + ReplCommands.postgres_ops() + \
83
88
  ReplCommands.app_ops() + ReplCommands.audit_ops() + ReplCommands.export_ops() + ReplCommands.tools() + ReplCommands.exit()
84
89
 
85
- intermediate_cmds: list[Command] = [App(), Audit(), Reaper(), Repair(), Deploy(), Show(), Undeploy()]
90
+ intermediate_cmds: list[Command] = [App(), Audit(), Reaper(), Repair(), Debug(), Deploy(), Show(), Undeploy()]
86
91
  ic = [c.command() for c in intermediate_cmds]
87
92
  # 1. dedup commands
88
93
  deduped = []
@@ -104,13 +109,13 @@ class ReplCommands:
104
109
  GetParam(), SetParam(), ShowParams(), ShowKubectlCommands(), ShowLogin(), ShowAdam(), ShowHost()]
105
110
 
106
111
  def cassandra_ops() -> list[Command]:
107
- return [Cqlsh(), ShowCassandraStatus(), ShowCassandraVersion(), ShowCassandraRepairs(), ShowStorage(), ShowProcesses(),
108
- Check(), Issues(), NodeTool(), Report(), AlterTables(), Bash(),
112
+ return [Cqlsh(), DownloadCassandraLog(), ShowCassandraStatus(), ShowCassandraVersion(), ShowCassandraRepairs(), ShowStorage(), ShowProcesses(),
113
+ Check(), Issues(), NodeTool(), GenerateReport(), AlterTables(), Bash(),
109
114
  ExportTables(), ExportXSelect(), ExportUse(), ShowExportDatabases(), ShowColumnCounts(),
110
115
  DropExportDatabase(), DropExportDatabases(),
111
116
  ShowExportSessions(), ShowExportSession(), DownloadExportSession(),
112
117
  CleanUpExportSessions(), CleanUpAllExportSessions(), ImportSession(), ImportCSVFiles()] + \
113
- Medusa().cmd_list() + [Restart(), RollOut(), Watch()] + Reaper().cmd_list() + Repair().cmd_list()
118
+ Medusa().cmd_list() + [RestartNodes(), RestartNode(), RestartCluster(), RollOut(), Watch()] + Reaper().cmd_list() + Repair().cmd_list() + Debug().cmd_list()
114
119
 
115
120
  def postgres_ops() -> list[Command]:
116
121
  return [Postgres(), DeployPgAgent(), UndeployPgAgent(), PostgresPg()]
adam/repl_session.py CHANGED
@@ -2,6 +2,7 @@ from prompt_toolkit import PromptSession
2
2
  from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
3
3
 
4
4
  from adam.config import Config
5
+ from adam.utils import ConfigHolder
5
6
 
6
7
  class ReplSession:
7
8
  # the singleton pattern
@@ -13,8 +14,8 @@ class ReplSession:
13
14
  def __init__(self):
14
15
  if not hasattr(self, 'prompt_session'):
15
16
  self.prompt_session = PromptSession(auto_suggest=AutoSuggestFromHistory())
17
+ ConfigHolder().append_command_history = self.append_history
16
18
 
17
19
  def append_history(self, entry: str):
18
- if entry and Config().get('repl.history.push-cat-remote-log-file', True):
19
- if self.prompt_session:
20
- self.prompt_session.history.append_string(entry)
20
+ if entry and self.prompt_session and Config().get('repl.history.push-cat-remote-log-file', True):
21
+ self.prompt_session.history.append_string(entry)
adam/repl_state.py CHANGED
@@ -431,6 +431,12 @@ class ReplState:
431
431
 
432
432
  return state1
433
433
 
434
+ def with_pod(self, pod: str):
435
+ state1 = copy(self)
436
+ state1.pod = pod
437
+
438
+ return state1
439
+
434
440
  class DevicePodService:
435
441
  def __init__(self, handler: 'DeviceExecHandler'):
436
442
  self.handler = handler
@@ -0,0 +1,44 @@
1
+ import asyncio
2
+ from concurrent.futures import ThreadPoolExecutor
3
+ import inspect
4
+ import threading
5
+ import traceback
6
+
7
+ from adam.utils import log2, log_timing
8
+
9
+ class AsyncExecutor:
10
+ # some lib does not handle asyncio loop properly, as sync exec submit does not work, use another async loop
11
+
12
+ lock = threading.Lock()
13
+ in_queue = set()
14
+
15
+ loop: asyncio.AbstractEventLoop = None
16
+ async_exec: ThreadPoolExecutor = None
17
+
18
+ def preload(action: callable, log_key: str = None):
19
+ with AsyncExecutor.lock:
20
+ if not AsyncExecutor.loop:
21
+ AsyncExecutor.loop = asyncio.new_event_loop()
22
+ AsyncExecutor.async_exec = ThreadPoolExecutor(max_workers=6, thread_name_prefix='async')
23
+ AsyncExecutor.loop.set_default_executor(AsyncExecutor.async_exec)
24
+
25
+ async def a():
26
+ try:
27
+ arg_needed = len(action.__code__.co_varnames)
28
+
29
+ if log_key:
30
+ with log_timing(log_key):
31
+ r = action(None) if arg_needed else action()
32
+ else:
33
+ r = action(None) if arg_needed else action()
34
+ if inspect.isawaitable(r):
35
+ await r
36
+
37
+ AsyncExecutor.in_queue.remove(log_key)
38
+ except Exception as e:
39
+ log2('preloading error', e, inspect.getsourcelines(action)[0][0])
40
+ traceback.print_exc()
41
+
42
+ if log_key not in AsyncExecutor.in_queue:
43
+ AsyncExecutor.in_queue.add(log_key)
44
+ AsyncExecutor.async_exec.submit(lambda: AsyncExecutor.loop.run_until_complete(a()))
@@ -5,10 +5,11 @@ from prompt_toolkit.completion import CompleteEvent, Completer, Completion, Nest
5
5
  from prompt_toolkit.document import Document
6
6
 
7
7
  from adam.config import Config
8
+ from adam.sql.async_executor import AsyncExecutor
8
9
  from adam.sql.lark_parser import LarkParser
9
10
  from adam.utils import debug, log_timing, offload
10
11
  from adam.utils_repl.appendable_completer import AppendableCompleter
11
- from adam.utils_repl.repl_completer import merge_completions, preload
12
+ from adam.utils_repl.repl_completer import merge_completions
12
13
 
13
14
  __all__ = [
14
15
  "LarkCompleter",
@@ -33,7 +34,7 @@ class LarkCompleter(Completer, AppendableCompleter):
33
34
  self.nested: NestedCompleter = None
34
35
  self.options_lambda = options_lambda
35
36
  if options_lambda and auto == 'lazy':
36
- preload(options_lambda, log_key=name)
37
+ AsyncExecutor.preload(options_lambda, log_key=name)
37
38
 
38
39
  self.variant = variant
39
40
  self.parser = None
@@ -60,7 +61,7 @@ class LarkCompleter(Completer, AppendableCompleter):
60
61
  for key, value in self.expandables.items():
61
62
  if callable(value):
62
63
  if self.auto_complete(key) == 'lazy':
63
- preload(value, log_key=key)
64
+ AsyncExecutor.preload(value, log_key=key)
64
65
 
65
66
  def from_lambda(name: str, options_lambda: callable, auto: str = 'lazy'):
66
67
  return LarkCompleter(name=name, options_lambda=options_lambda, auto=auto, variant=None)
@@ -131,8 +132,9 @@ class LarkCompleter(Completer, AppendableCompleter):
131
132
  'show_column_counts_command.path.identifier_ref': 'tables',
132
133
  'export_statement.export_tables.keyspace_name': 'keyspaces',
133
134
 
134
- 'alter_cql_table_statement.properties.property.property_name': 'table-props',
135
135
  'alter_tables_statement.properties.property.property_name': 'table-props',
136
+ 'alter_cql_table_statement.properties.property.property_name': 'table-props',
137
+ 'alter_tables_statement.properties.property.property_value.literal': 'table-props-value',
136
138
  'alter_cql_table_statement.properties.property.property_value.literal': 'table-props-value',
137
139
 
138
140
  'select_clause.projection.result_expr.expr.path.identifier_ref': 'columns',