Initial commit, squashed
This commit is contained in:
		
						commit
						158e8740b8
					
				
					 83 changed files with 2718 additions and 0 deletions
				
			
		
							
								
								
									
										56
									
								
								filter_plugins/cumulus.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								filter_plugins/cumulus.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| #!/usr/bin/python | ||||
| 
 | ||||
| import ipaddress | ||||
| import itertools | ||||
| import re | ||||
| 
 | ||||
| class FilterModule(object): | ||||
|     '''Filters for Cumulus Linux switches''' | ||||
|     def __init__(self): | ||||
|         self.iface_regex = re.compile(r'swp([0-9]+)(?:s([0-9]+))?') | ||||
| 
 | ||||
|     def filters(self): | ||||
|         return { | ||||
|             'cl_iface_index': self.cl_iface_index, | ||||
|             'cl_clag_id': self.cl_clag_id, | ||||
|             'cl_clag_sys_mac': self.cl_clag_sys_mac | ||||
|         } | ||||
| 
 | ||||
|     def cl_iface_index(self, interfaces): | ||||
|         ''' | ||||
|         Return interface index from its name, eg. swp3s2 → 17 | ||||
| 
 | ||||
|         Interfaces may be broken out into up to four subinterfaces. Indexing | ||||
|         starts at 5 and proceeds as: | ||||
| 
 | ||||
|             swp1 swp1s0 swp1s1 swp1s2 swp1s3 swp2 swp2s0 … | ||||
|         ''' | ||||
| 
 | ||||
|         for interface in interfaces: | ||||
|             if m := self.iface_regex.fullmatch(interface.get('name', '')): | ||||
|                 yield 5 * (int(m.group(1))) + (int(m.group(2))+1 if m.group(2) else 0) | ||||
| 
 | ||||
|     def cl_clag_id(self, interfaces): | ||||
|         ''' | ||||
|         Generate a clag-id from a list of interfaces making up a MLAG bond | ||||
| 
 | ||||
|         The clag-id for a bond must be between 1 and 65535. Generate it by | ||||
|         combining the indexes of the lowest-numbered interface on each switch. | ||||
|         This avoids manual ID assignment while keeping them mostly stable. | ||||
| 
 | ||||
|         This clag-id does not change unless the lowest-numbered interface for | ||||
|         the bond on any switch changes. IDs of other bonds are not affected. | ||||
|         ''' | ||||
|         clag_id = 0 | ||||
|         key = lambda i: i.get('device', {}).get('name') | ||||
|         for device, ifaces in itertools.groupby(sorted(interfaces, key=key), key): | ||||
|             clag_id = 256 * clag_id + min(self.cl_iface_index(ifaces)) | ||||
|         if 1 <= clag_id <= 65535: # sanity checking | ||||
|             return clag_id | ||||
| 
 | ||||
|     def cl_clag_sys_mac(self, address): | ||||
|         '''Generate a system MAC address for a MLAG with the given address''' | ||||
| 
 | ||||
|         index = int(ipaddress.ip_address(address)) % 2**16 | ||||
|         suffix = f'{index:04x}' | ||||
|         return f'44:38:39:ff:{suffix[:2]}:{suffix[2:]}' | ||||
							
								
								
									
										39
									
								
								filter_plugins/netbox.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								filter_plugins/netbox.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| #!/usr/bin/python | ||||
| 
 | ||||
| class FilterModule(object): | ||||
|     '''Various utilities for manipulating NetBox data''' | ||||
|     def __init__(self): | ||||
|         self.virtual_iface_types = ('bridge', 'lag', 'virtual') | ||||
| 
 | ||||
|     def filters(self): | ||||
|         return { | ||||
|             'iface_real': self.iface_real, | ||||
|             'iface_peer': self.iface_peer, | ||||
|             'iface_vlans': self.iface_vlans | ||||
|         } | ||||
| 
 | ||||
|     def iface_real(self, interfaces): | ||||
|         '''Return only non-virtual interfaces''' | ||||
|         for iface in interfaces: | ||||
|             if iface.get('type', {}).get('value') not in self.virtual_iface_types: | ||||
|                 yield iface | ||||
| 
 | ||||
|     def iface_peer(self, interfaces): | ||||
|         '''Return the name of the device connected to this interface''' | ||||
|         for iface in interfaces: | ||||
|             endpoint = (iface.get('connected_endpoints') or [{}])[0] | ||||
|             if device := endpoint.get('device', {}).get('name'): | ||||
|                 yield device | ||||
|             else: | ||||
|                 yield None | ||||
| 
 | ||||
|     def iface_vlans(self, interfaces): | ||||
|         '''Returns a list of allowed VLANs for each interface''' | ||||
|         for iface in interfaces: | ||||
|             match iface.get('mode', {}).get('value'): | ||||
|                 case 'access': | ||||
|                     yield [iface.get('untagged_vlan')] if 'untagged_vlan' in iface else [] | ||||
|                 case 'tagged': | ||||
|                     yield iface.get('tagged_vlans', []) | ||||
|                 case _: | ||||
|                     yield [] | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue