@@ -156,8 +156,56 @@ def execute_remote_command(command):
156156 error = stderr .read ().decode ()
157157 return output , error
158158
159+ def find_device_config (uci , device , config = None ):
160+ if config is None :
161+ config = []
162+ # Find the device configuration in UCI
163+ for n in utils .get_all_by_type (uci , 'network' , 'device' ):
164+ if uci .get ('network' , n , 'name' , default = None ) == device :
165+ device_config = uci .get_all ('network' , n )
166+ device_config ['record_type' ] = 'device'
167+ device_config ['record_id' ] = n
168+
169+ if device_config .get ('type' ) == 'bridge' :
170+ # List all ports of the bridge
171+ for port in device_config .get ('ports' , []):
172+ config = config + find_device_config (uci , port , config )
173+ elif device_config .get ('type' , '' ).startswith ('802' ):
174+ ifname = device_config .get ('ifname' )
175+ if not ifname :
176+ continue
177+ # Get the underlying device of the 802.1q interface
178+ for underlying_device in utils .get_all_by_type (uci , 'network' , 'device' ):
179+ if uci .get ('network' , underlying_device , 'name' , default = None ) == ifname :
180+ config = config + find_device_config (uci , ifname , config )
181+ break
182+ config .append (device_config )
183+
184+ return config
185+
159186### API functions
160187
188+ def import_network_config (configs ):
189+ # config is a dictionary with the network configuration
190+ uci = EUci ()
191+
192+ for config in configs :
193+ subprocess .run (["logger" , "-t" , "ns-ha" , f"Importing network configuration: { json .dumps (config )} " ], check = True )
194+
195+ for config in configs :
196+ if config .get ('record_type' ) == 'device' :
197+ # Remove existing devices for the same name
198+ for n in utils .get_all_by_type (uci , 'network' , 'device' ):
199+ if uci .get ('network' , n , 'name' , default = None ) == config ['name' ]:
200+ uci .delete ('network' , n )
201+ # Create the configuration
202+ uci .set ('network' , config ['record_id' ], config ['record_type' ])
203+ for k , v in config .items ():
204+ if k not in ['record_id' , 'record_type' ]:
205+ uci .set ('network' , config ['record_id' ], k , v )
206+ uci .commit ('network' )
207+
208+
161209def add_interface (role , interface , virtual_ip , gateway ):
162210 # Add a new interface to the keepalived configuration:
163211 # 1. find a fake unused address inside the fake network 169.254.0.0/16 (reserved for documentation by RFC)
@@ -171,10 +219,38 @@ def add_interface(role, interface, virtual_ip, gateway):
171219 raise utils .ValidationError ('interface' , 'interface_not_found_on_main_node' )
172220
173221 if role == 'main' :
174- _ , error = execute_remote_command (f"uci get network.{ interface } " )
222+ # Before adding the interface, configure interface and device on the backup node:
223+ # 1. get the device configuration from the main node and send it to the backup node
224+ # 2. create the interface on the backup node
225+ network_config = []
226+ device = u .get ('network' , interface , 'device' , default = None )
227+ device_config = find_device_config (u , device )
228+ # Please note that device_config could be emptry for a WAN on a clean machine
229+ network_config = network_config + device_config
230+
231+ interface_config = u .get_all ('network' , interface )
232+ interface_config ['record_type' ] = 'interface'
233+ interface_config ['record_id' ] = interface
234+ # Check if the interface is a bonding interface
235+ if interface_config .get ('device' ).startswith ('bond-' ):
236+ # List all the slaves of the bonding interface, strip the prefix 'bond-'
237+ try :
238+ bond_config = u .get_all ('network' , interface_config .get ('device' )[5 :])
239+ bond_config ['record_type' ] = 'interface'
240+ bond_config ['record_id' ] = interface_config .get ('device' )[5 :]
241+ network_config = network_config + [bond_config ]
242+ except :
243+ return utils .generic_error ("bonding_interface_not_found" )
244+ # Send the slaves to the backup node
245+ for slave in bond_config .get ('slaves' , []):
246+ network_config = network_config + find_device_config (u , slave )
247+ network_config = network_config + [interface_config ]
248+
249+ # Send the interface configuration to the backup node
250+ output , error = execute_remote_command (f"echo '{ json .dumps (network_config )} ' | /usr/libexec/rpcd/ns.ha call import-network-config" )
175251 if error :
176- raise utils .ValidationError ( 'interface' , 'interface_not_found_on_backup_node' )
177-
252+ return utils .generic_error ( "error_adding_interface_on_backup_node" )
253+
178254 # Get the fake IP address
179255 main_ip , backup_ip = allocate_fake_ips (u )
180256
@@ -241,9 +317,6 @@ def add_interface(role, interface, virtual_ip, gateway):
241317 })
242318
243319 output , error = execute_remote_command (f"echo '{ command } ' | /usr/libexec/rpcd/ns.ha call add-interface" )
244- print (output )
245- print (error )
246-
247320 if error :
248321 return utils .generic_error ("error_executing_add_interface_on_backup_node" )
249322
@@ -524,6 +597,15 @@ def check_remote(backup_node_ip, ssh_password):
524597 ssh .close ()
525598 return result
526599
600+ def list_interfaces ():
601+ u = EUci ()
602+ interfaces = []
603+ for n in utils .get_all_by_type (u , 'network' , 'interface' ):
604+ if n == 'loopback' :
605+ continue
606+ if u .get ('network' , n , 'device' , default = None ):
607+ interfaces .append (n )
608+ return { "interfaces" : interfaces }
527609
528610cmd = sys .argv [1 ]
529611
@@ -546,12 +628,16 @@ if cmd == 'list':
546628 },
547629 "validate-requirements" : { "role" : "main" },
548630 "add-interface" : { "role" : "main" , "interface" : "wan" , "virtual_ip" : "1.2.3.4/24" , "gateway" : "1.2.3.5" },
549- "status" : {}
631+ "import-network-config" : { [ { "type" : "device" , "id" : "wan" , "name" : "wan" , "device" : "eth0.2" , "proto" : "static" , "ipaddr" : "" } ] },
632+ "status" : {},
633+ "list-interfaces" : {}
550634 }))
551635else :
552636 action = sys .argv [2 ]
553637 if action == "status" :
554638 ret = status ()
639+ elif action == "list-interfaces" :
640+ ret = list_interfaces ()
555641 else :
556642 # Paramaters:
557643 args = json .loads (sys .stdin .read ())
@@ -565,5 +651,7 @@ else:
565651 ret = check_remote (args .get ('backup_node_ip' ), args .get ('ssh_password' ))
566652 elif action == "validate-requirements" :
567653 ret = validate_requirements (args .get ('role' ))
654+ elif action == "import-network-config" :
655+ ret = import_network_config (args )
568656
569657 print (json .dumps (ret ))
0 commit comments