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.
@@ -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