Add scripts for managing VLANs
This commit is contained in:
		
						commit
						80f2a9f8d7
					
				
					 3 changed files with 180 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								LICENSE
									
										
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								LICENSE
									
										
									
									
									
										Symbolic link
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
UNLICENSE
 | 
			
		||||
							
								
								
									
										24
									
								
								UNLICENSE
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								UNLICENSE
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
This is free and unencumbered software released into the public domain.
 | 
			
		||||
 | 
			
		||||
Anyone is free to copy, modify, publish, use, compile, sell, or
 | 
			
		||||
distribute this software, either in source code form or as a compiled
 | 
			
		||||
binary, for any purpose, commercial or non-commercial, and by any
 | 
			
		||||
means.
 | 
			
		||||
 | 
			
		||||
In jurisdictions that recognize copyright laws, the author or authors
 | 
			
		||||
of this software dedicate any and all copyright interest in the
 | 
			
		||||
software to the public domain. We make this dedication for the benefit
 | 
			
		||||
of the public at large and to the detriment of our heirs and
 | 
			
		||||
successors. We intend this dedication to be an overt act of
 | 
			
		||||
relinquishment in perpetuity of all present and future rights to this
 | 
			
		||||
software under copyright law.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 | 
			
		||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 | 
			
		||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 | 
			
		||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 | 
			
		||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 | 
			
		||||
OTHER DEALINGS IN THE SOFTWARE.
 | 
			
		||||
 | 
			
		||||
For more information, please refer to <http://unlicense.org/>
 | 
			
		||||
							
								
								
									
										155
									
								
								vlans.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								vlans.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,155 @@
 | 
			
		|||
from dcim.models import Device, Interface
 | 
			
		||||
from ipam.models import FHRPGroup, FHRPGroupAssignment, IPAddress, Prefix, Role, VLAN, VLANGroup, VRF
 | 
			
		||||
from tenancy.models import Tenant
 | 
			
		||||
from extras.scripts import *
 | 
			
		||||
import netaddr
 | 
			
		||||
 | 
			
		||||
class CreateVLANScript(Script):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        name = 'Create VLAN'
 | 
			
		||||
        description = 'Create and configure a new VLAN on exit switches'
 | 
			
		||||
        scheduling_enabled = False
 | 
			
		||||
 | 
			
		||||
    vlan_name = StringVar(label='VLAN name', regex='[a-z-]', max_length=15)
 | 
			
		||||
    vlan_id = IntegerVar(label='VLAN ID', min_value=2, max_value=4094)
 | 
			
		||||
    tenant = ObjectVar(model=Tenant)
 | 
			
		||||
    vrf = ObjectVar(model=VRF, label='VRF')
 | 
			
		||||
    net4 = IPNetworkVar(label='IPv4 network', required=False,
 | 
			
		||||
            description='IPv4 network for this VLAN')
 | 
			
		||||
    net6 = IPNetworkVar(label='IPv6 network', required=False,
 | 
			
		||||
            description='IPv6 network for this VLAN')
 | 
			
		||||
 | 
			
		||||
    def run(self, data, commit):
 | 
			
		||||
        tenant = data['tenant']
 | 
			
		||||
        vlan_id = data['vlan_id']
 | 
			
		||||
        vlan_name = data['vlan_name']
 | 
			
		||||
        net4 = data.get('net4')
 | 
			
		||||
        net6 = data.get('net6')
 | 
			
		||||
        vrf = data['vrf']
 | 
			
		||||
 | 
			
		||||
        fri_it = Tenant.objects.get(name='FRI IT')
 | 
			
		||||
        new_net = VLANGroup.objects.get(name='new-net')
 | 
			
		||||
 | 
			
		||||
        # Get or create the VLAN.
 | 
			
		||||
        vlan, new = VLAN.objects.get_or_create(vid=vlan_id, group=new_net)
 | 
			
		||||
        vlan.tenant = tenant
 | 
			
		||||
        vlan.name = vlan_name
 | 
			
		||||
        vlan.full_clean()
 | 
			
		||||
        vlan.save()
 | 
			
		||||
        self.log_info(f'{"created" if new else "got"} VLAN {vlan}')
 | 
			
		||||
 | 
			
		||||
        # Get or create the FHRP group for virtual router IPs.
 | 
			
		||||
        fhrp_group, new = FHRPGroup.objects.get_or_create(name=vlan_name, group_id=vlan_id, protocol='other')
 | 
			
		||||
        self.log_info(f'{"created" if new else "got"} FHRP group {fhrp_group}')
 | 
			
		||||
 | 
			
		||||
        # Get or create prefixes.
 | 
			
		||||
        prefixes = []
 | 
			
		||||
        for net in [net4, net6]:
 | 
			
		||||
            if net:
 | 
			
		||||
                prefix, new = Prefix.objects.get_or_create(prefix=net, vrf=vrf)
 | 
			
		||||
                self.log_info(f'{"created" if new else "got"} prefix {prefix.prefix}')
 | 
			
		||||
                prefix.tenant = tenant
 | 
			
		||||
                prefix.vlan = vlan
 | 
			
		||||
                prefix.tenant = tenant
 | 
			
		||||
                prefix.role = None
 | 
			
		||||
                prefix.full_clean()
 | 
			
		||||
                prefix.save()
 | 
			
		||||
                prefixes += [prefix]
 | 
			
		||||
 | 
			
		||||
                vip, new = IPAddress.objects.get_or_create(address=netaddr.IPNetwork((prefix.prefix.first+1, prefix.prefix.prefixlen)), vrf=vrf)
 | 
			
		||||
                self.log_info(f'{"created" if new else "got"} vip {vip}')
 | 
			
		||||
                vip.tenant = fri_it
 | 
			
		||||
                vip.save()
 | 
			
		||||
                fhrp_group.ip_addresses.add(vip)
 | 
			
		||||
 | 
			
		||||
        fhrp_group.full_clean()
 | 
			
		||||
        fhrp_group.save()
 | 
			
		||||
 | 
			
		||||
        # Create or update bridge child interface on each exit.
 | 
			
		||||
        exits = Device.objects.filter(role__slug='switch', name__startswith='exit-').order_by('name')
 | 
			
		||||
        for index, switch in enumerate(exits):
 | 
			
		||||
            bridge = switch.interfaces.get(name='bridge')
 | 
			
		||||
            child, new = bridge.child_interfaces.get_or_create(device=switch, name=f'bridge.{vlan_id}')
 | 
			
		||||
            self.log_info(f'{"created" if new else "got"} interface {child} on {switch}')
 | 
			
		||||
 | 
			
		||||
            child.type = 'virtual'
 | 
			
		||||
            child.vrf = vrf
 | 
			
		||||
            child.mode = 'access'
 | 
			
		||||
            child.untagged_vlan = vlan
 | 
			
		||||
 | 
			
		||||
            fhrp_group_assignment, new = child.fhrp_group_assignments.get_or_create(group_id=fhrp_group.id, priority=0)
 | 
			
		||||
            self.log_info(f'{"created" if new else "got"} fhrp_group_assignment {fhrp_group_assignment}')
 | 
			
		||||
 | 
			
		||||
            child.full_clean()
 | 
			
		||||
            child.save()
 | 
			
		||||
 | 
			
		||||
            for prefix in prefixes:
 | 
			
		||||
                network = prefix.prefix
 | 
			
		||||
                addr_switch = netaddr.IPNetwork((network.first+2+index, network.prefixlen))
 | 
			
		||||
                address, new = child.ip_addresses.get_or_create(address=addr_switch)
 | 
			
		||||
                self.log_info(f'{"created" if new else "got"} address {address}')
 | 
			
		||||
                address.vrf = vrf
 | 
			
		||||
                address.tenant = fri_it
 | 
			
		||||
                address.role = ''
 | 
			
		||||
                address.full_clean()
 | 
			
		||||
                address.save()
 | 
			
		||||
 | 
			
		||||
        self.log_success(f'wee!')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SetVLANScript(Script):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        name = 'Set VLAN'
 | 
			
		||||
        description = 'Set tagged and untagged VLANs on access switch ports'
 | 
			
		||||
        fieldsets = (
 | 
			
		||||
            ('Ports', ('access_ports', 'switch', 'switch_ports')),
 | 
			
		||||
            ('Settings', ('vlans', 'enable'))
 | 
			
		||||
        )
 | 
			
		||||
        scheduling_enabled = False
 | 
			
		||||
 | 
			
		||||
    access_ports = MultiObjectVar(model=Device, query_params={'device_type': 'rj45-access-port'}, required=False,
 | 
			
		||||
            description='These ports will be traced to corresponding switch ports')
 | 
			
		||||
    switch = ObjectVar(model=Device, query_params={'role': 'switch'}, required=False,
 | 
			
		||||
            description='Limit selection to this switch')
 | 
			
		||||
    switch_ports = MultiObjectVar(model=Interface, query_params={'device_id': '$switch'}, required=False,
 | 
			
		||||
            description='Select switch ports directly')
 | 
			
		||||
    vlans = MultiObjectVar(model=VLAN, label='VLANs', required=False,
 | 
			
		||||
            description='Select multiple VLANs to put selected ports into tagged mode')
 | 
			
		||||
    enable = BooleanVar(label='Enable ports', default=True)
 | 
			
		||||
 | 
			
		||||
    def run(self, data, commit):
 | 
			
		||||
        all_ports = list(data['switch_ports'])
 | 
			
		||||
        modified_switches = set()
 | 
			
		||||
 | 
			
		||||
        # Trace doesn’t work rear ports for some reason, so do it manually.
 | 
			
		||||
        # Assumes this layout (f=front port, r=rear port, i=interface, ---=cable):
 | 
			
		||||
        # 1f:012.23:r1 --- 23r:panel-012:f23 --- 46i:sw-xyzzy
 | 
			
		||||
        for device in data['access_ports']:
 | 
			
		||||
            rearport = device.rearports.first()
 | 
			
		||||
            panel_rearport = rearport.link_peers[0]
 | 
			
		||||
            panel_frontport = panel_rearport.frontports.first()
 | 
			
		||||
            all_ports += panel_frontport.link_peers
 | 
			
		||||
 | 
			
		||||
        for port in all_ports:
 | 
			
		||||
            port.enabled = data['enable']
 | 
			
		||||
            match len(data['vlans']):
 | 
			
		||||
                case 0:
 | 
			
		||||
                    port.mode = 'access'
 | 
			
		||||
                    port.tagged_vlans.clear()
 | 
			
		||||
                    port.untagged_vlan = None
 | 
			
		||||
                case 1:
 | 
			
		||||
                    port.mode = 'access'
 | 
			
		||||
                    port.tagged_vlans.clear()
 | 
			
		||||
                    port.untagged_vlan = data['vlans'][0]
 | 
			
		||||
                case _:
 | 
			
		||||
                    port.mode = 'tagged'
 | 
			
		||||
                    port.tagged_vlans.set(data['vlans'])
 | 
			
		||||
                    port.untagged_vlan = None
 | 
			
		||||
            port.full_clean()
 | 
			
		||||
            port.save()
 | 
			
		||||
            modified_switches.add(port.device.name)
 | 
			
		||||
 | 
			
		||||
            self.log_info(f'{port.device.name} {port} is {port.mode} for {",".join(str(vlan.vid) for vlan in data["vlans"])}')
 | 
			
		||||
 | 
			
		||||
        self.log_success(f'modified switches {",".join(sorted(modified_switches))}')
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue