1414
1515import logging
1616from pathlib import Path
17+ from socket import getfqdn
1718
1819import ops
1920
@@ -50,9 +51,15 @@ def __init__(self, framework: ops.Framework):
5051 self .framework .observe (self .on .install , self ._on_install )
5152 self .framework .observe (self .on .config_changed , self ._on_config_changed )
5253
54+ self .framework .observe (self .on .leader_elected , self ._on_leader_elected )
55+ self .framework .observe (
56+ self .on .replicas_relation_changed , self ._on_replicas_relation_changed
57+ )
58+
5359 @property
54- def _controller_ip (self ) -> str :
55- return str (self .config .get ("controller_ip" ))
60+ def _peer_relation (self ) -> ops .Relation | None :
61+ """Get replica peer relation if available."""
62+ return self .model .get_relation ("replicas" )
5663
5764 @property
5865 def _controller_port (self ) -> int :
@@ -71,16 +78,11 @@ def _lp_username(self) -> str:
7178
7279 @property
7380 def _node_id (self ) -> int :
74- node_id = self .config .get ("node_id" )
75- if isinstance (node_id , int ):
76- return node_id
77- return 0
81+ return int (self .unit .name .split ("/" )[- 1 ])
7882
7983 @property
8084 def _is_primary (self ) -> bool :
81- if self .config .get ("primary" ):
82- return True
83- return False
85+ return self .unit .is_leader ()
8486
8587 @property
8688 def _is_publishing_active (self ) -> bool :
@@ -110,6 +112,77 @@ def _lpuser_ssh_key(self) -> str | None:
110112
111113 return None
112114
115+ @property
116+ def _git_ubuntu_primary_relation (self ) -> ops .Relation | None :
117+ """Get the peer relation that contains the primary node IP.
118+
119+ Returns:
120+ The peer relation or None if it does not exist.
121+ """
122+ return self .model .get_relation ("replicas" )
123+
124+ def _open_controller_port (self ) -> bool :
125+ """Open the configured controller network port.
126+
127+ Returns:
128+ True if the port was opened, False otherwise.
129+ """
130+ self .unit .status = ops .MaintenanceStatus ("Opening controller port." )
131+
132+ try :
133+ port = self ._controller_port
134+
135+ if port > 0 :
136+ self .unit .set_ports (port )
137+ logger .info ("Opened controller port %d" , port )
138+ else :
139+ self .unit .status = ops .BlockedStatus ("Invalid controller port configuration." )
140+ return False
141+ except ops .ModelError :
142+ self .unit .status = ops .BlockedStatus ("Failed to open controller port." )
143+ return False
144+
145+ return True
146+
147+ def _set_peer_primary_node_address (self ) -> bool :
148+ """Set the primary node's IP to this unit's in the peer relation databag.
149+
150+ Returns:
151+ True if the data was updated, False otherwise.
152+ """
153+ self .unit .status = ops .MaintenanceStatus ("Setting primary node address in peer relation." )
154+
155+ relation = self ._git_ubuntu_primary_relation
156+
157+ if relation :
158+ new_primary_address = getfqdn ()
159+ relation .data [self .app ]["primary_address" ] = new_primary_address
160+ logger .info ("Updated primary node address to %s" , new_primary_address )
161+ return True
162+
163+ return False
164+
165+ def _get_primary_node_address (self ) -> str | None :
166+ """Get the primary node's network address - local if primary or juju binding if secondary.
167+
168+ Returns:
169+ The primary IP as a string if available, None otherwise.
170+ """
171+ if self ._is_primary :
172+ return "127.0.0.1"
173+
174+ relation = self ._git_ubuntu_primary_relation
175+
176+ if relation :
177+ primary_address = relation .data [self .app ]["primary_address" ]
178+
179+ if primary_address is not None and len (str (primary_address )) > 0 :
180+ logger .info ("Found primary node address %s" , primary_address )
181+ return str (primary_address )
182+
183+ logger .warning ("No primary node address found." )
184+ return None
185+
113186 def _refresh_importer_node (self ) -> None :
114187 """Remove old and install new git-ubuntu services."""
115188 self .unit .status = ops .MaintenanceStatus ("Refreshing git-ubuntu services." )
@@ -145,23 +218,29 @@ def _refresh_importer_node(self) -> None:
145218 return
146219 logger .info ("Initialized importer node as primary." )
147220 else :
221+ primary_ip = self ._get_primary_node_address ()
222+
223+ if primary_ip is None :
224+ self .unit .status = ops .BlockedStatus ("Secondary node requires a peer relation." )
225+ return
226+
148227 if not node .setup_secondary_node (
149228 GIT_UBUNTU_USER_HOME_DIR ,
150229 self ._node_id ,
151230 self ._num_workers ,
152231 GIT_UBUNTU_SYSTEM_USER_USERNAME ,
153232 will_publish ,
154233 self ._controller_port ,
155- self . _controller_ip ,
234+ primary_ip ,
156235 ):
157236 self .unit .status = ops .BlockedStatus ("Failed to install git-ubuntu services." )
158237 return
159238 logger .info ("Initialized importer node as secondary." )
160239
161240 self .unit .status = ops .ActiveStatus ("Importer node install complete." )
162241
163- def _on_start (self , _ : ops . StartEvent ) -> None :
164- """Handle start event ."""
242+ def _start_services (self ) -> None :
243+ """Start the services and note the result through status ."""
165244 if node .start (GIT_UBUNTU_USER_HOME_DIR ):
166245 node_type_str = "primary" if self ._is_primary else "secondary"
167246 self .unit .status = ops .ActiveStatus (
@@ -170,6 +249,10 @@ def _on_start(self, _: ops.StartEvent) -> None:
170249 else :
171250 self .unit .status = ops .BlockedStatus ("Failed to start services." )
172251
252+ def _on_start (self , _ : ops .StartEvent ) -> None :
253+ """Handle start event."""
254+ self ._start_services ()
255+
173256 def _update_git_user_config (self ) -> bool :
174257 """Attempt to update git config with the default git-ubuntu user name and email."""
175258 self .unit .status = ops .MaintenanceStatus ("Updating git config for git-ubuntu user." )
@@ -260,12 +343,26 @@ def _on_config_changed(self, _: ops.ConfigChangedEvent) -> None:
260343 not self ._update_git_user_config ()
261344 or not self ._update_lpuser_config ()
262345 or not self ._update_git_ubuntu_snap ()
346+ or not self ._open_controller_port ()
263347 ):
264348 return
265349
266350 # Initialize or re-install git-ubuntu services as needed.
267351 self ._refresh_importer_node ()
268352
353+ def _on_leader_elected (self , _ : ops .LeaderElectedEvent ) -> None :
354+ """Refresh services and update peer data when the unit is elected as leader."""
355+ if not self ._set_peer_primary_node_address ():
356+ self .unit .status = ops .BlockedStatus (
357+ "Failed to update primary node IP in peer relation."
358+ )
359+
360+ def _on_replicas_relation_changed (self , _ : ops .RelationChangedEvent ) -> None :
361+ """Refresh services for secondary nodes when peer relations change."""
362+ if not self ._is_primary :
363+ self ._refresh_importer_node ()
364+ self ._start_services ()
365+
269366
270367if __name__ == "__main__" : # pragma: nocover
271368 ops .main (GitUbuntuCharm )
0 commit comments