22from pydantic import BaseModel , ConfigDict
33from db import get_session
44import libvirt
5+ from host_manager import libvirt_connection
56
67
78class LibvirtNetworkBridge (SQLModel , table = True ):
@@ -23,6 +24,14 @@ class LibvirtNetworkBridgeResponse(BaseModel):
2324 active : bool
2425
2526
27+ class LibvirtNetworkBridgeApplyResponse (BaseModel ):
28+ model_config = ConfigDict (from_attributes = True )
29+ checked : int
30+ removed : int
31+ defined : int
32+ started : int
33+ errors : list [str ]
34+
2635class LibvirtNetworkCustom (SQLModel , table = True ):
2736 __tablename__ = "libvirtnetworkcustom"
2837 id : int | None = Field (primary_key = True )
@@ -47,15 +56,13 @@ def get_network_bridge(id: int) -> LibvirtNetworkBridgeResponse | None:
4756 statement = select (LibvirtNetworkBridge ).where (LibvirtNetworkBridge .id == id )
4857 result = session .exec (statement ).first ()
4958 if result :
50- # Check if active in libvirt
51- conn = libvirt .open (None )
59+ # Check if active in libvirt using shared connection
5260 try :
61+ conn = libvirt_connection .connection
5362 network = conn .networkLookupByName (result .name )
5463 active = network .isActive () == 1
5564 except libvirt .libvirtError :
5665 active = False
57- finally :
58- conn .close ()
5966 return LibvirtNetworkBridgeResponse (
6067 id = result .id ,
6168 name = result .name ,
@@ -128,15 +135,13 @@ def get_network_custom(id: int) -> LibvirtNetworkCustomResponse | None:
128135 statement = select (LibvirtNetworkCustom ).where (LibvirtNetworkCustom .id == id )
129136 result = session .exec (statement ).first ()
130137 if result :
131- # Check if active in libvirt
132- conn = libvirt .open (None )
138+ # Check if active in libvirt using shared connection
133139 try :
140+ conn = libvirt_connection .connection
134141 network = conn .networkLookupByName (result .name )
135142 active = network .isActive () == 1
136143 except libvirt .libvirtError :
137144 active = False
138- finally :
139- conn .close ()
140145 return LibvirtNetworkCustomResponse (
141146 id = result .id ,
142147 name = result .name ,
@@ -202,3 +207,95 @@ def delete_network_custom(id: int) -> bool:
202207 session .commit ()
203208 return True
204209 return False
210+
211+
212+ def apply_network_bridges () -> LibvirtNetworkBridgeApplyResponse :
213+ """Apply network bridge settings from the database to libvirt.
214+
215+ For every bridge entry in the database:
216+ - If a libvirt network with the same name exists, destroy and undefine it.
217+ - Define a new network XML for the bridge and register it with libvirt.
218+ - If the DB entry has `autostart=True`, set autostart and start the network.
219+
220+ Returns a summary dict with counts and any errors encountered.
221+ """
222+ summary = {
223+ "checked" : 0 ,
224+ "removed" : 0 ,
225+ "defined" : 0 ,
226+ "started" : 0 ,
227+ "errors" : [],
228+ }
229+
230+ try :
231+ conn = libvirt_connection .connection
232+ except Exception as e :
233+ summary ["errors" ].append (f"failed-to-get-libvirt-connection:{ e } " )
234+ return summary
235+
236+ with get_session () as session :
237+ stmt = select (LibvirtNetworkBridge )
238+ bridges = session .exec (stmt ).all ()
239+ for b in bridges :
240+ summary ["checked" ] += 1
241+ # Remove existing definition if present
242+ try :
243+ try :
244+ existing = conn .networkLookupByName (b .name )
245+ except libvirt .libvirtError :
246+ existing = None
247+
248+ if existing is not None :
249+ try :
250+ if existing .isActive () == 1 :
251+ existing .destroy ()
252+ except libvirt .libvirtError as e :
253+ # record but continue
254+ summary ["errors" ].append (f"destroy:{ b .name } :{ e } " )
255+ try :
256+ existing .undefine ()
257+ summary ["removed" ] += 1
258+ except libvirt .libvirtError as e :
259+ summary ["errors" ].append (f"undefine:{ b .name } :{ e } " )
260+
261+ # Build network XML for a bridged network
262+ xml = (
263+ f"<network>"
264+ f"<name>{ b .name } </name>"
265+ f"<forward mode='bridge'/>"
266+ f"<bridge name='{ b .bridge_name } '/>"
267+ f"</network>"
268+ )
269+
270+ try :
271+ net = conn .networkDefineXML (xml )
272+ if net is None :
273+ summary ["errors" ].append (f"define-failed:{ b .name } " )
274+ else :
275+ summary ["defined" ] += 1
276+ # Set autostart and start if requested
277+ if b .autostart :
278+ try :
279+ net .setAutostart (1 )
280+ except libvirt .libvirtError as e :
281+ summary ["errors" ].append (f"set-autostart:{ b .name } :{ e } " )
282+ try :
283+ if net .isActive () != 1 :
284+ net .create ()
285+ summary ["started" ] += 1
286+ except libvirt .libvirtError as e :
287+ summary ["errors" ].append (f"start:{ b .name } :{ e } " )
288+ except libvirt .libvirtError as e :
289+ summary ["errors" ].append (f"defineXML:{ b .name } :{ e } " )
290+
291+ except Exception as e :
292+ # Catch-all per-bridge to avoid aborting the whole operation
293+ summary ["errors" ].append (f"unexpected:{ b .name } :{ e } " )
294+ continue
295+ return LibvirtNetworkBridgeApplyResponse (
296+ checked = summary ["checked" ],
297+ removed = summary ["removed" ],
298+ defined = summary ["defined" ],
299+ started = summary ["started" ],
300+ errors = summary ["errors" ],
301+ )
0 commit comments