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

@ -39,8 +39,28 @@ class InterfaceComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
if config["exclude_virtual_interfaces"]: if config["exclude_virtual_interfaces"]:
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)
# 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]
return post_components(request, device, interfaces, interface_templates, Interface, InterfaceTemplate) 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"""
@ -65,8 +85,29 @@ 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)
# 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) 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"""
@ -91,8 +132,27 @@ 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)
# 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) 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"""
@ -117,8 +177,27 @@ 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)
# 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) 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
# 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")))
)
try: # Add selected interfaces to the device and count them
# If power outlets already exists, update and do not recreate add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device)
po = device.poweroutlets.get(name=i["name"])
except PowerOutlet.DoesNotExist:
po = PowerOutlet()
po.device = device
to_create = True
# Copy all fields from template bulk_create = []
for k in i.keys(): updated = 0
if k not in keys_to_avoid: keys_to_avoid = ["id"]
setattr(po, k, i[k])
po.power_port_id = matching.get(i["id"], None)
if to_create: for i in add_to_device_component.values():
bulk_create.append(po) to_create = False
else:
po.save()
updated += 1
created = len(PowerOutlet.objects.bulk_create(bulk_create)) try:
# If power outlets already exists, update and do not recreate
po = device.poweroutlets.get(name=i["name"])
except PowerOutlet.DoesNotExist:
po = PowerOutlet()
po.device = device
to_create = True
# Getting and validating a list of interfaces to rename # Copy all fields from template
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets) for k in i.keys():
if k not in keys_to_avoid:
setattr(po, k, i[k])
po.power_port_id = matching.get(i["id"], None)
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming if to_create:
unified_component_templates = [ bulk_create.append(po)
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] else:
po.save()
updated += 1
# Rename selected interfaces created = len(PowerOutlet.objects.bulk_create(bulk_create))
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: # Getting and validating a list of interfaces to rename
# Try to extract an interface template with the corresponding name fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets)
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: # Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming
message.append(f"created {created} interfaces") unified_component_templates = [
if updated > 0: 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]
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()) # 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]
return redirect(request.path) 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)
# 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]
return post_components(request, device, devicebays, devicebays_templates, DeviceBay, DeviceBayTemplate) 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)