@@ -164,6 +164,21 @@ def get_mylogin_cnf(self, node: remote.RemoteConnection) -> bytes:
164164
165165 def start_mysql_servers (self ) -> None :
166166 for node in self .mysql_nodes :
167+ _ , stdout , _ = node .ssh_client .exec_command (
168+ "docker container inspect mysql > /dev/null 2>&1 && echo exists"
169+ )
170+ if stdout .read ().decode ().strip () == "exists" :
171+ _ , stdout , _ = node .ssh_client .exec_command (
172+ "for i in $(seq 1 60); do "
173+ "docker inspect --format='{{.State.Health.Status}}'"
174+ " mysql 2>/dev/null"
175+ " | grep -qx healthy && exit 0; sleep 5; done; exit 1"
176+ )
177+ if stdout .channel .recv_exit_status () != 0 :
178+ raise RuntimeError (
179+ f"MySQL on { node .node } exists but did not become healthy"
180+ )
181+ continue
167182 node .upload_directory (
168183 local_directory_path = "./services/mysql-server" ,
169184 remote_directory_path = "/opt/superset-cluster/mysql-server"
@@ -195,6 +210,21 @@ def start_mysql_servers(self) -> None:
195210 )
196211
197212 def start_mysql_mgmt (self , node : remote .RemoteConnection , state : str , priority : int ) -> None :
213+ _ , stdout , _ = node .ssh_client .exec_command (
214+ "docker container inspect mysql-mgmt > /dev/null 2>&1 && echo exists"
215+ )
216+ if stdout .read ().decode ().strip () == "exists" :
217+ _ , stdout , _ = node .ssh_client .exec_command (
218+ "for i in $(seq 1 40); do "
219+ "docker inspect --format='{{.State.Health.Status}}'"
220+ " mysql-mgmt 2>/dev/null"
221+ " | grep -qx healthy && exit 0; sleep 5; done; exit 1"
222+ )
223+ if stdout .channel .recv_exit_status () != 0 :
224+ raise RuntimeError (
225+ f"mysql-mgmt on { node .node } exists but did not become healthy"
226+ )
227+ return
198228 node .upload_directory (
199229 local_directory_path = "./services/mysql-mgmt" ,
200230 remote_directory_path = "/opt/superset-cluster/mysql-mgmt"
@@ -251,6 +281,38 @@ def start_mysql_mgmt(self, node: remote.RemoteConnection, state: str, priority:
251281
252282 def start_superset (self ) -> None :
253283 for node in self .mgmt_nodes :
284+ _ , redis_out , _ = node .ssh_client .exec_command (
285+ "docker container inspect redis > /dev/null 2>&1 && echo exists"
286+ )
287+ _ , svc_out , _ = node .ssh_client .exec_command (
288+ "docker service inspect superset > /dev/null 2>&1 && echo exists"
289+ )
290+ redis_exists = redis_out .read ().decode ().strip () == "exists"
291+ superset_exists = svc_out .read ().decode ().strip () == "exists"
292+ if redis_exists != superset_exists :
293+ raise RuntimeError (
294+ f"Partial state on { node .node } : "
295+ f"redis={ 'exists' if redis_exists else 'missing' } , "
296+ f"superset={ 'exists' if superset_exists else 'missing' } . "
297+ f"Run --cleanup first"
298+ )
299+ if redis_exists and superset_exists :
300+ _ , stdout , _ = node .ssh_client .exec_command (
301+ "for i in $(seq 1 90); do "
302+ "docker inspect --format='{{.State.Health.Status}}'"
303+ " redis 2>/dev/null | grep -qx healthy"
304+ " && docker service ps superset"
305+ " --format='{{.CurrentState}}'"
306+ " --filter desired-state=running 2>/dev/null"
307+ " | head -1 | grep -q Running"
308+ " && exit 0; sleep 5; done; exit 1"
309+ )
310+ if stdout .channel .recv_exit_status () != 0 :
311+ raise RuntimeError (
312+ f"Superset stack on { node .node } exists"
313+ f" but did not become healthy"
314+ )
315+ continue
254316 node .upload_directory (
255317 local_directory_path = "./services/superset" ,
256318 remote_directory_path = '/opt/superset-cluster/superset'
@@ -283,27 +345,6 @@ def start_superset(self) -> None:
283345 mysql_superset_password = self .mysql_superset_password )
284346 )
285347
286- def _close_connections (self ) -> None :
287- for node in list (itertools .chain (self .mysql_nodes , self .mgmt_nodes )):
288- node .ssh_client .close ()
289- node .sftp_client .close ()
290-
291- def _run_ssh_command (self , node : remote .RemoteConnection , command : str ) -> None :
292- _ , stdout , stderr = node .ssh_client .exec_command (command )
293- exit_status = stdout .channel .recv_exit_status ()
294- if exit_status != 0 :
295- raise RuntimeError (
296- f"Command failed on { node .node } (exit { exit_status } ): "
297- f"{ command } \n { stderr .read ().decode ().strip ()} "
298- )
299-
300- def _prepare_nodes (self ) -> None :
301- for node in itertools .chain (self .mysql_nodes , self .mgmt_nodes ):
302- try :
303- node .sftp_client .mkdir ('/opt/superset-cluster' )
304- except IOError :
305- pass
306-
307348 def teardown_node (self , node : remote .RemoteConnection , is_mgmt : bool = False ) -> None :
308349 commands = [
309350 "if docker info --format '{{.Swarm.LocalNodeState}}' 2>/dev/null"
@@ -316,7 +357,7 @@ def teardown_node(self, node: remote.RemoteConnection, is_mgmt: bool = False) ->
316357 " | xargs -r docker network rm" ,
317358 "if [ -d /opt/superset-cluster ]; then"
318359 " rm -rf /opt/superset-cluster; fi" ,
319- "find /opt -maxdepth 1 -name '*.pyc' -delete" ,
360+ "find /opt/superset-cluster -maxdepth 1 -name '*.pyc' -delete" ,
320361 ]
321362 if is_mgmt :
322363 commands .extend ([
@@ -330,34 +371,35 @@ def teardown_node(self, node: remote.RemoteConnection, is_mgmt: bool = False) ->
330371 " ip route del {vip}; fi" .format (
331372 vip = self .virtual_ip_address ),
332373 ])
333- for cmd in commands :
334- self ._run_ssh_command (node , cmd )
335-
336- def teardown_cluster (self ) -> None :
337- for node in self .mgmt_nodes :
338- self .teardown_node (node , is_mgmt = True )
339- for node in self .mysql_nodes :
340- self .teardown_node (node , is_mgmt = False )
374+ _ , stdout , stderr = node .ssh_client .exec_command (" && " .join (commands ))
375+ exit_status = stdout .channel .recv_exit_status ()
376+ if exit_status != 0 :
377+ raise RuntimeError (
378+ f"Teardown failed on { node .node } (exit { exit_status } ): "
379+ f"{ stderr .read ().decode ().strip ()} "
380+ )
341381
342382 def start_cluster (self ) -> None :
343- self .start_mysql_servers ()
344- self .start_mysql_mgmt (node = self .mgmt_nodes [0 ], state = "MASTER" , priority = 100 )
345- self .start_mysql_mgmt (node = self .mgmt_nodes [1 ], state = "BACKUP" , priority = 90 )
346- self .start_superset ()
347-
348- def deploy (self ) -> None :
349383 try :
350- self .teardown_cluster ()
351- self ._prepare_nodes ()
352- self .start_cluster ()
384+ self .start_mysql_servers ()
385+ self .start_mysql_mgmt (node = self .mgmt_nodes [0 ], state = "MASTER" , priority = 100 )
386+ self .start_mysql_mgmt (node = self .mgmt_nodes [1 ], state = "BACKUP" , priority = 90 )
387+ self .start_superset ()
353388 finally :
354- self ._close_connections ()
389+ for node in list (itertools .chain (self .mysql_nodes , self .mgmt_nodes )):
390+ node .ssh_client .close ()
391+ node .sftp_client .close ()
355392
356393 def cleanup (self ) -> None :
357394 try :
358- self .teardown_cluster ()
395+ for node in self .mgmt_nodes :
396+ self .teardown_node (node , is_mgmt = True )
397+ for node in self .mysql_nodes :
398+ self .teardown_node (node , is_mgmt = False )
359399 finally :
360- self ._close_connections ()
400+ for node in list (itertools .chain (self .mysql_nodes , self .mgmt_nodes )):
401+ node .ssh_client .close ()
402+ node .sftp_client .close ()
361403
362404
363405if __name__ == "__main__" :
@@ -371,4 +413,4 @@ def cleanup(self) -> None:
371413 if action == "cleanup" :
372414 controller .cleanup ()
373415 else :
374- controller .deploy ()
416+ controller .start_cluster ()
0 commit comments