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
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 ?
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, ROUTER_DEVICE_ROLE, CORE_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={
'group_id': '$vlan_group',
'vid': [666, 667],
}
)
tags = MultiObjectVar(
description="Tags to be sent to Gondul. These are used for templating, so be sure what they do.",
model=Tag,
required=False,
)
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: {switch}")
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")
mgmt_prefix_v4 = mgmt_vlan.prefixes.get(prefix__family=4)
mgmt_prefix_v6 = mgmt_vlan.prefixes.get(prefix__family=6)
v4_mgmt_addr = IPAddress.objects.create(
address=mgmt_prefix_v4.get_first_available_ip(),
)
v6_mgmt_addr = IPAddress.objects.create(
address=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)
switch.primary_ip4 = v4_mgmt_addr
switch.primary_ip6 = v6_mgmt_addr
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))
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.lag = uplink_ae
b_interface.save()
# Configure downlink on destination
a_interface.lag = 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)
# https://github.com/netbox-community/netbox/discussions/10199
cable._terminations_modified = True
cable.save()
self.log_success(f"Cabled {data['destination_device']} {a_interface} to {switch} {b_interface}")
self.log_success(f"To create this switch in Gondul you can trigger an update immediately or view the device first and trigger an update from there.")