diff options
Diffstat (limited to 'tools/netbox/scripts/create-switch/create-switch.py')
-rw-r--r-- | tools/netbox/scripts/create-switch/create-switch.py | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/tools/netbox/scripts/create-switch/create-switch.py b/tools/netbox/scripts/create-switch/create-switch.py new file mode 100644 index 0000000..23b7250 --- /dev/null +++ b/tools/netbox/scripts/create-switch/create-switch.py @@ -0,0 +1,202 @@ +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 + +# Used for getting existing types/objects from Netbox. +DISTRIBUTION_SWITCH_DEVICE_ROLE = 'distribution-switch' # match the name or the slug +ACCESS_SWITCH_DEVICE_ROLE = DeviceRole.objects.get(name='Access Switch') +DEFAULT_SITE = Site.objects.first() # TODO: pick default site ? + +class CreateSwitch(Script): + + class Meta: + name = "Create Switch" + description = "Provision a new switch" + field_order = ['site_name', 'switch_count', 'switch_model'] + + switch_name = StringVar( + description="Switch name" + ) + device_type = ObjectVar( + description="Device model", + model=DeviceType, + ) + role = ObjectVar( + description="Device role", + model=DeviceRole, + default=ACCESS_SWITCH_DEVICE_ROLE.id, + ) + site = ObjectVar( + description = "Site", + model=Site, + default=DEFAULT_SITE.id, + ) + destination_device = ObjectVar( + description = "Destination/uplink", + model=Device, + query_params={ + 'role': DISTRIBUTION_SWITCH_DEVICE_ROLE, + }, + ) + destination_interfaces = MultiObjectVar( + description="Destination interface(s)", + model=Interface, + query_params={ + 'device_id': '$destination_device', + # ignore interfaces aleady cabled https://github.com/netbox-community/netbox/blob/v3.4.5/netbox/dcim/filtersets.py#L1225 + 'cabled': False, + } + ) + vlan_group = ObjectVar( + label="VLAN Group", + description="VLAN Group", + model=VLANGroup, + ) + vlan_id = IntegerVar( + label="VLAN ID", + description="Auto-assigned if not specified. Make sure it is available if you provide it.", + required=False, + default='', + ) + mgmt_vlan = ObjectVar( + description="Management VLAN", + model=VLAN, + query_params={ + 'vid': [666, 667], + } + ) + mgmt_prefix_v4 = ObjectVar( + description="IPv4 Prefix to assign a management IP Address from", + model=Prefix, + query_params={ + 'family': 4, + 'vlan_id': '$mgmt_vlan' + } + ) + mgmt_prefix_v6 = ObjectVar( + description="IPv6 Prefix to assign a management IP Address from", + model=Prefix, + query_params={ + 'family': 6, + 'vlan_id': '$mgmt_vlan' + } + ) + tags = MultiObjectVar( + description="Tags to be sent to Gondul. These are used for templating, so be sure what they do.", + model=Tag, + ) + + def run(self, data, commit): + mgmt_vlan = data['mgmt_vlan'] + + # Create the new switch + switch = Device( + name=data['switch_name'], + device_type=data['device_type'], + device_role=data['role'], + site=data['site'], + ) + switch.save() + self.log_success(f"Created new switch: <a href=\"{switch.get_absolute_url()}\">{switch}</a>") + + vlan_group = data['vlan_group'] + vid = vlan_group.get_next_available_vid() + # use provided vid if specified. + if data['vlan_id']: + vid = data['vlan_id'] + vlan = VLAN.objects.create( + name=switch.name, + group=vlan_group, + vid=vid + ) + vlan.save() + + mgmt_vlan_interface = Interface.objects.create( + device=switch, + name=f"vlan.{mgmt_vlan.vid}", + type=InterfaceTypeChoices.TYPE_VIRTUAL, + mode=InterfaceModeChoices.MODE_TAGGED, + ) + mgmt_vlan_interface.tagged_vlans.add(mgmt_vlan.id) + + uplink_ae = Interface.objects.create( + device=switch, + name="ae0", + description=data['destination_device'].name, + type=InterfaceTypeChoices.TYPE_LAG, + mode=InterfaceModeChoices.MODE_TAGGED, + ) + uplink_ae.tagged_vlans.add(mgmt_vlan.id) + uplink_vlan = Interface.objects.create( + device=switch, + name="ae0.0", + description=data['destination_device'].name, + type=InterfaceTypeChoices.TYPE_VIRTUAL, + parent=uplink_ae, + ) + destination_ae = Interface.objects.create( + device=data['destination_device'], + name=f"ae{vlan.vid}", + description=switch.name, + type=InterfaceTypeChoices.TYPE_LAG, + mode=InterfaceModeChoices.MODE_TAGGED, + ) + destination_ae.tagged_vlans.add(mgmt_vlan.id) + destination_vlan = Interface.objects.create( + device=data['destination_device'], + name=f"vlan.{vid}", + description=switch.name, + type=InterfaceTypeChoices.TYPE_VIRTUAL, + parent=destination_ae, + ) + self.log_success("Created AE and VLAN interfaces for both ends") + + v4_mgmt_addr = IPAddress.objects.create( + address=data['mgmt_prefix_v4'].get_first_available_ip(), + ) + v6_mgmt_addr = IPAddress.objects.create( + address=data['mgmt_prefix_v6'].get_first_available_ip(), + ) + mgmt_vlan_interface.ip_addresses.add(v4_mgmt_addr) + mgmt_vlan_interface.ip_addresses.add(v6_mgmt_addr) + + num_uplinks = len(data['destination_interfaces']) + interfaces = list(Interface.objects.filter(device=switch).exclude(type=InterfaceTypeChoices.TYPE_VIRTUAL).exclude(type=InterfaceTypeChoices.TYPE_LAG)) + interface_type = ContentType.objects.get_for_model(Interface) + for uplink_num in range(0, num_uplinks): + # mark last ports as uplinks + a_interface = data['destination_interfaces'][::-1][uplink_num] + b_interface = interfaces[(uplink_num * -1) -1] + + # Configure uplink as AE0 + b_interface.parent = uplink_ae + b_interface.save() + + # Configure downlink on destination + a_interface.parent = destination_ae + a_interface.save() + + cable = Cable.objects.create() + a = CableTermination.objects.create( + cable=cable, + cable_end='A', + termination_id=a_interface.id, + termination_type=interface_type, + ) + b = CableTermination.objects.create( + cable_end='B', + cable=cable, + termination_id=b_interface.id, + termination_type=interface_type, + ) + cable = Cable.objects.get(id=cable.id) + # TODO: fix cables being 'connected' + #cable.a_terminations = [a] + #cable.b_terminations = [b] + cable.save() + self.log_success(f"Cabled {data['destination_device']} {a_interface} to {switch} {b_interface}") |