Misc cleanup

This commit is contained in:
Jeremy Stretch 2023-07-25 14:17:26 -04:00
parent e38ba1283e
commit 9550f60a69
12 changed files with 42 additions and 54 deletions

View File

@ -87,6 +87,10 @@ Each device may designate one primary IPv4 address and/or one primary IPv6 addre
!!! tip !!! tip
NetBox will prefer IPv6 addresses over IPv4 addresses by default. This can be changed by setting the `PREFER_IPV4` configuration parameter. NetBox will prefer IPv6 addresses over IPv4 addresses by default. This can be changed by setting the `PREFER_IPV4` configuration parameter.
### Out-of-band (OOB) IP Address
Each device may designate its out-of-band IP address. Out-of-band IPs are typically used to access network infrastructure via a physically separate management network.
### Cluster ### Cluster
If this device will serve as a host for a virtualization [cluster](../virtualization/cluster.md), it can be assigned here. (Host devices can also be assigned by editing the cluster.) If this device will serve as a host for a virtualization [cluster](../virtualization/cluster.md), it can be assigned here. (Host devices can also be assigned by editing the cluster.)

View File

@ -687,11 +687,11 @@ class DeviceSerializer(NetBoxModelSerializer):
fields = [ fields = [
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device', 'status', 'site', 'location', 'rack', 'position', 'face', 'latitude', 'longitude', 'parent_device', 'status',
'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis',
'vc_priority', 'description', 'comments', 'config_template', 'local_context_data', 'tags', 'custom_fields', 'vc_position', 'vc_priority', 'description', 'comments', 'config_template', 'local_context_data', 'tags',
'created', 'last_updated', 'console_port_count', 'console_server_port_count', 'power_port_count', 'custom_fields', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count', 'device_bay_count', 'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
'module_bay_count', 'inventory_item_count', 'device_bay_count', 'module_bay_count', 'inventory_item_count',
] ]
@extend_schema_field(NestedDeviceSerializer) @extend_schema_field(NestedDeviceSerializer)
@ -713,11 +713,11 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
fields = [ fields = [
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag', 'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'airflow', 'primary_ip',
'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'primary_ip4', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'config_template', 'description', 'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context',
'created', 'last_updated', 'console_port_count', 'console_server_port_count', 'power_port_count', 'config_template', 'created', 'last_updated', 'console_port_count', 'console_server_port_count',
'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count', 'device_bay_count', 'power_port_count', 'power_outlet_count', 'interface_count', 'front_port_count', 'rear_port_count',
'module_bay_count', 'inventory_item_count', 'device_bay_count', 'module_bay_count', 'inventory_item_count',
] ]
@extend_schema_field(serializers.JSONField(allow_null=True)) @extend_schema_field(serializers.JSONField(allow_null=True))

View File

@ -943,7 +943,7 @@ class DeviceFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilter
) )
has_oob_ip = django_filters.BooleanFilter( has_oob_ip = django_filters.BooleanFilter(
method='_has_oob_ip', method='_has_oob_ip',
label=_('Has a OOB IP'), label=_('Has an out-of-band IP'),
) )
virtual_chassis_id = django_filters.ModelMultipleChoiceFilter( virtual_chassis_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_chassis', field_name='virtual_chassis',

View File

@ -725,7 +725,7 @@ class DeviceFilterForm(
) )
has_oob_ip = forms.NullBooleanField( has_oob_ip = forms.NullBooleanField(
required=False, required=False,
label='Has a OOB IP', label='Has an OOB IP',
widget=forms.Select( widget=forms.Select(
choices=BOOLEAN_WITH_BLANK_CHOICES choices=BOOLEAN_WITH_BLANK_CHOICES
) )

View File

@ -449,9 +449,9 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
model = Device model = Device
fields = [ fields = [
'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', 'oob_ip', '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',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -476,7 +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)) oob_ip_choices.extend(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,
@ -486,7 +486,6 @@ 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'].choices = oob_ip_choices self.fields['oob_ip'].choices = oob_ip_choices

View File

@ -39,7 +39,7 @@ def recalculate_device_counts(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('dcim', '0174_rack_starting_unit'), ('dcim', '0175_device_oob_ip'),
] ]
operations = [ operations = [

View File

@ -597,7 +597,7 @@ class Device(PrimaryModel, ConfigContextModel):
related_name='+', related_name='+',
blank=True, blank=True,
null=True, null=True,
verbose_name='OOB IP' verbose_name='Out-of-band IP'
) )
cluster = models.ForeignKey( cluster = models.ForeignKey(
to='virtualization.Cluster', to='virtualization.Cluster',
@ -824,7 +824,7 @@ class Device(PrimaryModel, ConfigContextModel):
except DeviceType.DoesNotExist: except DeviceType.DoesNotExist:
pass pass
# Validate primary IP addresses # Validate primary & OOB IP addresses
vc_interfaces = self.vc_interfaces(if_master=False) vc_interfaces = self.vc_interfaces(if_master=False)
if self.primary_ip4: if self.primary_ip4:
if self.primary_ip4.family != 4: if self.primary_ip4.family != 4:
@ -852,8 +852,6 @@ class Device(PrimaryModel, ConfigContextModel):
raise ValidationError({ raise ValidationError({
'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device." 'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device."
}) })
# OOB ip validation
if self.oob_ip: if self.oob_ip:
if self.oob_ip.assigned_object in vc_interfaces: if self.oob_ip.assigned_object in vc_interfaces:
pass pass
@ -863,6 +861,7 @@ class Device(PrimaryModel, ConfigContextModel):
raise ValidationError({ raise ValidationError({
'oob_ip': f"The specified IP address ({self.oob_ip}) is not assigned to this device." 'oob_ip': f"The specified IP address ({self.oob_ip}) 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:
if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer: if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer:

View File

@ -271,8 +271,8 @@ class DeviceTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type', 'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device', 'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'parent_device',
'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4', 'device_bay_position', 'position', 'face', 'latitude', 'longitude', 'airflow', 'primary_ip', 'primary_ip4',
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description', 'config_template', 'primary_ip6', 'oob_ip', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'description',
'comments', 'contacts', 'oob_ip', 'tags', 'created', 'last_updated', 'config_template', 'comments', 'contacts', 'tags', 'created', 'last_updated',
) )
default_columns = ( default_columns = (
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type', 'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',

View File

@ -2452,11 +2452,13 @@ class InterfaceView(generic.ObjectView):
queryset = Interface.objects.all() queryset = Interface.objects.all()
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
# Get assigned VDC's # Get assigned VDCs
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=(
'created', 'last_updated', 'actions', 'oob_ip'), 'tenant', 'tenant_group', 'primary_ip', 'primary_ip4', 'primary_ip6', 'oob_ip', 'comments', 'tags',
'created', 'last_updated', 'actions',
),
orderable=False orderable=False
) )

View File

@ -853,8 +853,7 @@ 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 parent.oob_ip: if parent.oob_ip_id == self.pk:
if parent.oob_ip.pk == self.pk:
return True return True
return False return False
@ -862,13 +861,9 @@ class IPAddress(PrimaryModel):
def is_primary_ip(self): def is_primary_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 self.family == 4 and parent.primary_ip4_id == self.pk:
if parent.primary_ip4:
if parent.primary_ip4.pk == self.pk:
return True return True
if self.family == 6: if self.family == 6 and parent.primary_ip6_id == self.pk:
if parent.primary_ip6:
if parent.primary_ip6.pk == self.pk:
return True return True
return False return False

View File

@ -52,25 +52,19 @@ def handle_prefix_deleted(instance, **kwargs):
@receiver(pre_delete, sender=IPAddress) @receiver(pre_delete, sender=IPAddress)
def clear_primary_ip(instance, **kwargs): def clear_primary_ip(instance, **kwargs):
""" """
When an IPAddress is deleted, trigger save() on any Devices/VirtualMachines for which it When an IPAddress is deleted, trigger save() on any Devices/VirtualMachines for which it was a primary IP.
was a primary IP.
""" """
field_name = f'primary_ip{instance.family}' field_name = f'primary_ip{instance.family}'
device = Device.objects.filter(**{field_name: instance}).first() if device := Device.objects.filter(**{field_name: instance}).first():
if device:
device.save() device.save()
virtualmachine = VirtualMachine.objects.filter(**{field_name: instance}).first() if virtualmachine := VirtualMachine.objects.filter(**{field_name: instance}).first():
if virtualmachine:
virtualmachine.save() virtualmachine.save()
@receiver(pre_delete, sender=IPAddress) @receiver(pre_delete, sender=IPAddress)
def clear_oob_ip(instance, **kwargs): def clear_oob_ip(instance, **kwargs):
""" """
When an IPAddress is deleted, trigger save() on any Devices/VirtualMachines for which it When an IPAddress is deleted, trigger save() on any Devices for which it was a OOB IP.
was a OOB IP.
""" """
field_name = f'oob_ip' if device := Device.objects.filter(oob_ip=instance).first():
device = Device.objects.filter(**{field_name: instance}).first()
if device:
device.save() device.save()

View File

@ -240,15 +240,10 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<th scope="row">OOB IP</th> <th scope="row">Out-of-band IP</th>
<td> <td>
{% if object.oob_ip %} {% if object.oob_ip %}
<a href="{{ object.oob_ip.get_absolute_url }}" id="oob_ip">{{ object.oob_ip.address.ip }}</a> <a href="{{ object.oob_ip.get_absolute_url }}" id="oob_ip">{{ object.oob_ip.address.ip }}</a>
{% if object.oob_ip.nat_inside %}
(NAT for <a href="{{ object.oob_ip.nat_inside.get_absolute_url }}">{{ object.oob_ip.nat_inside.address.ip }}</a>)
{% elif object.oob_ip.nat_outside.exists %}
(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 %}
{% copy_content "oob_ip" %} {% copy_content "oob_ip" %}
{% else %} {% else %}
{{ ''|placeholder }} {{ ''|placeholder }}