Created
November 7, 2019 22:27
-
-
Save chonty/b7f333587a9a23c2f7820411affe881d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.utils.text import slugify | |
from dcim.constants import * | |
from dcim.models import Device, DeviceRole, DeviceType, Site, Interface | |
from circuits.models import Circuit | |
from ipam.models import VLAN, VLANGroup, Role, Prefix, IPAddress | |
from extras.models import Tag | |
from extras.scripts import * | |
class NewELineService(Script): | |
class Meta: | |
name = "New AAPT E-Line Service" | |
description = "Provision a new AAPT E-Line service" | |
fields = ['fnn', 'state', 'speed', 'trunk'] | |
field_order = ['fnn', 'state', 'speed', 'trunk'] | |
commit_default = False | |
netbox_url = 'http://netbox.lab' | |
fnn = StringVar( | |
label = 'FNN', | |
min_length = 8, | |
max_length = 8, | |
regex = '\d+$', | |
description="The FNN of the service" | |
) | |
# TODO: Dropdown with custom selection | |
state = StringVar( | |
max_length = 3, | |
regex = '^(?:NSW|VIC|QLD|WA|TAS|ACT)$', | |
description = 'Service state: NSW/VIC/QLD/WA/TAS/ACT' | |
) | |
speed = IntegerVar( | |
min_value = 1, | |
max_value = 100, | |
description = 'The provisioned service speed in Mbps' | |
) | |
trunk = ObjectVar( | |
queryset = Interface.tags.filter(name__contains='aapt') | |
.exclude(name__regex='-[AZ]') | |
) | |
def Diff(self, li1, li2): | |
return (list(set(li1) - set(li2))) | |
def url(self, obj, x=''): | |
return x + self.netbox_url + obj.get_absolute_url() | |
def abort(self): | |
self.log_failure('!!! ABORTING !!!') | |
self.log_info('Nothing written to database.') | |
raise Exception | |
def run(self, data): | |
# vlans = VLAN.objects.filter( | |
# group__name='AAPT-SY1' | |
# ) | |
vlans = VLAN.objects.filter( | |
site__name='Equinix SY1' | |
) | |
vid_list = list(vlans.values_list('vid', flat=True)) | |
next_vid = self.next_available_vid(vid_list) | |
site = Site.objects.get( | |
name='Equinix SY1' | |
) | |
role = Role.objects.get( | |
name='AAPT' | |
) | |
cid = data['trunk'] | |
fnn = data['fnn'] | |
state = data['state'] | |
# TODO: don't allowed duplicate FNNs | |
name = 'aapt-' + fnn | |
self.log_info('===CREATE VLAN===') | |
vlan = self.create_vlan(next_vid, name, site, role) | |
self.log_info('===UPDATE TRUNKS===') | |
trunk_ports = self.get_trunk_ports(vlan, cid) | |
role = Role.objects.get( | |
name = 'AAPT E-Line NSW' | |
) | |
self.log_info('===CREATE PREFIX===') | |
prefix = self.get_next_prefix(state, fnn, role, vlan) | |
vlan.save() | |
for port in trunk_ports: | |
port.tagged_vlans.add(vlan) | |
port.save() | |
prefix.vlan = vlan | |
prefix.save() | |
self.log_info('===CREATE L3 INTERFACE===') | |
interface = self.create_router_interface(vlan, fnn, data['speed'], prefix) | |
# A bit of a dirty workaround for the lack of VLAN group | |
# allowed VLAN ranges | |
def next_available_vid(self, vids): | |
# start_ and end_vid for allowed VLAN range | |
start_vid = 1400 | |
end_vid = 1600 | |
vids.append(start_vid - 1) | |
vids.append(end_vid + 1) | |
vids.sort() | |
original_list = [x for x in range(vids[0], vids[-1] + 1)] | |
vids = set(vids) | |
return (min(list(vids ^ set(original_list)))) | |
def create_vlan(self, vid, name, site, role): | |
vlan = VLAN( | |
name=name, | |
vid=vid, | |
site=site, | |
role=role | |
) | |
# vlan.save() | |
self.log_success(vlan) | |
return vlan | |
def get_trunk_ports(self, vlan, cid): | |
trunk_ports = Interface.objects.filter(tags__name=cid) | |
tagged_ports = trunk_ports.filter(mode=200) # 200 = 'Tagged' | |
diff = self.Diff(trunk_ports, tagged_ports) | |
if len(diff) > 0: | |
self.log_failure('Interfaces not configured as tagged ports:') | |
for d in diff: | |
self.log_failure(d.name + ' on ' + d.device.name + self.url(d, ' ')) | |
self.abort() | |
self.log_info('Interfaces with CID ' + cid.name + ' :') | |
for port in trunk_ports: | |
self.log_info(port.device.name + ' / ' + port.name) | |
return trunk_ports | |
def get_next_prefix(self, state, fnn, role, vlan): | |
prefix = None | |
containers = Prefix.objects.filter( | |
role = role, | |
status=0 # 0 = Container | |
) | |
created = False | |
# Loop through containers in case there are more than 1 | |
# and they are full | |
for container in containers: | |
for cidr in container.get_available_prefixes().iter_cidrs(): | |
try: | |
c = next(cidr.subnet(30, 1)) # Get the next /30 | |
except: | |
continue | |
prefix = Prefix( | |
prefix = c, | |
role = role, | |
vlan = vlan, | |
description = fnn | |
) | |
self.log_success('Created prefix ' + str(prefix)) | |
created = True | |
break | |
if created: break | |
self.log_info('No available /30 in ' + str(container)) | |
if not created: | |
self.log_failure('No available /30.') | |
self.abort() | |
return prefix | |
def create_router_interface(self, vlan, fnn, speed, prefix): | |
z = Interface.objects.get(tags__name='aapt1234-Z') | |
d = z.device | |
i = Interface.objects.create( | |
name = z.name + '.' + str(vlan.vid), | |
description = fnn + ' ' + str(speed) + 'Mbps', | |
type = 1000 # Virtual | |
) | |
a = str(prefix.prefix[1]) + '/30' | |
IPAddress.objects.create(address=a, status=1, family=4, interface=i) | |
self.log_success('Created interface ' + i.name + ' on ' + d.name + ' with IP ' + a) | |
d.interfaces.add(i) | |
i.save() | |
d.save() | |
return i |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment