55"""
66
77import sys
8- from typing import Dict , List , Optional , cast
8+ from typing import List , Optional , cast
99
1010import click
11- from rich import box
1211from rich .console import Console
1312from rich .panel import Panel
14- from rich .table import Table
1513
14+ from cratedb_toolkit .admin .xmover .analyze .report import ShardReporter
15+ from cratedb_toolkit .admin .xmover .analyze .shard import ShardAnalyzer
16+ from cratedb_toolkit .admin .xmover .analyze .zone import ZoneReport
1617from cratedb_toolkit .admin .xmover .model import (
1718 RecommendationConstraints ,
18- ShardInfo ,
1919 ShardMoveRequest ,
2020 SizeCriteria ,
2121)
2222from cratedb_toolkit .admin .xmover .recommender import Recommender
23- from cratedb_toolkit .admin .xmover .reporter import ShardReporter
2423
25- from .analyzer import ShardAnalyzer
2624from .database import CrateDBClient
2725from .recovery import RecoveryMonitor , RecoveryOptions
2826
@@ -182,44 +180,8 @@ def test_connection(ctx, connection_string: Optional[str]):
182180def check_balance (ctx , table : Optional [str ], tolerance : float ):
183181 """Check zone balance for shards"""
184182 client = ctx .obj ["client" ]
185- analyzer = ShardAnalyzer (client )
186-
187- console .print (Panel .fit ("[bold blue]Zone Balance Check[/bold blue]" ))
188- console .print ("[dim]Note: Analyzing all shards regardless of state for complete cluster view[/dim]" )
189- console .print ()
190-
191- zone_stats = analyzer .check_zone_balance (table , tolerance )
192-
193- if not zone_stats :
194- console .print ("[yellow]No shards found for analysis[/yellow]" )
195- return
196-
197- # Calculate totals and targets
198- total_shards = sum (stats ["TOTAL" ] for stats in zone_stats .values ())
199- zones = list (zone_stats .keys ())
200- target_per_zone = total_shards // len (zones ) if zones else 0
201- tolerance_range = (target_per_zone * (1 - tolerance / 100 ), target_per_zone * (1 + tolerance / 100 ))
202-
203- balance_table = Table (title = f"Zone Balance Analysis (Target: { target_per_zone } ±{ tolerance } %)" , box = box .ROUNDED )
204- balance_table .add_column ("Zone" , style = "cyan" )
205- balance_table .add_column ("Primary" , justify = "right" , style = "blue" )
206- balance_table .add_column ("Replica" , justify = "right" , style = "green" )
207- balance_table .add_column ("Total" , justify = "right" , style = "magenta" )
208- balance_table .add_column ("Status" , style = "bold" )
209-
210- for zone , stats in zone_stats .items ():
211- total = stats ["TOTAL" ]
212-
213- if tolerance_range [0 ] <= total <= tolerance_range [1 ]:
214- status = "[green]✓ Balanced[/green]"
215- elif total < tolerance_range [0 ]:
216- status = f"[yellow]⚠ Under ({ total - target_per_zone :+} )[/yellow]"
217- else :
218- status = f"[red]⚠ Over ({ total - target_per_zone :+} )[/red]"
219-
220- balance_table .add_row (zone , str (stats ["PRIMARY" ]), str (stats ["REPLICA" ]), str (total ), status )
221-
222- console .print (balance_table )
183+ report = ZoneReport (client = client )
184+ report .shard_balance (tolerance = tolerance , table = table )
223185
224186
225187@main .command ()
@@ -229,106 +191,8 @@ def check_balance(ctx, table: Optional[str], tolerance: float):
229191def zone_analysis (ctx , table : Optional [str ], show_shards : bool ):
230192 """Detailed analysis of zone distribution and potential conflicts"""
231193 client = ctx .obj ["client" ]
232-
233- console .print (Panel .fit ("[bold blue]Detailed Zone Analysis[/bold blue]" ))
234- console .print ("[dim]Comprehensive zone distribution analysis for CrateDB cluster[/dim]" )
235- console .print ()
236-
237- # Get all shards for analysis
238- shards = client .get_shards_info (table_name = table , for_analysis = True )
239-
240- if not shards :
241- console .print ("[yellow]No shards found for analysis[/yellow]" )
242- return
243-
244- # Organize by table and shard
245- tables : Dict [str , Dict [str , List [ShardInfo ]]] = {}
246- for shard in shards :
247- table_key = f"{ shard .schema_name } .{ shard .table_name } "
248- if table_key not in tables :
249- tables [table_key ] = {}
250-
251- shard_key = shard .shard_id
252- if shard_key not in tables [table_key ]:
253- tables [table_key ][shard_key ] = []
254-
255- tables [table_key ][shard_key ].append (shard )
256-
257- # Analyze each table
258- zone_conflicts = 0
259- under_replicated = 0
260-
261- for table_name , table_shards in tables .items ():
262- console .print (f"\n [bold cyan]Table: { table_name } [/bold cyan]" )
263-
264- # Create analysis table
265- analysis_table = Table (title = f"Shard Distribution for { table_name } " , box = box .ROUNDED )
266- analysis_table .add_column ("Shard ID" , justify = "right" , style = "magenta" )
267- analysis_table .add_column ("Primary Zone" , style = "blue" )
268- analysis_table .add_column ("Replica Zones" , style = "green" )
269- analysis_table .add_column ("Total Copies" , justify = "right" , style = "cyan" )
270- analysis_table .add_column ("Status" , style = "bold" )
271-
272- for shard_id , shard_copies in sorted (table_shards .items ()):
273- primary_zone = "Unknown"
274- replica_zones = set ()
275- total_copies = len (shard_copies )
276- zones_with_copies = set ()
277-
278- for shard_copy in shard_copies :
279- zones_with_copies .add (shard_copy .zone )
280- if shard_copy .is_primary :
281- primary_zone = shard_copy .zone
282- else :
283- replica_zones .add (shard_copy .zone )
284-
285- # Determine status
286- status_parts = []
287- if len (zones_with_copies ) == 1 :
288- zone_conflicts += 1
289- status_parts .append ("[red]⚠ ZONE CONFLICT[/red]" )
290-
291- if total_copies < 2 : # Assuming we want at least 1 replica
292- under_replicated += 1
293- status_parts .append ("[yellow]⚠ Under-replicated[/yellow]" )
294-
295- if not status_parts :
296- status_parts .append ("[green]✓ Good[/green]" )
297-
298- replica_zones_str = ", " .join (sorted (replica_zones )) if replica_zones else "None"
299-
300- analysis_table .add_row (
301- str (shard_id ), primary_zone , replica_zones_str , str (total_copies ), " " .join (status_parts )
302- )
303-
304- # Show individual shard details if requested
305- if show_shards :
306- for shard_copy in shard_copies :
307- health_indicator = "✓" if shard_copy .routing_state == "STARTED" else "⚠"
308- console .print (
309- f" { health_indicator } { shard_copy .shard_type } "
310- f"on { shard_copy .node_name } ({ shard_copy .zone } ) - { shard_copy .routing_state } "
311- )
312-
313- console .print (analysis_table )
314-
315- # Summary
316- console .print ("\n [bold]Zone Analysis Summary:[/bold]" )
317- console .print (f" • Tables analyzed: [cyan]{ len (tables )} [/cyan]" )
318- console .print (f" • Zone conflicts detected: [red]{ zone_conflicts } [/red]" )
319- console .print (f" • Under-replicated shards: [yellow]{ under_replicated } [/yellow]" )
320-
321- if zone_conflicts > 0 :
322- console .print (f"\n [red]⚠ Found { zone_conflicts } zone conflicts that need attention![/red]" )
323- console .print ("[dim]Zone conflicts occur when all copies of a shard are in the same zone.[/dim]" )
324- console .print ("[dim]This violates CrateDB's zone-awareness and creates availability risks.[/dim]" )
325-
326- if under_replicated > 0 :
327- console .print (f"\n [yellow]⚠ Found { under_replicated } under-replicated shards.[/yellow]" )
328- console .print ("[dim]Consider increasing replication for better availability.[/dim]" )
329-
330- if zone_conflicts == 0 and under_replicated == 0 :
331- console .print ("\n [green]✓ No critical zone distribution issues detected![/green]" )
194+ report = ZoneReport (client = client )
195+ report .distribution_conflicts (shard_details = show_shards , table = table )
332196
333197
334198@main .command ()
0 commit comments