from django.contrib.contenttypes.models import ContentType from django.utils.text import slugify from dcim.choices import DeviceStatusChoices, InterfaceModeChoices, InterfaceTypeChoices, SiteStatusChoices from dcim.models import Cable, CableTermination, Device, DeviceRole, DeviceType, Interface, Manufacturer, Site from extras.models import Tag from extras.scripts import * from ipam.models import IPAddress, Prefix, VLAN, VLANGroup, Role from ipam.choices import PrefixStatusChoices, IPAddressFamilyChoices from netaddr import IPNetwork import random from utilities.exceptions import AbortScript # Used for getting existing types/objects from Netbox. DISTRIBUTION_SWITCH_DEVICE_ROLE = 'distribution-switch' # match the name or the slug ROUTER_DEVICE_ROLE = 'router' CORE_DEVICE_ROLE = 'core' ACCESS_SWITCH_DEVICE_ROLE = DeviceRole.objects.get(name='Access Switch') DEFAULT_SITE = Site.objects.get(slug='floor') DEFAULT_DEVICE_TYPE = DeviceType.objects.get(model='EX2200-48T') TAGS = [Tag.objects.get(name='dhcp-client')] FLOOR_MGMT_VLAN = VLAN.objects.get(name="edge-mgmt.floor.r1.tele") VLAN_GROUP_FLOOR = VLANGroup.objects.get(slug="floor") MULTIRATE_DEVICE_TYPE = DeviceType.objects.get(model="EX4300-48MP") CORE_DEVICE = Device.objects.get(name="r1.tele") CORE_INTERFACE_FLOOR = Interface.objects.get(device=CORE_DEVICE, description="d1.roof") # Copied from examples/tg19/netbox_tools/switchestxt2netbox.py def parse_switches_txt(switches_txt_lines, logger): distros = {} switches = {} for switch in switches_txt_lines: # example: # e7-1 88.92.80.0/26 2a06:5844:e:71::/64 88.92.0.66/26 2a06:5841:d:2::66/64 1071 s2.floor switch = switch.strip().split() if len(switch) == 0: # skip empty lines continue switches[switch[0]] = { 'sysname': switch[0], 'subnet4': switch[1], 'subnet6': switch[2], 'mgmt4': switch[3], 'mgmt6': switch[4], 'vlan_id': int(switch[5]), 'distro_name': switch[6], 'is_distro': False, 'device_type': DEFAULT_DEVICE_TYPE, 'lag_name': "ae0", } return switches def parse_patchlist_txt(patchlist_txt_lines, switches, logger): for patchlist in patchlist_txt_lines: columns = patchlist.split() switch_name = columns[0] if 'multirate' in patchlist: switches[switch_name]['device_type'] = MULTIRATE_DEVICE_TYPE uplinks = [] links = columns[2:] for link in links: # Skip columns with comments if 'ge-' in link or 'mge-' in link: uplinks.append(link) switches[switch_name]['uplinks'] = uplinks class Planning2Netbox(Script): class Meta: name = "Planning to netbox" description = "Import output from planning into netbox" commit_default = False field_order = ['site_name', 'switch_count', 'switch_model'] fieldsets = "" switches_txt = TextVar( description="Switch output from planning", ) patchlist_txt = TextVar( description="Patchlist output from planning", ) def run(self, data, commit): planning_tag, _created = Tag.objects.get_or_create(name="from-planning") switches_txt_lines = data['switches_txt'].split('\n') # clean "file" content for i in range(0, len(switches_txt_lines)-1): switches_txt_lines[i] = switches_txt_lines[i].strip() patchlist_txt_lines = data['patchlist_txt'].split('\n') # clean "file" content for i in range(0, len(patchlist_txt_lines)-1): patchlist_txt_lines[i] = patchlist_txt_lines[i].strip() switches = parse_switches_txt(switches_txt_lines, self.log_debug) patchlist = parse_patchlist_txt(patchlist_txt_lines, switches, self.log_debug) self.log_info(f"Configuring {len(switches)} switches") self.log_info("Importing switches") for switch_name in switches: data = switches[switch_name] self.log_debug(f"Creating switch {switch_name} from {data}") switch, created_switch = Device.objects.get_or_create( name=switch_name, device_type=data['device_type'], device_role=ACCESS_SWITCH_DEVICE_ROLE, site=DEFAULT_SITE, ) if not created_switch: self.log_info(f"Updating existing switch: {switch.name}") distro = Device.objects.get(name=data['distro_name']) mgmt_vlan = FLOOR_MGMT_VLAN ae_interface = None ae_interface, created_ae_interface = Interface.objects.get_or_create( device=switch, name=f"{data['lag_name']}", description=distro.name, type=InterfaceTypeChoices.TYPE_LAG, mode=InterfaceModeChoices.MODE_TAGGED, ) ae_interface.tagged_vlans.add(mgmt_vlan) # distro side distro_ae_interface, created_distro_ae_interface = Interface.objects.get_or_create( device=distro, name=f"ae{data['vlan_id']}", # TODO: can we get this from tagged vlans on ae? description=switch.name, type=InterfaceTypeChoices.TYPE_LAG, mode=InterfaceModeChoices.MODE_TAGGED, ) if not created_distro_ae_interface: self.log_info(f"Updated existing distro interface: {distro_ae_interface}") distro_ae_interface.tagged_vlans.add(mgmt_vlan) vlan_interface, _created_vlan_interface = Interface.objects.get_or_create( device=switch, name=f"vlan.{mgmt_vlan.vid}", description=f"mgmt.{distro.name}", type=InterfaceTypeChoices.TYPE_VIRTUAL, mode=InterfaceModeChoices.MODE_TAGGED, ) traffic_vlan, _created_traffic_vlan = VLAN.objects.get_or_create( name=switch.name, vid=data['vlan_id'], group=VLAN_GROUP_FLOOR, ) ae_interface.tagged_vlans.add(traffic_vlan) ae_interface.tagged_vlans.add(traffic_vlan) # patchlist switch_uplinks = data['uplinks'] #self.log_debug(f"uplinks: {switch_uplinks}") # from planning we always cable from port 44 and upwards # except for multirate then we always use 47 and 48 # 'ge-0/0/44' or 'mge-0/0/47' is_multirate = 'mge' in switch_uplinks[0] uplink_port = 46 if is_multirate else 44 uplink_port_name = "mge-0/0/{}" if is_multirate else "ge-0/0/{}" interface_type = ContentType.objects.get_for_model(Interface) for distro_port in switch_uplinks: distro_interface = Interface.objects.get( device=distro, name=distro_port, ) distro_interface.lag = distro_ae_interface distro_interface.save() switch_uplink_interface = Interface.objects.get( device=switch, name=uplink_port_name.format(uplink_port), ) switch_uplink_interface.lag = ae_interface switch_uplink_interface.save() # Delete A cable termination if it exists try: CableTermination.objects.get( cable_end='A', termination_id=distro_interface.id, termination_type=interface_type, ).delete() except CableTermination.DoesNotExist: pass # Delete B cable termination if it exists try: CableTermination.objects.get( cable_end='B', termination_id=switch_uplink_interface.id, termination_type=interface_type, ).delete() except CableTermination.DoesNotExist: pass # Create cable now that we've cleaned up the cable mess. cable = Cable.objects.create() a = CableTermination.objects.create( cable=cable, cable_end='A', termination_id=distro_interface.id, termination_type=interface_type, ) b = CableTermination.objects.create( cable_end='B', cable=cable, termination_id=switch_uplink_interface.id, termination_type=interface_type, ) cable = Cable.objects.get(id=cable.id) # https://github.com/netbox-community/netbox/discussions/10199 cable._terminations_modified = True cable.save() cable.tags.add(planning_tag) #self.log_debug(f"Cabled switch port {b} to distro port {a}") uplink_port += 1 # Set mgmt ip mgmt_addr_v4, _ = IPAddress.objects.get_or_create( address=data['mgmt4'], ) mgmt_addr_v6, _ = IPAddress.objects.get_or_create( address=data['mgmt6'], ) switch.primary_ip4 = mgmt_addr_v4 switch.primary_ip6 = mgmt_addr_v6 switch.save() # Set prefix prefix_v4, _ = Prefix.objects.get_or_create( prefix=data['subnet4'], vlan=traffic_vlan, ) prefix_v6, _ = Prefix.objects.get_or_create( prefix=data['subnet6'], vlan=traffic_vlan, ) core_subinterface, _ = Interface.objects.get_or_create( device=CORE_DEVICE, parent=CORE_INTERFACE_FLOOR, name=f"{CORE_INTERFACE_FLOOR.name}.{traffic_vlan.vid}", description=switch.name, type=InterfaceTypeChoices.TYPE_VIRTUAL, mode=InterfaceModeChoices.MODE_TAGGED, ) # Set gw addrs # We "manually create" an IP address from the defined # network (instead of from the Prefix object) # because the Prefix is not persisted in the database yet, # and then some of the features of it doesn't work, # e.g. prefix.get_first_available_ip(). uplink_addr_v4, _ = IPAddress.objects.get_or_create( address=IPNetwork(data['subnet4'])[1], ) uplink_addr_v6, _ = IPAddress.objects.get_or_create( address=IPNetwork(data['subnet6'])[1], ) core_subinterface.ip_addresses.add(uplink_addr_v4) core_subinterface.ip_addresses.add(uplink_addr_v6) core_subinterface.tagged_vlans.add(traffic_vlan) # Add tag to everything we created so it's easy to identify in case we # want to recreate things_we_created = [ switch, ae_interface, distro_ae_interface, vlan_interface, traffic_vlan, prefix_v4, prefix_v6, mgmt_addr_v4, mgmt_addr_v6, uplink_addr_v4, uplink_addr_v6, core_subinterface, ] for thing in things_we_created: thing.tags.add(planning_tag)