mysql-shell-client 0.6.0__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.
- mysql_shell/__init__.py +7 -0
- mysql_shell/builders/__init__.py +7 -0
- mysql_shell/builders/authorization/__init__.py +5 -0
- mysql_shell/builders/authorization/base.py +23 -0
- mysql_shell/builders/authorization/charm.py +213 -0
- mysql_shell/builders/locking/__init__.py +5 -0
- mysql_shell/builders/locking/base.py +28 -0
- mysql_shell/builders/locking/charm.py +97 -0
- mysql_shell/builders/logging/__init__.py +5 -0
- mysql_shell/builders/logging/base.py +14 -0
- mysql_shell/builders/logging/charm.py +36 -0
- mysql_shell/builders/quoting.py +46 -0
- mysql_shell/clients/__init__.py +5 -0
- mysql_shell/clients/cluster.py +414 -0
- mysql_shell/clients/instance.py +524 -0
- mysql_shell/executors/__init__.py +5 -0
- mysql_shell/executors/base.py +36 -0
- mysql_shell/executors/errors/__init__.py +4 -0
- mysql_shell/executors/errors/runtime.py +15 -0
- mysql_shell/executors/local.py +188 -0
- mysql_shell/models/__init__.py +8 -0
- mysql_shell/models/account.py +49 -0
- mysql_shell/models/cluster.py +57 -0
- mysql_shell/models/connection.py +22 -0
- mysql_shell/models/instance.py +27 -0
- mysql_shell/models/statement.py +30 -0
- mysql_shell_client-0.6.0.dist-info/METADATA +143 -0
- mysql_shell_client-0.6.0.dist-info/RECORD +30 -0
- mysql_shell_client-0.6.0.dist-info/WHEEL +4 -0
- mysql_shell_client-0.6.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Copyright 2025 Canonical Ltd.
|
|
2
|
+
# See LICENSE file for licensing details.
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Mapping
|
|
7
|
+
|
|
8
|
+
from ..executors import BaseExecutor
|
|
9
|
+
from ..executors.errors import ExecutionError
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger()
|
|
12
|
+
|
|
13
|
+
_Options = Mapping[str, str] | None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MySQLClusterClient:
|
|
17
|
+
"""Class to encapsulate all cluster operations using MySQL Shell."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, executor: BaseExecutor):
|
|
20
|
+
"""Initialize the class."""
|
|
21
|
+
self._executor = executor
|
|
22
|
+
|
|
23
|
+
def create_cluster(self, cluster_name: str, options: _Options = None) -> None:
|
|
24
|
+
"""Creates an InnoDB cluster."""
|
|
25
|
+
command = f"dba.create_cluster('{cluster_name}', {options})"
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
logger.debug(f"Creating InnoDB cluster {cluster_name}")
|
|
29
|
+
self._executor.execute_py(command)
|
|
30
|
+
except ExecutionError:
|
|
31
|
+
logger.error(f"Failed to create cluster {cluster_name}")
|
|
32
|
+
raise
|
|
33
|
+
|
|
34
|
+
def destroy_cluster(self, cluster_name: str, options: _Options = None) -> None:
|
|
35
|
+
"""Destroys an InnoDB cluster."""
|
|
36
|
+
command = "\n".join((
|
|
37
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
38
|
+
f"cluster.dissolve({options})",
|
|
39
|
+
))
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
logger.debug(f"Destroying InnoDB cluster {cluster_name}")
|
|
43
|
+
self._executor.execute_py(command)
|
|
44
|
+
except ExecutionError:
|
|
45
|
+
logger.error(f"Failed to destroy cluster {cluster_name}")
|
|
46
|
+
raise
|
|
47
|
+
|
|
48
|
+
def fetch_cluster_status(self, cluster_name: str, extended: bool = False) -> dict:
|
|
49
|
+
"""Fetches an InnoDB cluster status."""
|
|
50
|
+
command = "\n".join((
|
|
51
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
52
|
+
f"status = cluster.status({{'extended': {extended}}})",
|
|
53
|
+
f"print(status)",
|
|
54
|
+
))
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
result = self._executor.execute_py(command, timeout=30)
|
|
58
|
+
except ExecutionError:
|
|
59
|
+
logger.error("Failed to fetch cluster status")
|
|
60
|
+
raise
|
|
61
|
+
else:
|
|
62
|
+
return json.loads(result)
|
|
63
|
+
|
|
64
|
+
def list_cluster_routers(self, cluster_name: str) -> dict:
|
|
65
|
+
"""Lists an InnoDB cluster connected MySQL Routers."""
|
|
66
|
+
command = "\n".join((
|
|
67
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
68
|
+
f"routers = cluster.list_routers()",
|
|
69
|
+
f"print(routers)",
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
result = self._executor.execute_py(command)
|
|
74
|
+
except ExecutionError:
|
|
75
|
+
logger.error("Failed to list cluster routers")
|
|
76
|
+
raise
|
|
77
|
+
else:
|
|
78
|
+
return json.loads(result)
|
|
79
|
+
|
|
80
|
+
def rescan_cluster(self, cluster_name: str, options: _Options = None) -> None:
|
|
81
|
+
"""Rescans an InnoDB cluster."""
|
|
82
|
+
command = "\n".join((
|
|
83
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
84
|
+
f"cluster.rescan({options})",
|
|
85
|
+
))
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
logger.debug(f"Re-scanning InnoDB cluster {cluster_name}")
|
|
89
|
+
self._executor.execute_py(command)
|
|
90
|
+
except ExecutionError:
|
|
91
|
+
logger.error(f"Failed to re-scan cluster {cluster_name}")
|
|
92
|
+
raise
|
|
93
|
+
|
|
94
|
+
def reboot_cluster(self, cluster_name: str, options: _Options = None) -> None:
|
|
95
|
+
"""Reboots an InnoDB cluster."""
|
|
96
|
+
command = f"dba.reboot_cluster_from_complete_outage('{cluster_name}', {options})"
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
logger.debug(f"Re-booting InnoDB cluster {cluster_name}")
|
|
100
|
+
self._executor.execute_py(command)
|
|
101
|
+
except ExecutionError:
|
|
102
|
+
logger.error(f"Failed to re-boot cluster {cluster_name}")
|
|
103
|
+
raise
|
|
104
|
+
|
|
105
|
+
def create_cluster_set(self, cluster_name: str, cluster_set_name: str) -> None:
|
|
106
|
+
"""Creates an InnoDB cluster set from the provided cluster."""
|
|
107
|
+
command = "\n".join((
|
|
108
|
+
f"shell.connect_to_primary()",
|
|
109
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
110
|
+
f"cluster.create_cluster_set('{cluster_set_name}')",
|
|
111
|
+
))
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
logger.debug(f"Creating InnoDB cluster set {cluster_set_name}")
|
|
115
|
+
self._executor.execute_py(command)
|
|
116
|
+
except ExecutionError:
|
|
117
|
+
logger.error(f"Failed to create cluster set {cluster_set_name}")
|
|
118
|
+
raise
|
|
119
|
+
|
|
120
|
+
def fetch_cluster_set_status(self, extended: bool = False) -> dict:
|
|
121
|
+
"""Fetches an InnoDB cluster set status."""
|
|
122
|
+
command = "\n".join((
|
|
123
|
+
f"shell.connect_to_primary()",
|
|
124
|
+
f"cluster_set = dba.get_cluster_set()",
|
|
125
|
+
f"status = cluster_set.status({{'extended': {extended}}})",
|
|
126
|
+
f"print(status)",
|
|
127
|
+
))
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
result = self._executor.execute_py(command, timeout=120)
|
|
131
|
+
except ExecutionError:
|
|
132
|
+
logger.error("Failed to fetch cluster set status")
|
|
133
|
+
raise
|
|
134
|
+
else:
|
|
135
|
+
return json.loads(result)
|
|
136
|
+
|
|
137
|
+
def list_cluster_set_routers(self) -> dict:
|
|
138
|
+
"""Lists an InnoDB cluster set connected MySQL Routers."""
|
|
139
|
+
command = "\n".join((
|
|
140
|
+
f"shell.connect_to_primary()",
|
|
141
|
+
f"cluster_set = dba.get_cluster_set()",
|
|
142
|
+
f"routers = cluster_set.list_routers()",
|
|
143
|
+
f"print(routers)",
|
|
144
|
+
))
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
result = self._executor.execute_py(command)
|
|
148
|
+
except ExecutionError:
|
|
149
|
+
logger.error("Failed to list cluster set routers")
|
|
150
|
+
raise
|
|
151
|
+
else:
|
|
152
|
+
return json.loads(result)
|
|
153
|
+
|
|
154
|
+
def create_cluster_set_replica(
|
|
155
|
+
self,
|
|
156
|
+
cluster_name: str,
|
|
157
|
+
source_host: str,
|
|
158
|
+
source_port: str,
|
|
159
|
+
options: _Options = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Creates an InnoDB replica cluster into the cluster set."""
|
|
162
|
+
address = f"{source_host}:{source_port}"
|
|
163
|
+
command = f"\n".join((
|
|
164
|
+
f"shell.connect_to_primary()",
|
|
165
|
+
f"cluster_set = dba.get_cluster_set()",
|
|
166
|
+
f"cluster_set.create_replica_cluster('{address}', '{cluster_name}', {options})",
|
|
167
|
+
))
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
logger.debug(f"Creating InnoDB cluster set replica {cluster_name}")
|
|
171
|
+
self._executor.execute_py(command)
|
|
172
|
+
except ExecutionError:
|
|
173
|
+
logger.error(f"Failed to create cluster set replica {cluster_name}")
|
|
174
|
+
raise
|
|
175
|
+
|
|
176
|
+
def promote_cluster_set_replica(self, cluster_name: str, force: bool = False) -> None:
|
|
177
|
+
"""Promotes an InnoDB replica cluster within the cluster set."""
|
|
178
|
+
if force:
|
|
179
|
+
logger.warning(f"Forcing cluster {cluster_name} to become primary")
|
|
180
|
+
method_name = "force_primary_cluster"
|
|
181
|
+
else:
|
|
182
|
+
logger.debug(f"Setting cluster {cluster_name} to become primary")
|
|
183
|
+
method_name = "set_primary_cluster"
|
|
184
|
+
|
|
185
|
+
command = "\n".join((
|
|
186
|
+
f"shell.connect_to_primary()",
|
|
187
|
+
f"cluster_set = dba.get_cluster_set()",
|
|
188
|
+
f"cluster_set.{method_name}('{cluster_name}')",
|
|
189
|
+
))
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
self._executor.execute_py(command)
|
|
193
|
+
except ExecutionError:
|
|
194
|
+
logger.error(f"Failed to make cluster {cluster_name} the primary")
|
|
195
|
+
raise
|
|
196
|
+
|
|
197
|
+
def remove_cluster_set_replica(self, cluster_name: str, options: _Options = None) -> None:
|
|
198
|
+
"""Removes an InnoDB replica cluster from the cluster set."""
|
|
199
|
+
command = "\n".join((
|
|
200
|
+
f"shell.connect_to_primary()",
|
|
201
|
+
f"cluster_set = dba.get_cluster_set()",
|
|
202
|
+
f"cluster_set.remove_cluster('{cluster_name}', {options})",
|
|
203
|
+
))
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
logger.debug(f"Removing InnoDB cluster set replica {cluster_name}")
|
|
207
|
+
self._executor.execute_py(command)
|
|
208
|
+
except ExecutionError:
|
|
209
|
+
logger.error(f"Failed to remove cluster set replica {cluster_name}")
|
|
210
|
+
raise
|
|
211
|
+
|
|
212
|
+
def rejoin_cluster_set_cluster(self, cluster_name: str) -> None:
|
|
213
|
+
"""Rejoins an InnoDB cluster back to its cluster set."""
|
|
214
|
+
command = "\n".join((
|
|
215
|
+
f"shell.connect_to_primary()",
|
|
216
|
+
f"cluster_set = dba.get_cluster_set()",
|
|
217
|
+
f"cluster_set.rejoin_cluster('{cluster_name}')",
|
|
218
|
+
))
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
logger.debug(f"Rejoining cluster {cluster_name}")
|
|
222
|
+
self._executor.execute_py(command)
|
|
223
|
+
except ExecutionError:
|
|
224
|
+
logger.error(f"Failed to rejoin cluster {cluster_name}")
|
|
225
|
+
raise
|
|
226
|
+
|
|
227
|
+
def attach_instance_into_cluster(
|
|
228
|
+
self,
|
|
229
|
+
cluster_name: str,
|
|
230
|
+
instance_host: str,
|
|
231
|
+
instance_port: str,
|
|
232
|
+
options: _Options = None,
|
|
233
|
+
) -> None:
|
|
234
|
+
"""Attached an instance into an InnoDB cluster."""
|
|
235
|
+
address = f"{instance_host}:{instance_port}"
|
|
236
|
+
command = f"\n".join((
|
|
237
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
238
|
+
f"cluster.add_instance('{address}', {options})",
|
|
239
|
+
))
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
logger.debug(f"Attaching instance {address} to cluster {cluster_name}")
|
|
243
|
+
self._executor.execute_py(command)
|
|
244
|
+
except ExecutionError:
|
|
245
|
+
logger.error(f"Failed to attach instance {address} to cluster {cluster_name}")
|
|
246
|
+
raise
|
|
247
|
+
|
|
248
|
+
def detach_instance_from_cluster(
|
|
249
|
+
self,
|
|
250
|
+
cluster_name: str,
|
|
251
|
+
instance_host: str,
|
|
252
|
+
instance_port: str,
|
|
253
|
+
options: _Options = None,
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Detaches an instance from an InnoDB cluster."""
|
|
256
|
+
address = f"{instance_host}:{instance_port}"
|
|
257
|
+
command = f"\n".join((
|
|
258
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
259
|
+
f"cluster.remove_instance('{address}', {options})",
|
|
260
|
+
))
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
logger.debug(f"Detaching instance {address} from cluster {cluster_name}")
|
|
264
|
+
self._executor.execute_py(command)
|
|
265
|
+
except ExecutionError:
|
|
266
|
+
logger.error(f"Failed to detach instance {address} from cluster {cluster_name}")
|
|
267
|
+
raise
|
|
268
|
+
|
|
269
|
+
def force_instance_quorum_into_cluster(
|
|
270
|
+
self,
|
|
271
|
+
cluster_name: str,
|
|
272
|
+
instance_host: str,
|
|
273
|
+
instance_port: str,
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Forces and instance quorum into an InnoDB cluster."""
|
|
276
|
+
address = f"{instance_host}:{instance_port}"
|
|
277
|
+
command = f"\n".join((
|
|
278
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
279
|
+
f"cluster.force_quorum_using_partition_of('{address}')",
|
|
280
|
+
))
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
logger.debug(f"Forcing quorum into cluster {cluster_name}")
|
|
284
|
+
self._executor.execute_py(command)
|
|
285
|
+
except ExecutionError:
|
|
286
|
+
logger.error(f"Failed to force quorum into cluster {cluster_name}")
|
|
287
|
+
raise
|
|
288
|
+
|
|
289
|
+
def rejoin_instance_into_cluster(
|
|
290
|
+
self,
|
|
291
|
+
cluster_name: str,
|
|
292
|
+
instance_host: str,
|
|
293
|
+
instance_port: str,
|
|
294
|
+
options: _Options = None,
|
|
295
|
+
) -> None:
|
|
296
|
+
"""Rejoins an instance back into its InnoDB cluster."""
|
|
297
|
+
address = f"{instance_host}:{instance_port}"
|
|
298
|
+
command = f"\n".join((
|
|
299
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
300
|
+
f"cluster.rejoin_instance('{address}', {options})",
|
|
301
|
+
))
|
|
302
|
+
|
|
303
|
+
try:
|
|
304
|
+
logger.debug(f"Rejoining instance {address} into cluster {cluster_name}")
|
|
305
|
+
self._executor.execute_py(command)
|
|
306
|
+
except ExecutionError:
|
|
307
|
+
logger.error(f"Failed to rejoin instance {address} into cluster {cluster_name}")
|
|
308
|
+
raise
|
|
309
|
+
|
|
310
|
+
def check_instance_before_cluster(self, options: _Options = None) -> dict:
|
|
311
|
+
"""Checks for an instance configuration before joining an InnoDB cluster."""
|
|
312
|
+
command = "\n".join((
|
|
313
|
+
f"result = dba.check_instance_configuration(options={options})",
|
|
314
|
+
f"print(result)",
|
|
315
|
+
))
|
|
316
|
+
|
|
317
|
+
host = self._executor.connection_details.host
|
|
318
|
+
port = self._executor.connection_details.port
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
logger.debug(f"Checking for instance {host}:{port} config")
|
|
322
|
+
result = self._executor.execute_py(command)
|
|
323
|
+
except ExecutionError:
|
|
324
|
+
logger.error(f"Failed to check for instance {host}:{port} config")
|
|
325
|
+
raise
|
|
326
|
+
else:
|
|
327
|
+
return json.loads(result)
|
|
328
|
+
|
|
329
|
+
def setup_instance_before_cluster(self, options: _Options = None) -> None:
|
|
330
|
+
"""Sets up an instance configuration before joining an InnoDB cluster."""
|
|
331
|
+
command = f"dba.configure_instance(options={options})"
|
|
332
|
+
host = self._executor.connection_details.host
|
|
333
|
+
port = self._executor.connection_details.port
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
logger.debug(f"Setting up instance {host}:{port} config")
|
|
337
|
+
self._executor.execute_py(command)
|
|
338
|
+
except ExecutionError:
|
|
339
|
+
logger.error(f"Failed to setup instance {host}:{port} config")
|
|
340
|
+
raise
|
|
341
|
+
|
|
342
|
+
def promote_instance_within_cluster(
|
|
343
|
+
self,
|
|
344
|
+
cluster_name: str,
|
|
345
|
+
instance_host: str,
|
|
346
|
+
instance_port: str,
|
|
347
|
+
force: bool = False,
|
|
348
|
+
) -> None:
|
|
349
|
+
"""Promotes an InnoDB cluster replica within the cluster."""
|
|
350
|
+
address = f"{instance_host}:{instance_port}"
|
|
351
|
+
|
|
352
|
+
if force:
|
|
353
|
+
logger.warning(f"Forcing instance {address} to become primary")
|
|
354
|
+
method_name = "force_primary_instance"
|
|
355
|
+
else:
|
|
356
|
+
logger.debug(f"Setting instance {address} to become primary")
|
|
357
|
+
method_name = "set_primary_instance"
|
|
358
|
+
|
|
359
|
+
command = "\n".join((
|
|
360
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
361
|
+
f"cluster.{method_name}('{address}')",
|
|
362
|
+
))
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
self._executor.execute_py(command)
|
|
366
|
+
except ExecutionError:
|
|
367
|
+
logger.error(f"Failed to make instance {address} the primary")
|
|
368
|
+
raise
|
|
369
|
+
|
|
370
|
+
def update_instance_within_cluster(
|
|
371
|
+
self,
|
|
372
|
+
cluster_name: str,
|
|
373
|
+
instance_host: str,
|
|
374
|
+
instance_port: str,
|
|
375
|
+
options: _Options = None,
|
|
376
|
+
) -> None:
|
|
377
|
+
"""Updates an instance within an InnoDB cluster."""
|
|
378
|
+
address = f"{instance_host}:{instance_port}"
|
|
379
|
+
command = [
|
|
380
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
for key, val in options.items():
|
|
384
|
+
val = f"'{val}'" if isinstance(val, str) else val
|
|
385
|
+
cmd = f"cluster.set_instance_option('{address}', '{key}', {val})"
|
|
386
|
+
command.append(cmd)
|
|
387
|
+
|
|
388
|
+
command = "\n".join(command)
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
logger.debug(f"Updating instance {address} within cluster {cluster_name}")
|
|
392
|
+
self._executor.execute_py(command)
|
|
393
|
+
except ExecutionError:
|
|
394
|
+
logger.error(f"Failed to update instance {address} within cluster {cluster_name}")
|
|
395
|
+
raise
|
|
396
|
+
|
|
397
|
+
def remove_router_from_cluster(
|
|
398
|
+
self,
|
|
399
|
+
cluster_name: str,
|
|
400
|
+
router_name: str,
|
|
401
|
+
router_mode: str,
|
|
402
|
+
) -> None:
|
|
403
|
+
"""Removes a router from an InnoDB cluster."""
|
|
404
|
+
command = "\n".join((
|
|
405
|
+
f"cluster = dba.get_cluster('{cluster_name}')",
|
|
406
|
+
f"cluster.remove_router_metadata('{router_name}::{router_mode}')",
|
|
407
|
+
))
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
logger.debug(f"Removing router from cluster {cluster_name}")
|
|
411
|
+
self._executor.execute_py(command)
|
|
412
|
+
except ExecutionError:
|
|
413
|
+
logger.error(f"Failed to remove router from cluster {cluster_name}")
|
|
414
|
+
raise
|