1515from ..data import get_box
1616from ..data .types import must_be_dict , must_be_id , must_be_list , transform_asdot
1717from ..data .validate import get_object_attributes , validate_attributes
18- from ..modules import bgp
18+ from ..modules import bgp , propagate_global_modules
1919from ..utils import log
2020
21+ '''
22+ Return computed members of special-purpose node group ('all' or device)
23+ '''
24+ def special_node_group_members (group : str , topology : Box ) -> typing .Optional [list ]:
25+ if group == 'all' :
26+ return list (topology .nodes )
27+
28+ if group in topology .defaults .devices :
29+ return [ node for node ,ndata in topology .nodes .items () if ndata .get ('device' ,None ) == group ]
30+
31+ return None
32+
2133'''
2234Return members of the specified group. Recurse through child groups if needed
2335'''
@@ -37,10 +49,16 @@ def group_members(topology: Box, group: str, grp_type: str = 'node', count: int
3749 header = True )
3850
3951 gdata = topology .groups [group ]
40- if gdata .get ('type' ,'node' ) != grp_type :
52+ g_type = gdata .get ('type' ,'node' )
53+ if g_type != grp_type :
4154 return []
4255
43- for m in gdata .members :
56+ if g_type == 'node' :
57+ sg_members = special_node_group_members (group ,topology )
58+ if not sg_members is None :
59+ return sg_members
60+
61+ for m in gdata .get ('members' ,[]):
4462 m_ns = grp_type + 's'
4563 if m in topology [m_ns ]:
4664 members = members + [ m ]
@@ -236,6 +254,11 @@ def check_group_data_structure(
236254 more_hints = 'Device names or "all" cannot be used as names for user-defined groups' ,
237255 category = log .IncorrectValue ,
238256 module = 'groups' )
257+ if grp in topology .defaults .devices and 'device' in gdata :
258+ log .error (
259+ text = f'Trying to change the device to { gdata .device } in device group { grp } is ridiculous' ,
260+ category = log .IncorrectAttr ,
261+ module = 'groups' )
239262
240263 must_be_list (gdata ,'module' ,gpath ,create_empty = False ,module = 'groups' ,valid_values = sorted (list_of_modules ))
241264
@@ -452,39 +475,64 @@ def reverse_topsort(topology: Box) -> list:
452475Copy group-level module or device setting into node data
453476'''
454477def copy_group_device_module (topology : Box ) -> None :
455- for grp in reverse_topsort (topology ):
478+ sorted_groups = reverse_topsort (topology )
479+ for grp in sorted_groups : # First, set the device type
456480 gdata = topology .groups [grp ]
457- if not 'device' in gdata and not 'module' in gdata :
458- continue # This group is not interesting, move on
481+ if not 'device' in gdata :
482+ continue # This group is not interesting, move on
459483
460484 if log .debug_active ('groups' ):
461- print (f'Setting device/module for group { grp } ' )
485+ print (f'Setting device for group { grp } : { gdata . device } ' )
462486 g_members = group_members (topology ,grp )
463487 if not g_members and not gdata .get ('_default_group' ,False ):
464488 log .error (
465- f'Cannot use "module" or " device" attribute on in group { grp } that has no direct or indirect members' ,
489+ f'Cannot use "device" attribute in group { grp } that has no direct or indirect members' ,
466490 log .IncorrectValue ,
467491 'groups' )
468492 continue
469493
470- for name in g_members : # Iterate over group members
471- if not name in topology .nodes : # Member not a node? Move on...
494+ for name in g_members : # Iterate over group members
495+ if not name in topology .nodes : # Member not a node? Weird, move on...
472496 continue
473497
474498 ndata = topology .nodes [name ]
475- if 'device' in gdata and not 'device' in ndata : # Copy device from group data to node data
499+ if 'device' not in ndata : # Copy device from group data to node data
476500 ndata .device = gdata .device
477501 if log .debug_active ('groups' ):
478502 print (f'... setting { name } .device to { gdata .device } ' )
479503
480- if 'module' in gdata : # Merge group modules with device modules
481- ndata . module = ndata . module or [ ] # Make sure node.module is a list
482- for m in gdata . module : # Now iterate over group modules
483- if not m in ndata . module : # ... and add missing modules to nodes
484- ndata . module . append ( m )
504+ for grp in sorted_groups : # Next, augment device modules
505+ gdata = topology . groups [ grp ] # We have to do this after augmenting devices
506+ # ... because modules could be specified on a device group
507+ if 'module' not in gdata : # No module specified on this group
508+ continue # ... move on
485509
486- if log .debug_active ('groups' ):
487- print (f'... adding module { gdata .module } to { name } . Node modules now { ndata .module } ' )
510+ if log .debug_active ('groups' ):
511+ print (f'Setting module for group { grp } : { gdata .module } ' )
512+ g_members = group_members (topology ,grp )
513+ if not g_members and not gdata .get ('_default_group' ,False ):
514+ log .error (
515+ f'Cannot use "module" attribute in group { grp } that has no direct or indirect members' ,
516+ log .IncorrectValue ,
517+ 'groups' )
518+ continue
519+
520+ for name in g_members : # Iterate over group members
521+ if not name in topology .nodes : # Member not a node? Move on...
522+ continue
523+
524+ ndata = topology .nodes [name ] # Get node data
525+ if 'module' not in ndata : # Group can set module(s) to empty, so we have to
526+ ndata .module = [] # ... start with an empty list
527+
528+ if not isinstance (ndata .module ,list ): # We'll let someone else deal with incorrect
529+ continue # ... ndata.module data type
530+
531+ for m in gdata .module : # ... iterate over group modules
532+ data .append_to_list (ndata ,'module' ,m ) # ... and append group modules to node.module list
533+
534+ if log .debug_active ('groups' ):
535+ print (f'... adding module { gdata .module } to { name } . Node modules now { ndata .module } ' )
488536
489537'''
490538Copy node data from group into group members
@@ -587,7 +635,12 @@ def create_bgp_autogroups(topology: Box) -> None:
587635 'groups' )
588636
589637 for n_name ,n_data in topology .nodes .items (): # Now iterate over nodes
590- n_module = n_data .get ('module' ,g_module ) # Get node or global module (global modules haven't been propagated yet)
638+ #
639+ # Get node or global module (global modules haven't been propagated yet) taking into account
640+ # whether the global modules will be propagated into the node
641+ #
642+ g_propagate = propagate_global_modules (n_data ,topology )
643+ n_module = n_data .get ('module' ,g_module if g_propagate else [])
591644 if not isinstance (n_module ,list ): # Node list of modules is insane, someone else will complain
592645 continue
593646
0 commit comments