Initial work on #4721 (WIP)

This commit is contained in:
Jeremy Stretch
2020-06-22 13:10:56 -04:00
parent 181bcd70ad
commit 6cb31a274f
26 changed files with 481 additions and 215 deletions

View File

@@ -1,3 +1,5 @@
from django.db.models import Q
from .choices import IPAddressRoleChoices
# BGP ASN bounds
@@ -29,6 +31,11 @@ PREFIX_LENGTH_MAX = 127 # IPv6
# IPAddresses
#
IPADDRESS_ASSIGNMENT_MODELS = Q(
Q(app_label='dcim', model='interface') |
Q(app_label='virtualization', model='interface')
)
IPADDRESS_MASK_LENGTH_MIN = 1
IPADDRESS_MASK_LENGTH_MAX = 128 # IPv6

View File

@@ -299,37 +299,37 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldFilterSet,
to_field_name='rd',
label='VRF (RD)',
)
device = MultiValueCharFilter(
method='filter_device',
field_name='name',
label='Device (name)',
)
device_id = MultiValueNumberFilter(
method='filter_device',
field_name='pk',
label='Device (ID)',
)
virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
field_name='interface__virtual_machine',
queryset=VirtualMachine.objects.unrestricted(),
label='Virtual machine (ID)',
)
virtual_machine = django_filters.ModelMultipleChoiceFilter(
field_name='interface__virtual_machine__name',
queryset=VirtualMachine.objects.unrestricted(),
to_field_name='name',
label='Virtual machine (name)',
)
interface = django_filters.ModelMultipleChoiceFilter(
field_name='interface__name',
queryset=Interface.objects.unrestricted(),
to_field_name='name',
label='Interface (ID)',
)
interface_id = django_filters.ModelMultipleChoiceFilter(
queryset=Interface.objects.unrestricted(),
label='Interface (ID)',
)
# device = MultiValueCharFilter(
# method='filter_device',
# field_name='name',
# label='Device (name)',
# )
# device_id = MultiValueNumberFilter(
# method='filter_device',
# field_name='pk',
# label='Device (ID)',
# )
# virtual_machine_id = django_filters.ModelMultipleChoiceFilter(
# field_name='interface__virtual_machine',
# queryset=VirtualMachine.objects.unrestricted(),
# label='Virtual machine (ID)',
# )
# virtual_machine = django_filters.ModelMultipleChoiceFilter(
# field_name='interface__virtual_machine__name',
# queryset=VirtualMachine.objects.unrestricted(),
# to_field_name='name',
# label='Virtual machine (name)',
# )
# interface = django_filters.ModelMultipleChoiceFilter(
# field_name='interface__name',
# queryset=Interface.objects.unrestricted(),
# to_field_name='name',
# label='Interface (ID)',
# )
# interface_id = django_filters.ModelMultipleChoiceFilter(
# queryset=Interface.objects.unrestricted(),
# label='Interface (ID)',
# )
assigned_to_interface = django_filters.BooleanFilter(
method='_assigned_to_interface',
label='Is assigned to an interface',

View File

@@ -1,4 +1,5 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.validators import MaxValueValidator, MinValueValidator
from dcim.models import Device, Interface, Rack, Region, Site
@@ -14,7 +15,7 @@ from utilities.forms import (
ExpandableIPAddressField, ReturnURLForm, SlugField, StaticSelect2, StaticSelect2Multiple, TagFilterField,
BOOLEAN_WITH_BLANK_CHOICES,
)
from virtualization.models import VirtualMachine
from virtualization.models import Interface as VMInterface, VirtualMachine
from .choices import *
from .constants import *
from .models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
@@ -1194,13 +1195,14 @@ class ServiceForm(BootstrapMixin, CustomFieldModelForm):
# Limit IP address choices to those assigned to interfaces of the parent device/VM
if self.instance.device:
vc_interface_ids = [i['id'] for i in self.instance.device.vc_interfaces.values('id')]
self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
interface_id__in=vc_interface_ids
assigned_object_type=ContentType.objects.get_for_model(Interface),
assigned_object_id__in=self.instance.device.vc_interfaces.values('id', flat=True)
)
elif self.instance.virtual_machine:
self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
interface__virtual_machine=self.instance.virtual_machine
assigned_object_type=ContentType.objects.get_for_model(VMInterface),
assigned_object_id__in=self.instance.virtual_machine.interfaces.values_list('id', flat=True)
)
else:
self.fields['ipaddresses'].choices = []

View File

@@ -0,0 +1,35 @@
from django.db import migrations, models
import django.db.models.deletion
def set_assigned_object_type(apps, schema_editor):
ContentType = apps.get_model('contenttypes', 'ContentType')
IPAddress = apps.get_model('ipam', 'IPAddress')
device_ct = ContentType.objects.get(app_label='dcim', model='interface').pk
IPAddress.objects.update(assigned_object_type=device_ct)
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('ipam', '0036_standardize_description'),
]
operations = [
migrations.RenameField(
model_name='ipaddress',
old_name='interface',
new_name='assigned_object_id',
),
migrations.AddField(
model_name='ipaddress',
name='assigned_object_type',
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'dcim'), ('model', 'interface')), models.Q(('app_label', 'virtualization'), ('model', 'interface')), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType', blank=True, null=True),
preserve_default=False,
),
migrations.RunPython(
code=set_assigned_object_type
),
]

View File

@@ -1,6 +1,7 @@
import netaddr
from django.conf import settings
from django.contrib.contenttypes.fields import GenericRelation
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
@@ -606,13 +607,25 @@ class IPAddress(ChangeLoggedModel, CustomFieldModel):
blank=True,
help_text='The functional role of this IP'
)
interface = models.ForeignKey(
assigned_object_type = models.ForeignKey(
to=ContentType,
limit_choices_to=IPADDRESS_ASSIGNMENT_MODELS,
on_delete=models.PROTECT,
related_name='+',
blank=True,
null=True
)
assigned_object_id = models.ForeignKey(
to='dcim.Interface',
on_delete=models.CASCADE,
related_name='ip_addresses',
blank=True,
null=True
)
assigned_object = GenericForeignKey(
ct_field='assigned_object_type',
fk_field='assigned_object_id'
)
nat_inside = models.OneToOneField(
to='self',
on_delete=models.SET_NULL,

View File

@@ -431,18 +431,14 @@ class IPAddressTable(BaseTable):
tenant = tables.TemplateColumn(
template_code=TENANT_LINK
)
parent = tables.TemplateColumn(
template_code=IPADDRESS_PARENT,
orderable=False
)
interface = tables.Column(
orderable=False
assigned = tables.BooleanColumn(
accessor='assigned_object_id'
)
class Meta(BaseTable.Meta):
model = IPAddress
fields = (
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'dns_name', 'description',
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
)
row_attrs = {
'class': lambda record: 'success' if not isinstance(record, IPAddress) else '',
@@ -465,11 +461,11 @@ class IPAddressDetailTable(IPAddressTable):
class Meta(IPAddressTable.Meta):
fields = (
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'parent', 'interface', 'dns_name',
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name',
'description', 'tags',
)
default_columns = (
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'dns_name', 'description',
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',
)

View File

@@ -4,7 +4,7 @@ from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer,
from ipam.choices import *
from ipam.filters import *
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
from virtualization.models import Cluster, ClusterType, VirtualMachine
from virtualization.models import Cluster, ClusterType, Interfaces as VMInterface, VirtualMachine
from tenancy.models import Tenant, TenantGroup
@@ -375,6 +375,13 @@ class IPAddressTestCase(TestCase):
)
Device.objects.bulk_create(devices)
interfaces = (
Interface(device=devices[0], name='Interface 1'),
Interface(device=devices[1], name='Interface 2'),
Interface(device=devices[2], name='Interface 3'),
)
Interface.objects.bulk_create(interfaces)
clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
cluster = Cluster.objects.create(type=clustertype, name='Cluster 1')
@@ -385,15 +392,12 @@ class IPAddressTestCase(TestCase):
)
VirtualMachine.objects.bulk_create(virtual_machines)
interfaces = (
Interface(device=devices[0], name='Interface 1'),
Interface(device=devices[1], name='Interface 2'),
Interface(device=devices[2], name='Interface 3'),
Interface(virtual_machine=virtual_machines[0], name='Interface 1'),
Interface(virtual_machine=virtual_machines[1], name='Interface 2'),
Interface(virtual_machine=virtual_machines[2], name='Interface 3'),
vm_interfaces = (
VMInterface(virtual_machine=virtual_machines[0], name='Interface 1'),
VMInterface(virtual_machine=virtual_machines[1], name='Interface 2'),
VMInterface(virtual_machine=virtual_machines[2], name='Interface 3'),
)
Interface.objects.bulk_create(interfaces)
VMInterface.objects.bulk_create(vm_interfaces)
tenant_groups = (
TenantGroup(name='Tenant group 1', slug='tenant-group-1'),