88import sys
99from ssl import CERT_NONE , create_default_context
1010from time import sleep
11+ from urllib .parse import urljoin
1112from urllib .request import urlopen
1213
1314API_REQUEST_TIMEOUT = 5
1718LOG_FILE_PATH = "/var/log/cluster_topology_observer.log"
1819
1920
21+ class UnreachableUnitsError (Exception ):
22+ """Cannot reach any known cluster member."""
23+
24+
2025def dispatch (run_cmd , unit , charm_dir ):
2126 """Use the input juju-run command to dispatch a :class:`ClusterTopologyChangeEvent`."""
2227 dispatch_sub_cmd = "JUJU_DISPATCH_PATH=hooks/cluster_topology_change {}/dispatch"
@@ -29,25 +34,43 @@ def main():
2934
3035 Watch the Patroni API cluster info. When changes are detected, dispatch the change event.
3136 """
32- patroni_url , run_cmd , unit , charm_dir = sys .argv [1 :]
37+ patroni_urls , run_cmd , unit , charm_dir = sys .argv [1 :]
3338
3439 previous_cluster_topology = {}
40+ urls = [urljoin (url , PATRONI_CLUSTER_STATUS_ENDPOINT ) for url in patroni_urls .split ("," )]
41+ member_name = unit .replace ("/" , "-" )
3542 while True :
3643 # Disable TLS chain verification
3744 context = create_default_context ()
3845 context .check_hostname = False
3946 context .verify_mode = CERT_NONE
4047
41- # Scheme is generated by the charm
42- resp = urlopen ( # noqa: S310
43- f"{ patroni_url } /{ PATRONI_CLUSTER_STATUS_ENDPOINT } " ,
44- timeout = API_REQUEST_TIMEOUT ,
45- context = context ,
46- )
47- cluster_status = json .loads (resp .read ())
48- current_cluster_topology = {
49- member ["name" ]: member ["role" ] for member in cluster_status ["members" ]
50- }
48+ cluster_status = None
49+ for url in urls :
50+ try :
51+ # Scheme is generated by the charm
52+ resp = urlopen ( # noqa: S310
53+ url ,
54+ timeout = API_REQUEST_TIMEOUT ,
55+ context = context ,
56+ )
57+ cluster_status = json .loads (resp .read ())
58+ break
59+ except Exception as e :
60+ print (f"Failed to contact { url } with { e } " )
61+ continue
62+ if not cluster_status :
63+ raise UnreachableUnitsError ("Unable to reach cluster members" )
64+ current_cluster_topology = {}
65+ urls = []
66+ for member in cluster_status ["members" ]:
67+ current_cluster_topology [member ["name" ]] = member ["role" ]
68+ member_url = urljoin (member ["api_url" ], PATRONI_CLUSTER_STATUS_ENDPOINT )
69+ # Call the current unit first
70+ if member ["name" ] == member_name :
71+ urls .insert (0 , member_url )
72+ else :
73+ urls .append (member_url )
5174
5275 # If it's the first time the cluster topology was retrieved, then store it and use
5376 # it for subsequent checks.
0 commit comments