Added front ports and rear ports sync

This commit is contained in:
rizlas 2022-01-11 13:47:43 +01:00
parent d1b2b82d13
commit 836d850401
2 changed files with 196 additions and 20 deletions

View File

@ -69,18 +69,49 @@ class FrontPortComparison(ParentTypedComparison):
"""A unified way to represent the front port and front port template""" """A unified way to represent the front port and front port template"""
color: str color: str
rearports: str # rear_port_id: int
rear_port_position: int
is_template: bool = False 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.label == other.label)
and (self.description == other.description)
and (self.type == other.type)
and (self.color == other.color)
and (self.rear_port_position == other.rear_port_position)
)
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 RearPortComparison(ParentTypedComparison): class RearPortComparison(ParentTypedComparison):
"""A unified way to represent the rear port and rear port template""" """A unified way to represent the rear port and rear port template"""
color: str color: str
positions: str positions: int
is_template: bool = False 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.label == other.label)
and (self.description == other.description)
and (self.type == other.type)
and (self.color == other.color)
and (self.positions == other.positions)
)
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, eq=False) @dataclass(frozen=True, eq=False)
class ConsolePortComparison(ParentTypedComparison): class ConsolePortComparison(ParentTypedComparison):

View File

@ -8,7 +8,7 @@ from django.conf import settings
from django.contrib import messages from django.contrib import messages
from .utils import natural_keys, get_components, post_components from .utils import natural_keys, get_components, post_components
from .comparison import PowerPortComparison, PowerOutletComparison, InterfaceComparison, ConsolePortComparison, ConsoleServerPortComparison, DeviceBayComparison from .comparison import FrontPortComparison, PowerPortComparison, PowerOutletComparison, InterfaceComparison, ConsolePortComparison, ConsoleServerPortComparison, DeviceBayComparison, RearPortComparison
from .forms import InterfaceComparisonForm from .forms import InterfaceComparisonForm
config = settings.PLUGINS_CONFIG['netbox_interface_sync'] config = settings.PLUGINS_CONFIG['netbox_interface_sync']
@ -252,20 +252,19 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
matching[i.id] = pp.id matching[i.id] = pp.id
found = True found = True
# If at least on power port is found there is a dependency # If at least one power port is not found in device there is a dependency
# Better not to sync at all # Better not to sync at all
if not found: if not found:
mismatch = True mismatch = True
break break
if not mismatch: if not mismatch:
# Manually validating interfaces and interface templates lists
add_to_device = filter( add_to_device = filter(
lambda i: i in poweroutlets_templates.values_list("id", flat=True), lambda i: i in poweroutlets_templates.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device"))) map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
) )
# Add selected interfaces to the device and count them # Add selected component to the device and count them
add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device) add_to_device_component = PowerOutletTemplate.objects.filter(id__in=add_to_device)
bulk_create = [] bulk_create = []
@ -297,21 +296,21 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
created = len(PowerOutlet.objects.bulk_create(bulk_create)) created = len(PowerOutlet.objects.bulk_create(bulk_create))
# Getting and validating a list of interfaces to rename # Getting and validating a list of components to rename
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets) fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), poweroutlets)
# Casting interface templates into UnifiedInterface objects for proper comparison with interfaces for renaming # Casting component templates into UnifiedInterface objects for proper comparison with component for renaming
unified_component_templates = [ 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] 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 # Rename selected interfaces
fixed = 0 fixed = 0
for component in fix_name_components: 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] unified_poweroutlet = PowerOutletComparison(component.id, component.name, component.label, component.description, component.type, component.get_type_display(), power_port_name=PowerPort.objects.get(id=component.power_port_id).name if component.power_port_id is not None else "", feed_leg=component.feed_leg)
try: try:
# Try to extract an interface template with the corresponding name # Try to extract a component template with the corresponding name
corresponding_template = unified_component_templates[unified_component_templates.index(unified_component)] corresponding_template = unified_component_templates[unified_component_templates.index(unified_poweroutlet)]
component.name = corresponding_template.name component.name = corresponding_template.name
component.save() component.save()
fixed += 1 fixed += 1
@ -321,13 +320,13 @@ class PowerOutletComparisonView(LoginRequiredMixin, PermissionRequiredMixin, Vie
message.append("Dependecy detected, sync power ports first!") message.append("Dependecy detected, sync power ports first!")
if created > 0: if created > 0:
message.append(f"created {created} interfaces") message.append(f"created {created} power outlet")
if updated > 0: if updated > 0:
message.append(f"updated {updated} interfaces") message.append(f"updated {updated} power outlet")
if deleted > 0: if deleted > 0:
message.append(f"deleted {deleted} interfaces") message.append(f"deleted {deleted} power outlet")
if fixed > 0: if fixed > 0:
message.append(f"fixed {fixed} interfaces") message.append(f"fixed {fixed} power outlet")
messages.info(request, "; ".join(message).capitalize()) messages.info(request, "; ".join(message).capitalize())
@ -338,12 +337,17 @@ class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
permission_required = ("dcim.view_interface", "dcim.add_interface", "dcim.change_interface", "dcim.delete_interface") permission_required = ("dcim.view_interface", "dcim.add_interface", "dcim.change_interface", "dcim.delete_interface")
def get(self, request, device_id): def get(self, request, device_id):
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 get_components(request, device, frontports, frontports_templates) unified_frontports = [FrontPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position) for i in frontports]
unified_frontports_templates = [
FrontPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position, is_template=True) for i in frontports_templates]
return get_components(request, device, frontports, unified_frontports, unified_frontports_templates)
def post(self, request, device_id): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST) form = InterfaceComparisonForm(request.POST)
@ -352,8 +356,114 @@ class FrontPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View)
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) # Generating result message
message = []
created = 0
updated = 0
fixed = 0
remove_from_device = filter(
lambda i: i in frontports.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("remove_from_device")))
)
# Remove selected interfaces from the device and count them
deleted = FrontPort.objects.filter(id__in=remove_from_device).delete()[0]
# Get device power ports to check dependency between power outlets
device_rp = RearPort.objects.filter(device_id=device.id)
matching = {}
mismatch = False
for i in frontports_templates:
found = False
if i.rear_port_id is not None:
rpt = RearPortTemplate.objects.get(id=i.rear_port_id)
for rp in device_rp:
if rp.name == rpt.name:
# Save matching to add the correct power port later
matching[i.id] = rp.id
found = True
# If at least one power port is not found in device there is a dependency
# Better not to sync at all
if not found:
mismatch = True
break
if not mismatch:
add_to_device = filter(
lambda i: i in frontports_templates.values_list("id", flat=True),
map(int, filter(lambda x: x.isdigit(), request.POST.getlist("add_to_device")))
)
# Add selected component to the device and count them
add_to_device_component = FrontPortTemplate.objects.filter(id__in=add_to_device)
bulk_create = []
updated = 0
keys_to_avoid = ["id"]
for i in add_to_device_component.values():
to_create = False
try:
# If power outlets already exists, update and do not recreate
fp = device.frontports.get(name=i["name"])
except FrontPort.DoesNotExist:
fp = FrontPort()
fp.device = device
to_create = True
# Copy all fields from template
for k in i.keys():
if k not in keys_to_avoid:
setattr(fp, k, i[k])
fp.rear_port_id = matching.get(i["id"], None)
if to_create:
bulk_create.append(fp)
else:
fp.save()
updated += 1
created = len(FrontPort.objects.bulk_create(bulk_create))
# Getting and validating a list of components to rename
fix_name_components = filter(lambda i: str(i.id) in request.POST.getlist("fix_name"), frontports)
# Casting component templates into UnifiedInterface objects for proper comparison with component for renaming
unified_frontports_templates = [
FrontPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.rear_port_position, is_template=True) for i in frontports_templates]
# Rename selected interfaces
fixed = 0
for component in fix_name_components:
unified_frontport = FrontPortComparison(component.id, component.name, component.label, component.description, component.type, component.get_type_display(), component.color, component.rear_port_position)
try:
# Try to extract a component template with the corresponding name
corresponding_template = unified_frontports_templates[unified_frontports_templates.index(unified_frontport)]
component.name = corresponding_template.name
component.save()
fixed += 1
except ValueError:
pass
else:
message.append("Dependecy detected, sync rear ports first!")
if created > 0:
message.append(f"created {created} front ports")
if updated > 0:
message.append(f"updated {updated} front ports")
if deleted > 0:
message.append(f"deleted {deleted} front ports")
if fixed > 0:
message.append(f"fixed {fixed} front ports")
messages.info(request, "; ".join(message).capitalize())
return redirect(request.path)
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"""
@ -365,9 +475,44 @@ class RearPortComparisonView(LoginRequiredMixin, PermissionRequiredMixin, View):
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 get_components(request, device, rearports, rearports_templates) unified_rearports = [RearPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.positions) for i in rearports]
unified_rearports_templates = [
RearPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.positions, is_template=True) for i in rearports_templates]
return get_components(request, device, rearports, unified_rearports, unified_rearports_templates)
def post(self, request, device_id): def post(self, request, device_id):
form = InterfaceComparisonForm(request.POST)
if form.is_valid():
device = get_object_or_404(Device.objects.filter(id=device_id))
rearports = device.rearports.all()
rearports_templates = RearPortTemplate.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"), rearports
)
unified_rearports_templates = [
RearPortComparison(i.id, i.name, i.label, i.description, i.type, i.get_type_display(), i.color, i.positions, is_template=True) for i in rearports_templates]
unified_rearports = []
for component in fix_name_components:
unified_rearports.append((component, RearPortComparison(
component.id,
component.name,
component.label,
component.description,
component.type,
component.get_type_display(),
component.color,
component.positions)))
return post_components(request, device, rearports, rearports_templates, RearPort, RearPortTemplate, unified_rearports, unified_rearports_templates)
form = InterfaceComparisonForm(request.POST) form = InterfaceComparisonForm(request.POST)
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))