aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorslinderud <simen.linderud@gmail.com>2023-03-29 23:12:10 +0200
committerHåkon Solbjørg <hakon@solbj.org>2023-03-29 23:12:10 +0200
commit2304baffdeaac54863d377ec048688b58d6ea29e (patch)
treed436c9c1e9ecc54b5b5b800fc778b546cb0a87f7
parentbfa92e35d394d30ffaaa3fe0d518c988e0ea931b (diff)
feat(create-switch): Import Simen changes
-rw-r--r--tools/netbox/scripts/create-switch/create-switch.py336
1 files changed, 275 insertions, 61 deletions
diff --git a/tools/netbox/scripts/create-switch/create-switch.py b/tools/netbox/scripts/create-switch/create-switch.py
index ee3196a..cdba028 100644
--- a/tools/netbox/scripts/create-switch/create-switch.py
+++ b/tools/netbox/scripts/create-switch/create-switch.py
@@ -5,143 +5,351 @@ from dcim.choices import DeviceStatusChoices, InterfaceModeChoices, InterfaceTyp
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
+from ipam.models import IPAddress, Prefix, VLAN, VLANGroup, Role
+from ipam.choices import PrefixStatusChoices, IPAddressFamilyChoices
+import random
+
+from utilities.exceptions import AbortScript
+
+# self.log_success for successfull creation
+# self.log_info for FYI information
+
+# Todo:
+# * Tag switch based on this so config in templates is correct, see tags in tech-templates
+# * https://github.com/gathering/tech-templates
+# * We should be able to choose a VLAN that actually exists. This will make switch delivery on stand MUCH easier
# 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.first() # TODO: pick default site ?
+DEFAULT_SITE = Site.objects.get(slug='ring') # Site.objects.first() # TODO: pick default site ?
+DEFAULT_L1_SWITCH = Device.objects.get(name='d1.ring') # Site.objects.first() # TODO: pick default site ?
+DEFAULT_DEVICE_TYPE = DeviceType.objects.get(model='EX2200-48T') # Site.objects.first() # TODO: pick default site ?
+
+UPLINK_TYPES = (
+ (InterfaceTypeChoices.TYPE_10GE_SFP_PLUS, '10G SFP+'),
+ (InterfaceTypeChoices.TYPE_1GE_FIXED, '1G CAT'),
+ (InterfaceTypeChoices.TYPE_10GE_FIXED, '10G CAT')
+)
+
+LEVERANSE_TYPES = (
+ (DeviceRole.objects.get(name='Access Switch'), 'Access Switch'),
+ (DeviceRole.objects.get(name='Distribution Switch'), '"Utskutt" distro')
+)
+
+# Helper functions
+def generateMgmtVlan(self, data):
+ name = ''
+
+ if data['leveranse'].name == "Access Switch":
+ name += "edge-mgmt."
+ elif data['leveranse'].name == "Distribution Switch":
+ name += "distro-mgmt."
+ else:
+ raise AbortScript(f"Tbh, i only support access_switch and distro_switch in role")
+
+ if "ring" in data['site'].slug or "floor" in data['site'].slug:
+ name += data['site'].slug + ".r1.tele"
+ elif "stand" in data['site'].slug:
+ name += data['site'].slug + ".r1.stand"
+ else:
+ raise AbortScript(f"I only support creating switches in floor, ring or stand")
+
+ return VLAN.objects.get(name=name)
+
+# Cheeky, let's just do this hardcoded...
+def getL3(self, data):
+ if data['site'].slug == "ring":
+
+ l3Term = Device.objects.get(
+ name='r1.tele'
+ )
+ l3Intf = Interface.objects.get(
+ device=l3Term.id,
+ name='ae11'
+ )
+
+ elif data['site'].slug == "stand":
+ l3Term = Device.objects.get(
+ name='r1.stand'
+ )
+ l3Intf = "NOT IMPLEMENTED LOCAL VLAN OPTION. THIS USECAE DOESN'T WORK"
+
+ elif data['site'].slug == "floor":
+ l3Term = Device.objects.get(
+ name='r1.tele'
+ )
+ l3Intf = Interface.objects.get(
+ device=l3Term.id,
+ name='ae10'
+ )
+ else:
+ raise AbortScript(f"I only support creating switches in floor, ring or stand")
+
+ self.log_info(f"l3Term: {l3Term}, l3Intf {l3Intf}")
+ return l3Term, l3Intf
+
+def generatePrefix(prefix, length):
+
+ firstPrefix = prefix.get_first_available_prefix()
+ out = list(firstPrefix.subnet(length, count=1))[0]
+ return out
+
+def getDeviceRole(type):
+ if type == "Access Switch":
+ out = DeviceRole.objects.get(name='Access Switch')
+ elif type == "Distribution Switch":
+ out = DeviceRole.objects.get(name='Distribution Switch')
+ return out
+
class CreateSwitch(Script):
class Meta:
name = "Create Switch"
description = "Provision a new switch"
+ commit_default = False
field_order = ['site_name', 'switch_count', 'switch_model']
+ fieldsets = ""
+
+ leveranse = ChoiceVar(
+ label='Leveranse Type',
+ description="Pick the appropriate leveranse type",
+ choices=LEVERANSE_TYPES,
+ #default=ACCESS_SWITCH_DEVICE_ROLE.id,
+ )
switch_name = StringVar(
- description="Switch name"
+ description="Switch name. Remember, e = access switch, d = distro switch"
+ )
+
+ uplink_type = ChoiceVar(
+ label='Uplink Type',
+ description="What type of interface should this switch be delivered on",
+ choices=UPLINK_TYPES,
+ default=InterfaceTypeChoices.TYPE_1GE_FIXED
+
)
device_type = ObjectVar(
description="Device model",
model=DeviceType,
+ default=DEFAULT_DEVICE_TYPE.id,
)
- role = ObjectVar(
- description="Device role",
- model=DeviceRole,
- default=ACCESS_SWITCH_DEVICE_ROLE.id,
- )
+
site = ObjectVar(
description = "Site",
model=Site,
- default=DEFAULT_SITE.id,
+ default=DEFAULT_SITE,
)
+
destination_device = ObjectVar(
description = "Destination/uplink",
model=Device,
+ default=DEFAULT_L1_SWITCH.id,
query_params={
+ 'site_id': '$site',
'role': [DISTRIBUTION_SWITCH_DEVICE_ROLE, ROUTER_DEVICE_ROLE, CORE_DEVICE_ROLE],
},
)
destination_interfaces = MultiObjectVar(
- description="Destination interface(s)",
+ description="Destination interface(s). \n\n IF You're looking at d1.ring: ge-{PLACEMENT}/x/x. Placements: 0 = South, 1 = Log, 2 = Swing, 3 = North, 4 = noc, 5 = tele",
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,
+ 'type': '$uplink_type'
}
)
- 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={
- 'group_id': '$vlan_group',
- 'vid': [666, 667],
- }
- )
+ # I don't think we'll actually use this
+ #vlan_id = IntegerVar(
+ # label="VLAN ID",
+ # description="NB: Only applicable for 'Access' deliveries! Auto-assigned if not specified. Make sure it is available if you provide it.",
+ # required=False,
+ # default='',
+ #)
tags = MultiObjectVar(
description="Tags to be sent to Gondul. These are used for templating, so be sure what they do.",
model=Tag,
required=False,
)
+ nat = BooleanVar(
+ label='NAT?',
+ description="Should the network provided by the switch be NATed?"
+ )
+
+
def run(self, data, commit):
- mgmt_vlan = data['mgmt_vlan']
+
+
+ self.log_success(f"{self.request.__dir__()}")
+ self.log_success(f"{self.request.id.__dir__()}")
+ self.log_success(f"{self.request.user}")
+ self.log_success(f"{self.request.META}")
+ # Unfuck shit
+ # Choice var apparently only gives you a string, not an object.
+ # Or i might be stooopid
+ data['leveranse'] = getDeviceRole(data['leveranse'])
+
+ # Let's start with assumptions!
+ # We can generate the name of the vlan. No need to enter manually.
+ # Possbly less confusing so.
+ mgmt_vlan = generateMgmtVlan(self, data)
+ # Make sure that site ang vlan group is the same. Since our vlan boundaries is the same as site
+ vlan_group = VLANGroup.objects.get(slug=data['site'].slug)
# Create the new switch
switch = Device(
name=data['switch_name'],
device_type=data['device_type'],
- device_role=data['role'],
+ device_role=data['leveranse'],
site=data['site'],
)
switch.save()
+ for tag in data['tags']:
+ switch.tags.add(tag)
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()
+
+
+ # Only do this if access switch
+ if data['leveranse'].name == "Access Switch":
+ 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()
+
+
+
+ # Only do this if access switch
+ if data['leveranse'].name == "Access Switch":
+ #
+ # Prefixes Part
+ #
+
+ prefixes = Prefix.objects.filter(
+ site = data['site'],
+ status = PrefixStatusChoices.STATUS_CONTAINER,
+ #family = IPAddressFamilyChoices.FAMILY_4,
+ role = Role.objects.get(slug='crew').id
+ )
+
+ if len(prefixes) > 2 or len(prefixes) == 0:
+ raise AbortScript(f"Got two or none prefixes. Run to Simen and ask for help!")
+
+ # Doesn't support anything else than crew networks
+ for prefix in prefixes:
+
+ if prefix.family == 4:
+ v4_prefix = Prefix.objects.create(
+ prefix = generatePrefix(prefix, 26),
+ status = PrefixStatusChoices.STATUS_ACTIVE,
+ site = data['site'],
+ role = Role.objects.get(slug='crew'),
+ vlan = vlan
+ )
+ self.log_info(f"Created new IPv4 Prefix: {v4_prefix}")
+ if data['nat']:
+ nat = Tag.objects.get(slug='nat')
+ self.log_info(f"VLAN Id: {nat.name} - {nat.id}")
+ v4_prefix.tags.add(nat)
+
+ elif prefix.family == 6:
+ v6_prefix = Prefix.objects.create(
+ prefix = generatePrefix(prefix, 64),
+ status = PrefixStatusChoices.STATUS_ACTIVE,
+ site = data['site'],
+ role = Role.objects.get(slug='crew'),
+ vlan = vlan
+ )
+ self.log_info(f"IPv6 Prefix: {v6_prefix}")
+ if data['nat']:
+ nat = Tag.objects.get(slug='nat')
+ self.log_info(f"VLAN Id: {nat.name} - {nat.id}")
+ v6_prefix.tags.add(nat)
+ else:
+ raise AbortScript(f"Prefix is neither v4 or v6, shouldn't happend!")
+
+
+ #Cheky. But let's resolve the l3 termination hardkoded instead of resolving via netbox.
+ l3Term, l3Intf = getL3(self, data)
+ self.log_success(f"{l3Term} - {l3Intf} - vl{vid}")
+
+ l3Uplink = Interface.objects.create(
+ device=l3Term,
+ description = f'C: {switch.name} - VLAN {vlan.id}',
+ name=f"{l3Intf}.{vid}",
+ type=InterfaceTypeChoices.TYPE_VIRTUAL,
+ parent=l3Intf
+ )
+
+
+ self.log_success(f"Created Interface: {l3Uplink.name} on {l3Term.name}")
+
+ v4_uplink_addr = IPAddress.objects.create(
+ address=v4_prefix.get_first_available_ip(),
+ )
+ v6_uplink_addr = IPAddress.objects.create(
+ address=v6_prefix.get_first_available_ip(),
+ )
+ l3Uplink.ip_addresses.add(v4_uplink_addr)
+ l3Uplink.ip_addresses.add(v6_uplink_addr)
+ l3Uplink.tagged_vlans.add(vlan.id)
+
mgmt_vlan_interface = Interface.objects.create(
device=switch,
name=f"vlan.{mgmt_vlan.vid}",
+ description = f'X: Mgmt',
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,
+ description = f"B: {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,
- )
+# uplink_vlan = Interface.objects.create(
+# device=switch,
+# name="ae0.0",
+# description=data['destination_device'].name,
+# type=InterfaceTypeChoices.TYPE_VIRTUAL,
+# parent=uplink_ae,
+# )
+
+ # Hack to create AE name
+ if data['leveranse'].name == "Access Switch":
+ dest_ae_id = vlan.vid
+ elif data['leveranse'].name == "Distribution Switch":
+ dest_ae_id = str(random.randint(5000,6000))
+ self.log_warning("SCRIPT IS GENERATING AE WITH RANDOM NUMBER. PLS FIX ACCORDING TO TEMPLATE :(")
+
destination_ae = Interface.objects.create(
device=data['destination_device'],
- name=f"ae{vlan.vid}",
- description=switch.name,
+ name=f"ae{dest_ae_id}",
+ description = f'B: {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,
- )
+ if data['leveranse'].name == "Access Switch":
+ destination_ae.tagged_vlans.add(mgmt_vlan.id)
+ destination_ae.tagged_vlans.add(vlan.id)
self.log_success("Created AE and VLAN interfaces for both ends")
mgmt_prefix_v4 = mgmt_vlan.prefixes.get(prefix__family=4)
@@ -160,13 +368,19 @@ class CreateSwitch(Script):
switch.save()
num_uplinks = len(data['destination_interfaces'])
- interfaces = list(Interface.objects.filter(device=switch).exclude(type=InterfaceTypeChoices.TYPE_VIRTUAL).exclude(type=InterfaceTypeChoices.TYPE_LAG))
+ interfaces = list(Interface.objects.filter(device=switch, type=data['uplink_type']).exclude(type=InterfaceTypeChoices.TYPE_VIRTUAL).exclude(type=InterfaceTypeChoices.TYPE_LAG))
+ if len(interfaces) < 1:
+ raise AbortScript(f"You chose a device type without any {data['uplink_type']} interfaces! Pick another model :)")
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]
+ # Fix Descriptions
+ a_interface.description = f'G: {switch.name} (ae0)'
+ b_interface.description = f"G: {data['destination_device'].name} (ae0)"
+
# Configure uplink as AE0
b_interface.lag = uplink_ae
b_interface.save()