You've already forked mariadb-columnstore-engine
							
							
				mirror of
				https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
				synced 2025-10-31 18:30:33 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			123 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import concurrent.futures
 | |
| import json
 | |
| from contextlib import contextmanager
 | |
| from pathlib import Path
 | |
| from typing import Any, Callable
 | |
| 
 | |
| from fabric import Connection
 | |
| 
 | |
| 
 | |
| from dataclasses import dataclass, field
 | |
| 
 | |
| @dataclass
 | |
| class RemoteHost:
 | |
|     """Keeps configuration for a host in the cluster"""
 | |
|     name: str
 | |
|     private_ip: str
 | |
|     public_fqdn: str
 | |
|     key_file_path: Path = field(repr=False)
 | |
|     ssh_user: str = field(repr=False)
 | |
| 
 | |
|     @contextmanager
 | |
|     def ssh_connection(self):
 | |
|         """Connect to the host using SSH (via Fabric)"""
 | |
|         conn = Connection(
 | |
|             host=self.public_fqdn,
 | |
|             user=self.ssh_user,
 | |
|             connect_kwargs={
 | |
|                 "key_filename": str(self.key_file_path),
 | |
|             }
 | |
|         )
 | |
| 
 | |
|         yield conn
 | |
| 
 | |
|         conn.close()
 | |
| 
 | |
|     def is_reachable(self) -> bool:
 | |
|         """Check if the host is reachable via SSH"""
 | |
|         try:
 | |
|             with self.ssh_connection() as conn:
 | |
|                 conn.run("echo 'SSH connection successful'", hide=True)
 | |
|             return True
 | |
|         except Exception as e:
 | |
|             print(f"SSH connection failed for {self.name}: {e}")
 | |
|             return False
 | |
| 
 | |
|     def exec_mcs(self, params: str, drop_timestamp: bool = True) -> dict:
 | |
|         """Execute an mcs subcommand and return its parsed output"""
 | |
|         with self.ssh_connection() as conn:
 | |
|             print(f"{self.name}: Executing 'mcs {params}'")
 | |
|             result = conn.sudo(f"mcs {params}", hide=True)
 | |
|             if result.return_code != 0:
 | |
|                 raise RuntimeError(f"Failed to execute mcs command on {self.name}: {result.stderr}")
 | |
|             output = json.loads(result.stdout)
 | |
|             return output
 | |
| 
 | |
| 
 | |
| class ClusterConfig:
 | |
|     """Keeps configuration of the cluster"""
 | |
|     def __init__(self, mcs_hosts: list[RemoteHost], maxscale_hosts: list[RemoteHost]) -> None:
 | |
|         self.mcs_hosts = mcs_hosts
 | |
|         self.maxscale_hosts = maxscale_hosts
 | |
| 
 | |
|     def __repr__(self) -> str:
 | |
|         return f"ClusterConfig(mcs_hosts={self.mcs_hosts}"
 | |
| 
 | |
|     @property
 | |
|     def primary(self) -> RemoteHost:
 | |
|         return self.mcs_hosts[0]
 | |
| 
 | |
|     @property
 | |
|     def replicas(self) -> list[RemoteHost]:
 | |
|         return self.mcs_hosts[1:]
 | |
| 
 | |
| 
 | |
| def run_on_all_hosts_parallel(
 | |
|       hosts: list[RemoteHost],
 | |
|       func: Callable[[RemoteHost], Any],
 | |
|       timeout: float | None = None,
 | |
|       max_workers: int | None = None,
 | |
|     ) -> dict[str, Any]:
 | |
|     """
 | |
|     Run a function on all hosts in parallel.
 | |
| 
 | |
|     :param hosts: List of RemoteHost objects
 | |
|     :param func: Function that takes a RemoteHost as its only argument
 | |
|     :param timeout: Timeout for the function execution
 | |
|     :param max_workers: Maximum number of worker threads (None = default based on system)
 | |
|     :returns: Dictionary mapping hosts to function results
 | |
|     """
 | |
|     results = {}
 | |
| 
 | |
|     with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
 | |
|         # Start the function execution on each host
 | |
|         future_to_host = {executor.submit(func, host): host for host in hosts}
 | |
| 
 | |
|         # Process results as they complete
 | |
|         for future in concurrent.futures.as_completed(future_to_host):
 | |
|             host = future_to_host[future]
 | |
|             try:
 | |
|                 result = future.result(timeout=timeout)
 | |
|                 results[host.name] = result
 | |
|             except Exception as exc:
 | |
|                 print(f"Error on host {host.name}: {exc}")
 | |
|                 results[host.name] = exc
 | |
|                 raise
 | |
| 
 | |
|     return results
 | |
| 
 | |
| 
 | |
| def block_port(host: RemoteHost, port: int) -> None:
 | |
|     """Block a port on the host using iptables"""
 | |
|     with host.ssh_connection() as conn:
 | |
|         print(f"{host.name}: Blocking port {port}")
 | |
|         conn.sudo(f"iptables -A INPUT -p tcp --dport {port} -j DROP")
 | |
|         print(f"{host.name}: Port {port} blocked")
 | |
| 
 | |
| 
 | |
| def unblock_port(host: RemoteHost, port: int) -> None:
 | |
|     """Unblock a port on the host using iptables"""
 | |
|     with host.ssh_connection() as conn:
 | |
|         print(f"{host.name}: Unblocking port {port}")
 | |
|         conn.sudo(f"iptables -D INPUT -p tcp --dport {port} -j DROP")
 | |
|         print(f"{host.name}: Port {port} unblocked") |