diff --git a/docs/data-model/ipam.md b/docs/data-model/ipam.md index ee54e74d2..abb50d67b 100644 --- a/docs/data-model/ipam.md +++ b/docs/data-model/ipam.md @@ -83,7 +83,7 @@ One IP address can be designated as the network address translation (NAT) IP add # VLANs -A VLAN represents an isolated layer two domain, identified by a name and a numeric ID (1-4094). Note that while it is good practice, neither VLAN names nor IDs must be unique within a site. This is to accommodate the fact that many real-world network use less-than-optimal VLAN allocations and may have overlapping VLAN ID assignments in practice. +A VLAN represents an isolated layer two domain, identified by a name and a numeric ID (1-4094). Note that while it is good practice, neither VLAN names nor IDs must be unique within a site. This is to accommodate the fact that many real-world network use less-than-optimal VLAN allocations and may have overlapping VLAN ID assignments in practice. A "service identifier" (VXLAN or ISID, 1-16777215) can uniquely identify a VLAN where the ID is not unique. Like prefixes, each VLAN is assigned an operational status and (optionally) a functional role. diff --git a/netbox/ipam/admin.py b/netbox/ipam/admin.py index f3f914129..57170188c 100644 --- a/netbox/ipam/admin.py +++ b/netbox/ipam/admin.py @@ -72,9 +72,9 @@ class VLANGroupAdmin(admin.ModelAdmin): @admin.register(VLAN) class VLANAdmin(admin.ModelAdmin): - list_display = ['site', 'vid', 'name', 'tenant', 'status', 'role'] + list_display = ['site', 'vid', 'name', 'tenant', 'status', 'role', 'service_identifier'] list_filter = ['site', 'tenant', 'status', 'role'] - search_fields = ['vid', 'name'] + search_fields = ['vid', 'name', 'service_identifier'] def get_queryset(self, request): qs = super(VLANAdmin, self).get_queryset(request) diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 83be76169..5babe40ec 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -522,7 +522,7 @@ class VLANForm(BootstrapMixin, CustomFieldForm): class Meta: model = VLAN - fields = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description'] + fields = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'service_identifier'] help_texts = { 'site': "The site at which this VLAN exists", 'group': "VLAN group (optional)", @@ -530,6 +530,7 @@ class VLANForm(BootstrapMixin, CustomFieldForm): 'name': "Configured VLAN name", 'status': "Operational status of this VLAN", 'role': "The primary function of this VLAN", + 'service_identifier': "Unique identifier: VXLAN or ISID", } widgets = { 'site': forms.Select(attrs={'filter-for': 'group'}), @@ -561,7 +562,7 @@ class VLANFromCSVForm(forms.ModelForm): class Meta: model = VLAN - fields = ['site', 'group', 'vid', 'name', 'tenant', 'status_name', 'role', 'description'] + fields = ['site', 'group', 'vid', 'name', 'tenant', 'status_name', 'role', 'description', 'service_identifier'] def save(self, *args, **kwargs): m = super(VLANFromCSVForm, self).save(commit=False) @@ -586,7 +587,7 @@ class VLANBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): description = forms.CharField(max_length=100, required=False) class Meta: - nullable_fields = ['group', 'tenant', 'role', 'description'] + nullable_fields = ['group', 'tenant', 'role', 'description', 'service_identifier'] def vlan_status_choices(): diff --git a/netbox/ipam/migrations/0014_auto_20170122_0829.py b/netbox/ipam/migrations/0014_auto_20170122_0829.py new file mode 100644 index 000000000..7fe230992 --- /dev/null +++ b/netbox/ipam/migrations/0014_auto_20170122_0829.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-22 08:29 +from __future__ import unicode_literals + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0013_prefix_add_is_pool'), + ] + + operations = [ + migrations.AlterModelOptions( + name='vlan', + options={'ordering': ['site', 'group', 'vid', 'service_identifier'], 'verbose_name': 'VLAN', 'verbose_name_plural': 'VLANs'}, + ), + migrations.AddField( + model_name='vlan', + name='service_identifier', + field=models.PositiveIntegerField(blank=True, default=None, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(16777215)], verbose_name=b'Service Identifier'), + ), + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index d37fdec25..d1ad2abea 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -524,10 +524,14 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel): status = models.PositiveSmallIntegerField('Status', choices=VLAN_STATUS_CHOICES, default=1) role = models.ForeignKey('Role', related_name='vlans', on_delete=models.SET_NULL, blank=True, null=True) description = models.CharField(max_length=100, blank=True) + service_identifier = models.PositiveIntegerField(null=True, blank=True, default=None, verbose_name='Service Identifier', validators=[ + MinValueValidator(1), + MaxValueValidator(16777215) + ]) custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id') class Meta: - ordering = ['site', 'group', 'vid'] + ordering = ['site', 'group', 'vid', 'service_identifier'] unique_together = [ ['group', 'vid'], ['group', 'name'], @@ -559,6 +563,7 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel): self.get_status_display(), self.role.name if self.role else None, self.description, + self.service_identifier ]) @property diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index 6c99f7d9e..df20fd9e2 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -312,7 +312,8 @@ class VLANTable(BaseTable): status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status') role = tables.TemplateColumn(VLAN_ROLE_LINK, verbose_name='Role') description = tables.Column(verbose_name='Description') + service_identifier = tables.Column(verbose_name='Service Identifier') class Meta(BaseTable.Meta): model = VLAN - fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description') + fields = ('pk', 'vid', 'site', 'group', 'name', 'prefixes', 'tenant', 'status', 'role', 'description', 'service_identifier') diff --git a/netbox/templates/ipam/vlan.html b/netbox/templates/ipam/vlan.html index 3d81392e0..a379cf405 100644 --- a/netbox/templates/ipam/vlan.html +++ b/netbox/templates/ipam/vlan.html @@ -108,6 +108,16 @@ N/A {% endif %} + + + Service Identifier + + {% if vlan.service_identifier %} + {{ vlan.service_identifier }} + {% else %} + None + {% endif %} + diff --git a/netbox/templates/ipam/vlan_edit.html b/netbox/templates/ipam/vlan_edit.html index 10c633aa5..59d4b85ce 100644 --- a/netbox/templates/ipam/vlan_edit.html +++ b/netbox/templates/ipam/vlan_edit.html @@ -13,6 +13,7 @@ {% render_field form.status %} {% render_field form.role %} {% render_field form.description %} + {% render_field form.service_identifier %} {% if form.custom_fields %} diff --git a/netbox/templates/ipam/vlan_import.html b/netbox/templates/ipam/vlan_import.html index 16456ba01..e728dbf16 100644 --- a/netbox/templates/ipam/vlan_import.html +++ b/netbox/templates/ipam/vlan_import.html @@ -68,10 +68,15 @@ Short description (optional) Security team only + + Service Identifier + VXLAN identifier or ISID + 1001400 +

Example

-
LAS2,Backend Network,1400,Cameras,Internal,Active,Security,Security team only
+
LAS2,Backend Network,1400,Cameras,Internal,Active,Security,Security team only,1001400
{% endblock %}