@@ -65,6 +65,9 @@ def _get_ip_config(context, name, inventory_hostname, defroute=None):
6565 'rx' , 'tx' , 'rx-max' , 'tx-max' , 'rx-jumbo' , 'rx-mini'
6666}
6767
68+ MAX_U32 = 2 ** 32 - 1
69+ MAX_VLAN_PRIORITY = 7
70+
6871
6972def _resolve_ethtool_feature_aliases (features ):
7073 """Convert ethtool feature aliases to canonical names.
@@ -290,6 +293,79 @@ def _get_bond_options(context, name, inventory_hostname):
290293 return bond_options
291294
292295
296+ def _validate_vlan_qos_map_int (value , network_name , direction , field ):
297+ if isinstance (value , bool ) or not isinstance (value , int ):
298+ raise ValueError (
299+ f"Network '{ network_name } ' has invalid { direction } QoS map "
300+ f"'{ field } ' value '{ value } '. Expected an integer." )
301+
302+ if value < 0 or value > MAX_U32 :
303+ raise ValueError (
304+ f"Network '{ network_name } ' has invalid { direction } QoS map "
305+ f"'{ field } ' value '{ value } '. Expected a value in range "
306+ f"0..{ MAX_U32 } ." )
307+
308+ if direction == "ingress" and field == "from" \
309+ and value > MAX_VLAN_PRIORITY :
310+ raise ValueError (
311+ f"Network '{ network_name } ' has invalid ingress QoS map "
312+ f"'from' value '{ value } '. Maximum VLAN priority is "
313+ f"{ MAX_VLAN_PRIORITY } ." )
314+
315+ if direction == "egress" and field == "to" \
316+ and value > MAX_VLAN_PRIORITY :
317+ raise ValueError (
318+ f"Network '{ network_name } ' has invalid egress QoS map "
319+ f"'to' value '{ value } '. Maximum VLAN priority is "
320+ f"{ MAX_VLAN_PRIORITY } ." )
321+
322+ return value
323+
324+
325+ def _validate_vlan_qos_map_entry (entry , network_name , direction ):
326+ if not isinstance (entry , dict ):
327+ raise ValueError (
328+ f"Network '{ network_name } ' has invalid { direction } QoS map "
329+ "entry format. Expected a dict with keys 'from' and 'to'." )
330+
331+ if "from" not in entry or "to" not in entry :
332+ raise ValueError (
333+ f"Network '{ network_name } ' has invalid { direction } QoS map "
334+ "entry. Required keys are 'from' and 'to'." )
335+
336+ return {
337+ "from" : _validate_vlan_qos_map_int (
338+ entry ["from" ], network_name , direction , "from" ),
339+ "to" : _validate_vlan_qos_map_int (
340+ entry ["to" ], network_name , direction , "to" )
341+ }
342+
343+
344+ def _validate_and_sort_vlan_qos_map (raw_map , network_name , direction ):
345+ if raw_map is None :
346+ return None
347+
348+ if not isinstance (raw_map , list ):
349+ raise ValueError (
350+ f"Network '{ network_name } ' has invalid { direction } QoS map "
351+ f"format '{ type (raw_map ).__name__ } '. Expected a list of dicts "
352+ "with keys 'from' and 'to'." )
353+
354+ normalized = []
355+ for entry in raw_map :
356+ normalized .append (_validate_vlan_qos_map_entry (
357+ entry , network_name , direction ))
358+
359+ normalized .sort (key = lambda item : (item ["from" ], item ["to" ]))
360+ return normalized
361+
362+
363+ def _get_vlan_qos_map (context , name , inventory_hostname , direction ):
364+ qos_map = networks .net_attr (
365+ context , name , f"{ direction } _qos_map" , inventory_hostname )
366+ return _validate_and_sort_vlan_qos_map (qos_map , name , direction )
367+
368+
293369@jinja2 .pass_context
294370def nmstate_config (context , names , inventory_hostname = None ):
295371 interfaces = {}
@@ -514,6 +590,17 @@ def get_iface(name):
514590 "base-iface" : parent ,
515591 "id" : int (vlan_id )
516592 }
593+
594+ ingress_qos_map = _get_vlan_qos_map (
595+ context , name , inventory_hostname , "ingress" )
596+ if ingress_qos_map is not None :
597+ iface ["vlan" ]["ingress-qos-map" ] = ingress_qos_map
598+
599+ egress_qos_map = _get_vlan_qos_map (
600+ context , name , inventory_hostname , "egress" )
601+ if egress_qos_map is not None :
602+ iface ["vlan" ]["egress-qos-map" ] = egress_qos_map
603+
517604 # Ensure parent is initialized
518605 get_iface (parent )
519606
0 commit comments