kaqing 2.0.188__py3-none-any.whl → 2.0.211__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 (78) hide show
  1. adam/batch.py +7 -7
  2. adam/commands/app/utils_app.py +1 -1
  3. adam/commands/bash/bash.py +1 -1
  4. adam/commands/bash/utils_bash.py +1 -1
  5. adam/commands/cassandra/__init__.py +0 -0
  6. adam/commands/command.py +1 -1
  7. adam/commands/commands_utils.py +8 -13
  8. adam/commands/{alter_tables.py → cql/alter_tables.py} +1 -1
  9. adam/commands/cql/completions_c.py +1 -0
  10. adam/commands/cql/utils_cql.py +14 -13
  11. adam/commands/debug/__init__.py +0 -0
  12. adam/commands/debug/debug.py +22 -0
  13. adam/commands/debug/debug_completes.py +35 -0
  14. adam/commands/debug/debug_timings.py +35 -0
  15. adam/commands/devices/device.py +1 -1
  16. adam/commands/devices/devices.py +1 -1
  17. adam/commands/download_cassandra_log.py +45 -0
  18. adam/commands/export/export_databases.py +13 -8
  19. adam/commands/export/export_sessions.py +12 -11
  20. adam/commands/export/exporter.py +140 -53
  21. adam/commands/export/import_session.py +0 -4
  22. adam/commands/export/importer.py +11 -11
  23. adam/commands/export/importer_athena.py +15 -6
  24. adam/commands/export/importer_sqlite.py +19 -8
  25. adam/commands/export/utils_export.py +37 -15
  26. adam/commands/generate_report.py +52 -0
  27. adam/commands/medusa/medusa_restore.py +0 -16
  28. adam/commands/nodetool.py +1 -1
  29. adam/commands/os/__init__.py +0 -0
  30. adam/commands/postgres/postgres_databases.py +2 -3
  31. adam/commands/postgres/postgres_ls.py +1 -1
  32. adam/commands/postgres/utils_postgres.py +2 -1
  33. adam/commands/preview_table.py +1 -1
  34. adam/commands/restart_cluster.py +47 -0
  35. adam/commands/restart_node.py +51 -0
  36. adam/commands/restart_nodes.py +47 -0
  37. adam/commands/show/show_cassandra_status.py +3 -10
  38. adam/commands/show/show_cli_commands.py +1 -1
  39. adam/commands/show/show_processes.py +1 -1
  40. adam/commands/show/show_storage.py +2 -1
  41. adam/config.py +4 -6
  42. adam/embedded_params.py +1 -1
  43. adam/repl.py +5 -3
  44. adam/repl_commands.py +23 -17
  45. adam/repl_session.py +4 -3
  46. adam/repl_state.py +6 -0
  47. adam/sql/async_executor.py +44 -0
  48. adam/sql/lark_completer.py +6 -4
  49. adam/sql/qingl.lark +1076 -0
  50. adam/sso/cred_cache.py +2 -5
  51. adam/utils.py +206 -83
  52. adam/utils_k8s/app_clusters.py +11 -4
  53. adam/utils_k8s/app_pods.py +10 -5
  54. adam/utils_k8s/cassandra_clusters.py +8 -4
  55. adam/utils_k8s/cassandra_nodes.py +14 -5
  56. adam/utils_k8s/kube_context.py +1 -4
  57. adam/{pod_exec_result.py → utils_k8s/pod_exec_result.py} +8 -2
  58. adam/utils_k8s/pods.py +83 -24
  59. adam/utils_local.py +78 -2
  60. adam/utils_repl/repl_completer.py +10 -89
  61. adam/utils_sqlite.py +3 -8
  62. adam/version.py +1 -1
  63. {kaqing-2.0.188.dist-info → kaqing-2.0.211.dist-info}/METADATA +1 -1
  64. {kaqing-2.0.188.dist-info → kaqing-2.0.211.dist-info}/RECORD +67 -65
  65. adam/commands/cat.py +0 -36
  66. adam/commands/cd.py +0 -41
  67. adam/commands/download_file.py +0 -47
  68. adam/commands/find_files.py +0 -51
  69. adam/commands/find_processes.py +0 -76
  70. adam/commands/head.py +0 -36
  71. adam/commands/logs.py +0 -37
  72. adam/commands/ls.py +0 -41
  73. adam/commands/report.py +0 -61
  74. adam/commands/restart.py +0 -60
  75. adam/commands/shell.py +0 -41
  76. {kaqing-2.0.188.dist-info → kaqing-2.0.211.dist-info}/WHEEL +0 -0
  77. {kaqing-2.0.188.dist-info → kaqing-2.0.211.dist-info}/entry_points.txt +0 -0
  78. {kaqing-2.0.188.dist-info → kaqing-2.0.211.dist-info}/top_level.txt +0 -0
@@ -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':cat {r.log_file}')
150
149
 
151
150
  return r
152
151
 
@@ -1,5 +1,5 @@
1
1
  from adam.commands.command import Command
2
- from adam.commands.ls import Ls
2
+ from adam.commands.fs.ls import Ls
3
3
  from adam.repl_state import ReplState, RequiredState
4
4
 
5
5
  class PostgresLs(Command):
@@ -1,6 +1,7 @@
1
1
  import functools
2
2
 
3
3
  from adam.commands.postgres.postgres_databases import PostgresDatabases, pg_path
4
+ from adam.config import Config
4
5
  from adam.repl_state import ReplState
5
6
  from adam.utils import log2, wait_log
6
7
  from adam.utils_k8s.pods import Pods
@@ -62,7 +63,7 @@ class PostgresPodService:
62
63
  if isinstance(args, list):
63
64
  query = ' '.join(args)
64
65
 
65
- PostgresDatabases.run_sql(state, query, backgrounded=backgrounded)
66
+ PostgresDatabases.run_sql(state, query, show_out=Config().is_debug(), backgrounded=backgrounded)
66
67
 
67
68
  class PostgresExecHandler:
68
69
  def __init__(self, state: ReplState, backgrounded=False):
@@ -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"
@@ -8,13 +8,14 @@ from adam.checks.gossip import Gossip
8
8
  from adam.columns.columns import Columns
9
9
  from adam.commands import extract_options, extract_trailing_options
10
10
  from adam.commands.command import Command
11
+ from adam.commands.commands_utils import write_to_kaqing_log_file
11
12
  from adam.commands.cql.utils_cql import cassandra
12
13
  from adam.config import Config
13
14
  from adam.repl_session import ReplSession
14
15
  from adam.utils_issues import IssuesUtils
15
16
  from adam.utils_k8s.statefulsets import StatefulSets
16
17
  from adam.repl_state import ReplState, RequiredState
17
- from adam.utils import SORT, tabulize, log2, log_exc
18
+ from adam.utils import SORT, log_dir, tabulize, log2, log_exc
18
19
  from adam.checks.status import parse_nodetool_status
19
20
 
20
21
  class ShowCassandraStatus(Command):
@@ -105,15 +106,7 @@ class ShowCassandraStatus(Command):
105
106
  r = tabulize(status, lambda s: ','.join([c.host_value(check_results, s) for c in columns]), header=header, separator=',', sorted=SORT, to = 0 if backgrounded else 1)
106
107
 
107
108
  if backgrounded:
108
- log_prefix = Config().get('log-prefix', '/tmp/qing')
109
- log_file = f'{log_prefix}-{datetime.now().strftime("%d%H%M%S")}.log'
110
-
111
- with open(log_file, 'w') as f:
112
- f.write(r)
113
-
114
- ReplSession().append_history(f':sh cat {log_file}')
115
-
116
- r = log_file
109
+ r = write_to_kaqing_log_file(r)
117
110
 
118
111
  IssuesUtils.show(check_results)
119
112
 
@@ -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):
@@ -37,7 +37,7 @@ class ShowProcesses(Command):
37
37
  header = Config().get('processes-qing.header', 'POD_NAME,Q_CPU/TOTAL,MEM/LIMIT')
38
38
 
39
39
  with cassandra(state) as pods:
40
- pods.display_table(cols, header, show_out=show_out, backgrounded=backgrounded)
40
+ pods.display_table(cols, header, show_out=show_out, backgrounded=backgrounded, msg='Checking processes')
41
41
 
42
42
  return state
43
43
 
@@ -3,6 +3,7 @@ from adam.commands.command import Command
3
3
  from adam.commands.cql.utils_cql import cassandra
4
4
  from adam.config import Config
5
5
  from adam.repl_state import ReplState, RequiredState
6
+ from adam.utils import ing
6
7
 
7
8
  class ShowStorage(Command):
8
9
  COMMAND = 'show storage'
@@ -32,7 +33,7 @@ class ShowStorage(Command):
32
33
  cols = Config().get('storage.columns', 'pod,volume_root,volume_cassandra,snapshots,data,compactions')
33
34
  header = Config().get('storage.header', 'POD_NAME,VOLUME /,VOLUME CASS,SNAPSHOTS,DATA,COMPACTIONS')
34
35
  with cassandra(state) as pods:
35
- pods.display_table(cols, header, show_out=show_out, backgrounded=backgrounded)
36
+ pods.display_table(cols, header, show_out=show_out, backgrounded=backgrounded, msg='Checking storage')
36
37
 
37
38
  return state
38
39
 
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/q/export/db'}, 'athena': {'workers': 8, 'columns': '<keys>', 'bucket': 'c3.ops--qing'}, 'csv': {'workers': 8, 'columns': '<row-key>'}, 'log-dir': '/tmp/qing-db/q/export/logs'}, '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-qing-dir': '/tmp/qing-db/q', 'local-downloads-dir': '/tmp/qing-db/q/downloads', 'logs': {'path': '/c3/cassandra/logs/system.log'}, 'log-dir': '/tmp/qing-db/q/logs', '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': '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
@@ -1,13 +1,16 @@
1
- from adam.commands.alter_tables import AlterTables
2
1
  from adam.commands.app.app import App
3
2
  from adam.commands.app.app_ping import AppPing
4
3
  from adam.commands.app.show_app_actions import ShowAppActions
5
4
  from adam.commands.app.show_app_id import ShowAppId
6
5
  from adam.commands.app.show_app_queues import ShowAppQueues
7
6
  from adam.commands.audit.audit import Audit
8
- from adam.commands.cat import Cat
7
+ from adam.commands.fs.cat import Cat
9
8
  from adam.commands.code import Code
10
- from adam.commands.download_file import DownloadFile
9
+ from adam.commands.cql.alter_tables import AlterTables
10
+ from adam.commands.debug.debug import Debug
11
+ from adam.commands.download_cassandra_log import DownloadCassandraLog
12
+ from adam.commands.fs.cat_local import CatLocal
13
+ from adam.commands.fs.download_file import DownloadFile
11
14
  from adam.commands.deploy.code_start import CodeStart
12
15
  from adam.commands.deploy.code_stop import CodeStop
13
16
  from adam.commands.deploy.deploy import Deploy
@@ -38,14 +41,17 @@ from adam.commands.export.show_column_counts import ShowColumnCounts
38
41
  from adam.commands.export.show_export_databases import ShowExportDatabases
39
42
  from adam.commands.export.show_export_session import ShowExportSession
40
43
  from adam.commands.export.show_export_sessions import ShowExportSessions
41
- from adam.commands.find_files import FindLocalFiles
42
- from adam.commands.find_processes import FindProcesses
43
- from adam.commands.head import Head
44
+ from adam.commands.fs.find_files import FindLocalFiles
45
+ from adam.commands.fs.find_processes import FindProcesses
46
+ from adam.commands.fs.head import Head
47
+ from adam.commands.fs.ls_local import LsLocal
44
48
  from adam.commands.kubectl import Kubectl
45
- from adam.commands.shell import Shell
49
+ from adam.commands.restart_cluster import RestartCluster
50
+ from adam.commands.restart_node import RestartNode
51
+ from adam.commands.fs.shell import Shell
46
52
  from adam.commands.clipboard_copy import ClipboardCopy
47
53
  from adam.commands.bash.bash import Bash
48
- from adam.commands.cd import Cd
54
+ from adam.commands.fs.cd import Cd
49
55
  from adam.commands.check import Check
50
56
  from adam.commands.command import Command
51
57
  from adam.commands.cql.cqlsh import Cqlsh
@@ -53,15 +59,15 @@ from adam.commands.exit import Exit
53
59
  from adam.commands.medusa.medusa import Medusa
54
60
  from adam.commands.param_get import GetParam
55
61
  from adam.commands.issues import Issues
56
- from adam.commands.ls import Ls
62
+ from adam.commands.fs.ls import Ls
57
63
  from adam.commands.nodetool import NodeTool
58
64
  from adam.commands.postgres.postgres import Postgres, PostgresPg
59
65
  from adam.commands.preview_table import PreviewTable
60
66
  from adam.commands.pwd import Pwd
61
67
  from adam.commands.reaper.reaper import Reaper
62
68
  from adam.commands.repair.repair import Repair
63
- from adam.commands.report import Report
64
- from adam.commands.restart import Restart
69
+ from adam.commands.generate_report import GenerateReport
70
+ from adam.commands.restart_nodes import RestartNodes
65
71
  from adam.commands.rollout import RollOut
66
72
  from adam.commands.param_set import SetParam
67
73
  from adam.commands.show.show import Show
@@ -82,7 +88,7 @@ class ReplCommands:
82
88
  cmds: list[Command] = ReplCommands.navigation() + ReplCommands.cassandra_ops() + ReplCommands.postgres_ops() + \
83
89
  ReplCommands.app_ops() + ReplCommands.audit_ops() + ReplCommands.export_ops() + ReplCommands.tools() + ReplCommands.exit()
84
90
 
85
- intermediate_cmds: list[Command] = [App(), Audit(), Reaper(), Repair(), Deploy(), Show(), Undeploy()]
91
+ intermediate_cmds: list[Command] = [App(), Audit(), Reaper(), Repair(), Debug(), Deploy(), Show(), Undeploy()]
86
92
  ic = [c.command() for c in intermediate_cmds]
87
93
  # 1. dedup commands
88
94
  deduped = []
@@ -99,18 +105,18 @@ class ReplCommands:
99
105
  return deduped
100
106
 
101
107
  def navigation() -> list[Command]:
102
- return [Ls(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), DeviceAuditLog(), DeviceExport(),
103
- Cd(), Cat(), Head(), DownloadFile(), FindLocalFiles(), FindProcesses(), Pwd(), ClipboardCopy(),
108
+ return [Ls(), LsLocal(), PreviewTable(), DeviceApp(), DevicePostgres(), DeviceCass(), DeviceAuditLog(), DeviceExport(),
109
+ Cd(), Cat(), CatLocal(), Head(), DownloadFile(), FindLocalFiles(), FindProcesses(), Pwd(), ClipboardCopy(),
104
110
  GetParam(), SetParam(), ShowParams(), ShowKubectlCommands(), ShowLogin(), ShowAdam(), ShowHost()]
105
111
 
106
112
  def cassandra_ops() -> list[Command]:
107
- return [Cqlsh(), ShowCassandraStatus(), ShowCassandraVersion(), ShowCassandraRepairs(), ShowStorage(), ShowProcesses(),
108
- Check(), Issues(), NodeTool(), Report(), AlterTables(), Bash(),
113
+ return [Cqlsh(), DownloadCassandraLog(), ShowCassandraStatus(), ShowCassandraVersion(), ShowCassandraRepairs(), ShowStorage(), ShowProcesses(),
114
+ Check(), Issues(), NodeTool(), GenerateReport(), AlterTables(), Bash(),
109
115
  ExportTables(), ExportXSelect(), ExportUse(), ShowExportDatabases(), ShowColumnCounts(),
110
116
  DropExportDatabase(), DropExportDatabases(),
111
117
  ShowExportSessions(), ShowExportSession(), DownloadExportSession(),
112
118
  CleanUpExportSessions(), CleanUpAllExportSessions(), ImportSession(), ImportCSVFiles()] + \
113
- Medusa().cmd_list() + [Restart(), RollOut(), Watch()] + Reaper().cmd_list() + Repair().cmd_list()
119
+ Medusa().cmd_list() + [RestartNodes(), RestartNode(), RestartCluster(), RollOut(), Watch()] + Reaper().cmd_list() + Repair().cmd_list() + Debug().cmd_list()
114
120
 
115
121
  def postgres_ops() -> list[Command]:
116
122
  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',