refactor oob_ip

This commit is contained in:
Jamie Murphy 2023-07-24 21:51:29 +01:00
parent b92360faee
commit d267e42674
No known key found for this signature in database
GPG Key ID: 10EDAE1E6D858F5F
11 changed files with 57 additions and 128 deletions

View File

@ -663,9 +663,7 @@ class DeviceSerializer(NetBoxModelSerializer):
primary_ip = NestedIPAddressSerializer(read_only=True) primary_ip = NestedIPAddressSerializer(read_only=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True) primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
oob_ip = NestedIPAddressSerializer(read_only=True) oob_ip = NestedIPAddressSerializer(required=False, allow_null=True)
oob_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
oob_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)
parent_device = serializers.SerializerMethodField() parent_device = serializers.SerializerMethodField()
cluster = NestedClusterSerializer(required=False, allow_null=True) cluster = NestedClusterSerializer(required=False, allow_null=True)
virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None) virtual_chassis = NestedVirtualChassisSerializer(required=False, allow_null=True, default=None)

View File

@ -1000,15 +1000,10 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
queryset=IPAddress.objects.all(), queryset=IPAddress.objects.all(),
label=_('Primary IPv6 (ID)'), label=_('Primary IPv6 (ID)'),
) )
oob_ip4_id = django_filters.ModelMultipleChoiceFilter( oob_ip_id = django_filters.ModelMultipleChoiceFilter(
field_name='oob_ip4', field_name='oob_ip',
queryset=IPAddress.objects.all(), queryset=IPAddress.objects.all(),
label=_('OOB IPv4 (ID)'), label=_('OOB IP (ID)'),
)
oob_ip6_id = django_filters.ModelMultipleChoiceFilter(
field_name='oob_ip6',
queryset=IPAddress.objects.all(),
label=_('OOB IPv6 (ID)'),
) )
class Meta: class Meta:
@ -1035,7 +1030,7 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
return queryset.exclude(params) return queryset.exclude(params)
def _has_oob_ip(self, queryset, name, value): def _has_oob_ip(self, queryset, name, value):
params = Q(oob_ip4__isnull=False) | Q(oob_ip6__isnull=False) params = Q(oob_ip__isnull=False)
if value: if value:
return queryset.filter(params) return queryset.filter(params)
return queryset.exclude(params) return queryset.exclude(params)

View File

@ -451,7 +451,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face', 'name', 'device_role', 'device_type', 'serial', 'asset_tag', 'site', 'rack', 'location', 'position', 'face',
'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster', 'latitude', 'longitude', 'status', 'airflow', 'platform', 'primary_ip4', 'primary_ip6', 'cluster',
'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'tenant_group', 'tenant', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template',
'comments', 'tags', 'local_context_data' 'oob_ip', 'comments', 'tags', 'local_context_data', 'oob_ip',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -460,6 +460,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
if self.instance.pk: if self.instance.pk:
# Compile list of choices for primary IPv4 and IPv6 addresses # Compile list of choices for primary IPv4 and IPv6 addresses
oob_ip_choices = [(None, '---------')]
for family in [4, 6]: for family in [4, 6]:
ip_choices = [(None, '---------')] ip_choices = [(None, '---------')]
@ -475,6 +476,7 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
if interface_ips: if interface_ips:
ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips] ip_list = [(ip.id, f'{ip.address} ({ip.assigned_object})') for ip in interface_ips]
ip_choices.append(('Interface IPs', ip_list)) ip_choices.append(('Interface IPs', ip_list))
oob_ip_choices.append(('Interface IPv{}s'.format(family), ip_list))
# Collect NAT IPs # Collect NAT IPs
nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter( nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter(
address__family=family, address__family=family,
@ -484,8 +486,9 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
if nat_ips: if nat_ips:
ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips] ip_list = [(ip.id, f'{ip.address} (NAT)') for ip in nat_ips]
ip_choices.append(('NAT IPs', ip_list)) ip_choices.append(('NAT IPs', ip_list))
oob_ip_choices.append(('Nat IPv{}s'.format(family), ip_list))
self.fields['primary_ip{}'.format(family)].choices = ip_choices self.fields['primary_ip{}'.format(family)].choices = ip_choices
self.fields['oob_ip{}'.format(family)].choices = ip_choices self.fields['oob_ip'].choices = oob_ip_choices
# If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device # If editing an existing device, exclude it from the list of occupied rack units. This ensures that a device
# can be flipped from one face to another. # can be flipped from one face to another.
@ -505,10 +508,8 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
self.fields['primary_ip4'].widget.attrs['readonly'] = True self.fields['primary_ip4'].widget.attrs['readonly'] = True
self.fields['primary_ip6'].choices = [] self.fields['primary_ip6'].choices = []
self.fields['primary_ip6'].widget.attrs['readonly'] = True self.fields['primary_ip6'].widget.attrs['readonly'] = True
self.fields['oob_ip4'].choices = [] self.fields['oob_ip'].choices = []
self.fields['oob_ip4'].widget.attrs['readonly'] = True self.fields['oob_ip'].widget.attrs['readonly'] = True
self.fields['oob_ip6'].choices = []
self.fields['oob_ip6'].widget.attrs['readonly'] = True
# Rack position # Rack position
position = self.data.get('position') or self.initial.get('position') position = self.data.get('position') or self.initial.get('position')

View File

@ -1,25 +0,0 @@
# Generated by Django 4.1.9 on 2023-06-26 21:06
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ipam', '0066_iprange_mark_utilized'),
('dcim', '0172_larger_power_draw_values'),
]
operations = [
migrations.AddField(
model_name='device',
name='oob_ip4',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress'),
),
migrations.AddField(
model_name='device',
name='oob_ip6',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress'),
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 4.1.9 on 2023-07-24 20:29
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('ipam', '0066_iprange_mark_utilized'),
('dcim', '0174_rack_starting_unit'),
]
operations = [
migrations.AddField(
model_name='device',
name='oob_ip',
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='ipam.ipaddress',
),
),
]

View File

@ -591,21 +591,13 @@ class Device(PrimaryModel, ConfigContextModel):
null=True, null=True,
verbose_name='Primary IPv6' verbose_name='Primary IPv6'
) )
oob_ip4 = models.OneToOneField( oob_ip = models.OneToOneField(
to='ipam.IPAddress', to='ipam.IPAddress',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name='+', related_name='+',
blank=True, blank=True,
null=True, null=True,
verbose_name='OOB IPv4' verbose_name='OOB IP'
)
oob_ip6 = models.OneToOneField(
to='ipam.IPAddress',
on_delete=models.SET_NULL,
related_name='+',
blank=True,
null=True,
verbose_name='OOB IPv6'
) )
cluster = models.ForeignKey( cluster = models.ForeignKey(
to='virtualization.Cluster', to='virtualization.Cluster',
@ -820,31 +812,14 @@ class Device(PrimaryModel, ConfigContextModel):
}) })
# OOB ip validation # OOB ip validation
if self.oob_ip4: if self.oob_ip:
if self.oob_ip4.family != 4: if self.oob_ip.assigned_object in vc_interfaces:
raise ValidationError({
'oob_ip4': f"{self.oob_ip4} is not an IPv4 address."
})
if self.oob_ip4.assigned_object in vc_interfaces:
pass pass
elif self.oob_ip4.nat_inside is not None and self.oob_ip4.nat_inside.assigned_object in vc_interfaces: elif self.oob_ip.nat_inside is not None and self.oob_ip.nat_inside.assigned_object in vc_interfaces:
pass pass
else: else:
raise ValidationError({ raise ValidationError({
'oob_ip4': f"The specified IP address ({self.oob_ip4}) is not assigned to this device." 'oob_ip': f"The specified IP address ({self.oob_ip}) is not assigned to this device."
})
if self.oob_ip6:
if self.oob_ip6.family != 6:
raise ValidationError({
'oob_ip6': f"{self.oob_ip6} is not an IPv6 address."
})
if self.oob_ip6.assigned_object in vc_interfaces:
pass
elif self.oob_ip6.nat_inside is not None and self.oob_ip6.nat_inside.assigned_object in vc_interfaces:
pass
else:
raise ValidationError({
'oob_ip6': f"The specified IP address ({self.oob_ip6}) is not assigned to this device."
}) })
# Validate manufacturer/platform # Validate manufacturer/platform
if hasattr(self, 'device_type') and self.platform: if hasattr(self, 'device_type') and self.platform:
@ -956,17 +931,6 @@ class Device(PrimaryModel, ConfigContextModel):
else: else:
return None return None
@property
def oob_ip(self):
if ConfigItem('PREFER_IPV4')() and self.oob_ip4:
return self.oob_ip4
elif self.oob_ip6:
return self.oob_ip6
elif self.oob_ip4:
return self.oob_ip4
else:
return None
@property @property
def interfaces_count(self): def interfaces_count(self):
return self.vc_interfaces().count() return self.vc_interfaces().count()

View File

@ -203,16 +203,7 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
) )
oob_ip = tables.Column( oob_ip = tables.Column(
linkify=True, linkify=True,
order_by=('oob_ip4', 'oob_ip6'), verbose_name='OOB IP'
verbose_name='OOB IP Address'
)
oob_ip4 = tables.Column(
linkify=True,
verbose_name='OOB IPv4 Address'
)
oob_ip6 = tables.Column(
linkify=True,
verbose_name='OOB IPv6 Address'
) )
cluster = tables.Column( cluster = tables.Column(
linkify=True linkify=True

View File

@ -2456,7 +2456,7 @@ class InterfaceView(generic.ObjectView):
vdc_table = tables.VirtualDeviceContextTable( vdc_table = tables.VirtualDeviceContextTable(
data=instance.vdcs.restrict(request.user, 'view').prefetch_related('device'), data=instance.vdcs.restrict(request.user, 'view').prefetch_related('device'),
exclude=('tenant', 'tenant_group', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments', 'tags', exclude=('tenant', 'tenant_group', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments', 'tags',
'created', 'last_updated', 'actions', 'oob_ip', 'oob_ip4', 'oob_ip6'), 'created', 'last_updated', 'actions', 'oob_ip'),
orderable=False orderable=False
) )

View File

@ -853,14 +853,9 @@ class IPAddress(PrimaryModel):
def is_oob_ip(self): def is_oob_ip(self):
if self.assigned_object: if self.assigned_object:
parent = getattr(self.assigned_object, 'parent_object', None) parent = getattr(self.assigned_object, 'parent_object', None)
if self.family == 4: if parent.oob_ip:
if parent.oob_ip4: if parent.oob_ip.pk == self.pk:
if parent.oob_ip4.pk == self.pk: return True
return True
if self.family == 6:
if parent.oob_ip6:
if parent.oob_ip6.pk == self.pk:
return True
return False return False
@property @property

View File

@ -240,30 +240,16 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">OOB IPv4</th> <th scope="row">OOB IP</th>
<td> <td>
{% if object.oob_ip4 %} {% if object.oob_ip %}
<a href="{{ object.oob_ip4.get_absolute_url }}">{{ object.oob_ip4.address.ip }}</a> <a href="{{ object.oob_ip.get_absolute_url }}" id="oob_ip">{{ object.oob_ip.address.ip }}</a>
{% if object.oob_ip4.nat_inside %} {% if object.oob_ip.nat_inside %}
(NAT for <a href="{{ object.oob_ip4.nat_inside.get_absolute_url }}">{{ object.oob_ip4.nat_inside.address.ip }}</a>) (NAT for <a href="{{ object.oob_ip.nat_inside.get_absolute_url }}">{{ object.oob_ip.nat_inside.address.ip }}</a>)
{% elif object.oob_ip4.nat_outside.exists %} {% elif object.oob_ip.nat_outside.exists %}
(NAT: {% for nat in object.oob_ip4.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}) (NAT: {% for nat in object.oob_ip.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
{% endif %}
{% else %}
{{ ''|placeholder }}
{% endif %}
</td>
</tr>
<tr>
<th scope="row">OOB IPv6</th>
<td>
{% if object.oob_ip6 %}
<a href="{{ object.oob_ip6.get_absolute_url }}">{{ object.oob_ip6.address.ip }}</a>
{% if object.oob_ip6.nat_inside %}
(NAT for <a href="{{ object.oob_ip6.nat_inside.get_absolute_url }}">{{ object.oob_ip6.nat_inside.address.ip }}</a>)
{% elif object.oob_ip6.nat_outside.exists %}
(NAT: {% for nat in object.oob_ip6.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
{% endif %} {% endif %}
{% copy_content "oob_ip" %}
{% else %} {% else %}
{{ ''|placeholder }} {{ ''|placeholder }}
{% endif %} {% endif %}

View File

@ -68,8 +68,7 @@
{% if object.pk %} {% if object.pk %}
{% render_field form.primary_ip4 %} {% render_field form.primary_ip4 %}
{% render_field form.primary_ip6 %} {% render_field form.primary_ip6 %}
{% render_field form.oob_ip4 %} {% render_field form.oob_ip %}
{% render_field form.oob_ip6 %}
{% endif %} {% endif %}
</div> </div>