Fix interface comparison. Modified global post

This commit is contained in:
rizlas 2021-12-28 20:29:37 +01:00
parent 80112869e0
commit d1b2b82d13
3 changed files with 234 additions and 176 deletions

View File

@ -59,6 +59,10 @@ class InterfaceComparison(ParentTypedComparison):
and (self.mgmt_only == other.mgmt_only) and (self.mgmt_only == other.mgmt_only)
) )
def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))
@dataclass(frozen=True) @dataclass(frozen=True)
class FrontPortComparison(ParentTypedComparison): class FrontPortComparison(ParentTypedComparison):
@ -143,24 +147,3 @@ class DeviceBayComparison(ParentComparison):
"""A unified way to represent the interface and interface template""" """A unified way to represent the interface and interface template"""
is_template: bool = False is_template: bool = False
@dataclass(frozen=True)
class UnifiedInterface:
"""A unified way to represent the interface and interface template"""
id: int
name: str
type: str = ""
type_display: str = ""
is_template: bool = False
def __eq__(self, other):
# Ignore some fields when comparing; ignore interface name case and whitespaces
return (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
) and (self.type == other.type)
def __hash__(self):
# Ignore some fields when hashing; ignore interface name case and whitespaces
return hash((self.name.lower().replace(" ", ""), self.type))

View File

@ -2,7 +2,7 @@ import re
from typing import Iterable from typing import Iterable
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from .comparison import UnifiedInterface from django.core.exceptions import ObjectDoesNotExist
def split(s): def split(s):
@ -20,18 +20,6 @@ def human_sorted(iterable: Iterable):
def get_components(request, device, components, unified_components, unified_component_templates): def get_components(request, device, components, unified_components, unified_component_templates):
# try:
# unified_components = [UnifiedInterface(i.id, i.name, i.type, i.get_type_display()) for i in components]
# except AttributeError:
# unified_components = [UnifiedInterface(i.id, i.name) for i in components]
# try:
# unified_component_templates = [
# UnifiedInterface(i.id, i.name, i.type, i.get_type_display(), is_template=True) for i in component_templates]
# except AttributeError:
# unified_component_templates = [
# UnifiedInterface(i.id, i.name, is_template=True) for i in component_templates]
# List of interfaces and interface templates presented in the unified format # List of interfaces and interface templates presented in the unified format
overall_powers = list(set(unified_component_templates + unified_components)) overall_powers = list(set(unified_component_templates + unified_components))
overall_powers.sort(key=lambda o: natural_keys(o.name)) overall_powers.sort(key=lambda o: natural_keys(o.name))
@ -67,7 +55,7 @@ def get_components(request, device, components, unified_components, unified_comp
def post_components( def post_components(
request, device, components, component_templates, ObjectType, ObjectTemplateType request, device, components, component_templates, ObjectType, ObjectTemplateType, unified_component, unified_component_templates
): ):
# Manually validating interfaces and interface templates lists # Manually validating interfaces and interface templates lists
add_to_device = filter( add_to_device = filter(
@ -89,50 +77,38 @@ def post_components(
add_to_device_component = ObjectTemplateType.objects.filter(id__in=add_to_device) add_to_device_component = ObjectTemplateType.objects.filter(id__in=add_to_device)
bulk_create = [] bulk_create = []
updated = 0
keys_to_avoid = ["id"] keys_to_avoid = ["id"]
for i in add_to_device_component.values(): for i in add_to_device_component.values():
tmp = ObjectType() to_create = False
tmp.device = device
try:
tmp = components.get(name=i["name"])
except ObjectDoesNotExist:
tmp = ObjectType()
tmp.device = device
to_create = True
for k in i.keys(): for k in i.keys():
if k not in keys_to_avoid: if k not in keys_to_avoid:
setattr(tmp, k, i[k]) setattr(tmp, k, i[k])
bulk_create.append(tmp)
if to_create:
bulk_create.append(tmp)
else:
tmp.save()
updated += 1
created = len(ObjectType.objects.bulk_create(bulk_create)) created = len(ObjectType.objects.bulk_create(bulk_create))
# Getting and validating a list of interfaces to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), components
)
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
try:
unified_component_templates = [
UnifiedInterface(i.id, i.name, i.type, i.get_type_display())
for i in component_templates
]
except AttributeError:
unified_component_templates = [
UnifiedInterface(i.id, i.name) for i in component_templates
]
# Rename selected interfaces # Rename selected interfaces
fixed = 0 fixed = 0
for component in fix_name_components: for component, component_comparison in unified_component:
try:
unified_component = UnifiedInterface(
component.id,
component.name,
component.type,
component.get_type_display(),
)
except AttributeError:
unified_component = UnifiedInterface(component.id, component.name)
try: try:
# Try to extract an interface template with the corresponding name # Try to extract an interface template with the corresponding name
corresponding_template = unified_component_templates[ corresponding_template = unified_component_templates[
unified_component_templates.index(unified_component) unified_component_templates.index(component_comparison)
] ]
component.name = corresponding_template.name component.name = corresponding_template.name
component.save() component.save()
@ -144,6 +120,8 @@ def post_components(
message = [] message = []
if created > 0: if created > 0:
message.append(f"created {created} interfaces") message.append(f"created {created} interfaces")
if updated > 0:
message.append(f"updated {updated} interfaces")
if deleted > 0: if deleted > 0:
message.append(f"deleted {deleted} interfaces") message.append(f"deleted {deleted} interfaces")
if fixed > 0: if fixed > 0:

View File

@ -40,7 +40,27 @@ class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
interfaces = interfaces.exclude(type__in=["virtual", "lag"]) interfaces = interfaces.exclude(type__in=["virtual", "lag"])
interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type) interface_templates = InterfaceTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, interfaces, interface_templates, Interface, InterfaceTemplate) # Getting and validating a list of interfaces to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), interfaces
)
unified_interface_templates = [
InterfaceComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.mgmt_only, is_template=True) for i in interface_templates]
unified_interfaces = []
for component in fix_name_components:
unified_interfaces.append((component, InterfaceComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display(),
component.mgmt_only)))
return post_components(request, device, interfaces, interface_templates, Interface, InterfaceTemplate, unified_interfaces, unified_interface_templates)
class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View): class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of interfaces between a device and a device type and beautiful visualization""" """Comparison of interfaces between a device and a device type and beautiful visualization"""
@ -66,7 +86,28 @@ class PowerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
powerports = device.powerports.all() powerports = device.powerports.all()
powerports_templates = PowerPortTemplate.objects.filter(device_type=device.device_type) powerports_templates = PowerPortTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, powerports, powerports_templates, PowerPort, PowerPortTemplate) # Getting and validating a list of interfaces to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), powerports
)
unified_powerport_templates = [
PowerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.maximum_draw, i.allocated_draw, is_template=True) for i in powerports_templates]
unified_powerports = []
for component in fix_name_components:
unified_powerports.append((component, PowerPortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display(),
component.maximum_draw,
component.allocated_draw)))
return post_components(request, device, powerports, powerports_templates, PowerPort, PowerPortTemplate, unified_powerports, unified_powerport_templates)
class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View): class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of interfaces between a device and a device type and beautiful visualization""" """Comparison of interfaces between a device and a device type and beautiful visualization"""
@ -92,7 +133,26 @@ class ConsolePortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
consoleports = device.consoleports.all() consoleports = device.consoleports.all()
consoleports_templates = ConsolePortTemplate.objects.filter(device_type=device.device_type) consoleports_templates = ConsolePortTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, consoleports, consoleports_templates, ConsolePort, ConsolePortTemplate) # Getting and validating a list of interfaces to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), consoleports
)
unified_consoleport_templates = [
ConsolePortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleports_templates]
unified_consoleports = []
for component in fix_name_components:
unified_consoleports.append((component, ConsolePortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display())))
return post_components(request, device, consoleports, consoleports_templates, ConsolePort, ConsolePortTemplate, unified_consoleports, unified_consoleport_templates)
class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View): class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of interfaces between a device and a device type and beautiful visualization""" """Comparison of interfaces between a device and a device type and beautiful visualization"""
@ -118,7 +178,26 @@ class ConsoleServerPortComparisonView(LoginRequiredMixin, PermissionRequiredMixi
consoleserverports = device.consoleserverports.all() consoleserverports = device.consoleserverports.all()
consoleserverports_templates = ConsoleServerPortTemplate.objects.filter(device_type=device.device_type) consoleserverports_templates = ConsoleServerPortTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, consoleserverports, consoleserverports_templates, ConsoleServerPort, ConsoleServerPortTemplate) # Getting and validating a list of interfaces to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), consoleserverports
)
unified_consoleserverport_templates = [
ConsoleServerPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), is_template=True) for i in consoleserverports_templates]
unified_consoleserverports = []
for component in fix_name_components:
unified_consoleserverports.append((component, ConsoleServerPortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display())))
return post_components(request, device, consoleserverports, consoleserverports_templates, ConsoleServerPort, ConsoleServerPortTemplate, unified_consoleserverports, unified_consoleserverport_templates)
class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View): class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of interfaces between a device and a device type and beautiful visualization""" """Comparison of interfaces between a device and a device type and beautiful visualization"""
@ -141,118 +220,118 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
if form.is_valid(): if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id)) device = get_object_or_404(Device.objects.filter(id=device_id))
poweroutlets = device.poweroutlets.all() poweroutlets = device.poweroutlets.all()
poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type) poweroutlets_templates = PowerOutletTemplate.objects.filter(device_type=device.device_type)
# Generating result message # Generating result message
message = [] message = []
created = 0 created = 0
updated = 0 updated = 0
fixed = 0 fixed = 0
remove_from_device = filter( remove_from_device = filter(
lambda i: i in poweroutlets.values_list("id", flat=True), lambda i: i in poweroutlets.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device"))) map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
)
# Remove selected interfaces from the device and count them
deleted = PowerOutlet.objects.filter(id__in=remove_from_device).delete()[0]
# Get device power ports to check dependency between power outlets
device_pp = PowerPort.objects.filter(device_id=device.id)
matching = {}
mismatch = False
for i in poweroutlets_templates:
found = False
if i.power_port_id is not None:
ppt = PowerPortTemplate.objects.get(id=i.power_port_id)
for pp in device_pp:
if pp.name == ppt.name:
# Save matching to add the correct power port later
matching[i.id] = pp.id
found = True
# If at least on power port is found there is a dependency
# Better not to sync at all
if not found:
mismatch = True
break
if not mismatch:
# Manually validating interfaces and interface templates lists
add_to_device = filter(
lambda i: i in poweroutlets_templates.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
) )
# Add selected interfaces to the device and count them # Remove selected interfaces from the device and count them
add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device) deleted = PowerOutlet.objects.filter(id__in=remove_from_device).delete()[0]
bulk_create = [] # Get device power ports to check dependency between power outlets
updated = 0 device_pp = PowerPort.objects.filter(device_id=device.id)
keys_to_avoid = ["id"]
for i in add_to_device_component.values(): matching = {}
to_create = False mismatch = False
for i in poweroutlets_templates:
found = False
if i.power_port_id is not None:
ppt = PowerPortTemplate.objects.get(id=i.power_port_id)
for pp in device_pp:
if pp.name == ppt.name:
# Save matching to add the correct power port later
matching[i.id] = pp.id
found = True
try: # If at least on power port is found there is a dependency
# If power outlets already exists, update and do not recreate # Better not to sync at all
po = device.poweroutlets.get(name=i["name"]) if not found:
except PowerOutlet.DoesNotExist: mismatch = True
po = PowerOutlet() break
po.device = device
to_create = True
# Copy all fields from template if not mismatch:
for k in i.keys(): # Manually validating interfaces and interface templates lists
if k not in keys_to_avoid: add_to_device = filter(
setattr(po, k, i[k]) lambda i: i in poweroutlets_templates.values_list("id", flat=True),
po.power_port_id = matching.get(i["id"], None) map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
)
if to_create: # Add selected interfaces to the device and count them
bulk_create.append(po) add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device)
else:
po.save()
updated += 1
created = len(PowerOutlet.objects.bulk_create(bulk_create)) bulk_create = []
updated = 0
keys_to_avoid = ["id"]
# Getting and validating a list of interfaces to rename for i in add_to_device_component.values():
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets) to_create = False
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming try:
unified_component_templates = [ # If power outlets already exists, update and do not recreate
PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), power_port_name=PowerPortTemplate.objects.get(id=i.power_port_id).name if i.power_port_id is not None else "", feed_leg=i.feed_leg, is_template=True) for i in poweroutlets_templates] po = device.poweroutlets.get(name=i["name"])
except PowerOutlet.DoesNotExist:
po = PowerOutlet()
po.device = device
to_create = True
# Rename selected interfaces # Copy all fields from template
fixed = 0 for k in i.keys():
for component in fix_name_components: if k not in keys_to_avoid:
unified_component = [PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), power_port_name=PowerPort.objects.get(id=i.power_port_id).name if i.power_port_id is not None else "", feed_leg=i.feed_leg) for i in poweroutlets] setattr(po, k, i[k])
po.power_port_id = matching.get(i["id"], None)
try: if to_create:
# Try to extract an interface template with the corresponding name bulk_create.append(po)
corresponding_template = unified_component_templates[unified_component_templates.index(unified_component)] else:
component.name = corresponding_template.name po.save()
component.save() updated += 1
fixed += 1
except ValueError:
pass
else:
message.append("Dependecy detected, sync power ports first!")
if created > 0: created = len(PowerOutlet.objects.bulk_create(bulk_create))
message.append(f"created {created} interfaces")
if updated > 0:
message.append(f"updated {updated} interfaces")
if deleted > 0:
message.append(f"deleted {deleted} interfaces")
if fixed > 0:
message.append(f"fixed {fixed} interfaces")
messages.info(request, "; ".join(message).capitalize()) # Getting and validating a list of interfaces to rename
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets)
return redirect(request.path) # Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
unified_component_templates = [
PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), power_port_name=PowerPortTemplate.objects.get(id=i.power_port_id).name if i.power_port_id is not None else "", feed_leg=i.feed_leg, is_template=True) for i in poweroutlets_templates]
# Rename selected interfaces
fixed = 0
for component in fix_name_components:
unified_component = [PowerOutletComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), power_port_name=PowerPort.objects.get(id=i.power_port_id).name if i.power_port_id is not None else "", feed_leg=i.feed_leg) for i in poweroutlets]
try:
# Try to extract an interface template with the corresponding name
corresponding_template = unified_component_templates[unified_component_templates.index(unified_component)]
component.name = corresponding_template.name
component.save()
fixed += 1
except ValueError:
pass
else:
message.append("Dependecy detected, sync power ports first!")
if created > 0:
message.append(f"created {created} interfaces")
if updated > 0:
message.append(f"updated {updated} interfaces")
if deleted > 0:
message.append(f"deleted {deleted} interfaces")
if fixed > 0:
message.append(f"fixed {fixed} interfaces")
messages.info(request, "; ".join(message).capitalize())
return redirect(request.path)
class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View): class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of interfaces between a device and a device type and beautiful visualization""" """Comparison of interfaces between a device and a device type and beautiful visualization"""
@ -271,10 +350,10 @@ class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
if form.is_valid(): if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id)) device = get_object_or_404(Device.objects.filter(id=device_id))
frontports = device.frontports.all() frontports = device.frontports.all()
frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type) frontports_templates = FrontPortTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, frontports, frontports_templates, FrontPort, FrontPortTemplate) return post_components(request, device, frontports, frontports_templates, FrontPort, FrontPortTemplate)
class RearPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View): class RearPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of interfaces between a device and a device type and beautiful visualization""" """Comparison of interfaces between a device and a device type and beautiful visualization"""
@ -293,10 +372,10 @@ class RearPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
if form.is_valid(): if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id)) device = get_object_or_404(Device.objects.filter(id=device_id))
rearports = device.rearports.all() rearports = device.rearports.all()
rearports_templates = RearPortTemplate.objects.filter(device_type=device.device_type) rearports_templates = RearPortTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, rearports, rearports_templates, RearPort, RearPortTemplate) return post_components(request, device, rearports, rearports_templates, RearPort, RearPortTemplate)
class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View): class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
"""Comparison of interfaces between a device and a device type and beautiful visualization""" """Comparison of interfaces between a device and a device type and beautiful visualization"""
@ -319,7 +398,25 @@ class DeviceBayComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
if form.is_valid(): if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id)) device = get_object_or_404(Device.objects.filter(id=device_id))
devicebays = device.devicebays.all() devicebays = device.devicebays.all()
devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type) devicebays_templates = DeviceBayTemplate.objects.filter(device_type=device.device_type)
return post_components(request, device, devicebays, devicebays_templates, DeviceBay, DeviceBayTemplate) # Getting and validating a list of devicebays to rename
fix_name_components = filter(
lambda i: str(i.id) in request.POST.getlist("fix_name"), devicebays
)
unified_devicebay_templates = [
DeviceBayComparison(i.id, i.name, i.label, i.description, is_template=True) for i in devicebays_templates]
unified_devicebays = []
for component in fix_name_components:
unified_devicebays.append((component, DeviceBayComparison(
component.id,
component.name,
component.label,
component.description
)))
return post_components(request, device, devicebays, devicebays_templates, DeviceBay, DeviceBayTemplate, unified_devicebays, unified_devicebay_templates)