Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
26a96fb
9627 initial commit
arthanson Jun 13, 2024
9e99703
Merge branch 'feature' into 9627-vlan-group
arthanson Jun 20, 2024
2f2945b
9627 numeric range field
arthanson Jun 20, 2024
8e67c54
9627 numeric range field
arthanson Jun 20, 2024
8f8ba2c
9627 numeric range field
arthanson Jun 20, 2024
da8cf67
9627 numeric range field
arthanson Jun 20, 2024
2f38ecc
9627 add stuff for utilization calc
arthanson Jun 21, 2024
0411f0d
9627 update views
arthanson Jun 21, 2024
8fd80d7
9627 fixes
arthanson Jun 21, 2024
9bc2b8e
9627 available_vlans
arthanson Jun 21, 2024
35d7ba1
9627 available_vlans
arthanson Jun 21, 2024
35a5165
9627 fixes
arthanson Jun 21, 2024
7628145
9627 bulk import / edit
arthanson Jun 21, 2024
f509ca4
9627 test fixes
arthanson Jun 21, 2024
2263126
9627 test fixes
arthanson Jun 21, 2024
1560fff
9627 update validation
arthanson Jun 21, 2024
0ec5103
9627 fix tests
arthanson Jun 21, 2024
2800cd0
9627 fix tests
arthanson Jun 21, 2024
02221f1
9627 fix tests
arthanson Jun 21, 2024
cb55642
9627 fix tests
arthanson Jun 21, 2024
04d76ab
9627 fix tests
arthanson Jun 21, 2024
3c89651
9627 fix tests
arthanson Jun 24, 2024
f96ca90
9627 fix merge conflict
arthanson Jun 25, 2024
f32b0f6
Merge branch 'feature' into 9627-vlan-group3
jeremystretch Jul 9, 2024
ff9197e
9627 review changes
arthanson Jul 11, 2024
bbfcba4
9627 temp vlan_id filter
arthanson Jul 11, 2024
7a646cd
Clean up labels
jeremystretch Jul 13, 2024
6171ce6
Remove annotate_vlan_ranges() from VLANGroupQuerySet
jeremystretch Jul 13, 2024
674907c
Misc cleanup
jeremystretch Jul 13, 2024
45183e4
Implement contains_vid filter
jeremystretch Jul 13, 2024
46a40c5
Serialize VID ranges as integer lists in REST API
jeremystretch Jul 13, 2024
3c2392c
Remove default value from vlan_id_ranges
jeremystretch Jul 13, 2024
daccfc3
9627 fix typo in test
arthanson Jul 15, 2024
e039ba4
Merge branch 'feature' into 9627-vlan-group3
arthanson Jul 15, 2024
f6c2396
Require vlan_id_ranges & set default value
jeremystretch Jul 15, 2024
c1a0eb8
Fix logic for upper range boundaries
jeremystretch Jul 15, 2024
77cb678
Add field to VLANGroup model documentation
jeremystretch Jul 15, 2024
9f56745
Rename vlan_id_ranges to vid_ranges
jeremystretch Jul 15, 2024
aaa4992
Fix computation of available VLAN IDs
jeremystretch Jul 15, 2024
aa8475c
Clean up migration
jeremystretch Jul 16, 2024
881135b
Add tests for range utility functions
jeremystretch Jul 16, 2024
e2dae0c
Clean up add_available_vlans()
jeremystretch Jul 16, 2024
965be76
Misc cleanup, add test for VID validation
jeremystretch Jul 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/models/ipam/vlangroup.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ A unique human-friendly name.

A unique URL-friendly identifier. (This value can be used for filtering.)

### Minimum & Maximum VLAN IDs
### VLAN ID Ranges

A minimum and maximum child VLAN ID must be set for each group. (These default to 1 and 4094 respectively.) VLANs created within a group must have a VID that falls between these values (inclusive).
The set of VLAN IDs which are encompassed by the group. By default, this will be the entire range of valid IEEE 802.1Q VLAN IDs (1 to 4094, inclusive). VLANs created within a group must have a VID that falls within one of these ranges. Ranges may not overlap.

### Scope

Expand Down
7 changes: 4 additions & 3 deletions netbox/ipam/api/serializers_/vlans.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ipam.choices import *
from ipam.constants import VLANGROUP_SCOPE_TYPES
from ipam.models import VLAN, VLANGroup
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.fields import ChoiceField, ContentTypeField, IntegerRangeSerializer, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from utilities.api import get_serializer_for_model
Expand All @@ -32,6 +32,7 @@ class VLANGroupSerializer(NetBoxModelSerializer):
)
scope_id = serializers.IntegerField(allow_null=True, required=False, default=None)
scope = serializers.SerializerMethodField(read_only=True)
vid_ranges = IntegerRangeSerializer(many=True, required=False)
utilization = serializers.CharField(read_only=True)

# Related object counts
Expand All @@ -40,8 +41,8 @@ class VLANGroupSerializer(NetBoxModelSerializer):
class Meta:
model = VLANGroup
fields = [
'id', 'url', 'display_url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'min_vid',
'max_vid', 'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization'
'id', 'url', 'display_url', 'display', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'vid_ranges',
'description', 'tags', 'custom_fields', 'created', 'last_updated', 'vlan_count', 'utilization'
]
brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'vlan_count')
validators = []
Expand Down
20 changes: 19 additions & 1 deletion netbox/ipam/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -911,10 +911,13 @@ class VLANGroupFilterSet(OrganizationalModelFilterSet):
cluster = django_filters.NumberFilter(
method='filter_scope'
)
contains_vid = django_filters.NumberFilter(
method='filter_contains_vid'
)

class Meta:
model = VLANGroup
fields = ('id', 'name', 'slug', 'min_vid', 'max_vid', 'description', 'scope_id')
fields = ('id', 'name', 'slug', 'description', 'scope_id')

def search(self, queryset, name, value):
if not value.strip():
Expand All @@ -932,6 +935,21 @@ def filter_scope(self, queryset, name, value):
scope_id=value
)

def filter_contains_vid(self, queryset, name, value):
"""
Return all VLANGroups which contain the given VLAN ID.
"""
table_name = VLANGroup._meta.db_table
# TODO: See if this can be optimized without compromising queryset integrity
# Expand VLAN ID ranges to query by integer
groups = VLANGroup.objects.raw(
f'SELECT id FROM {table_name}, unnest(vid_ranges) vid_range WHERE %s <@ vid_range',
params=(value,)
)
return queryset.filter(
pk__in=[g.id for g in groups]
)


class VLANFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
region_id = TreeNodeMultipleChoiceFilter(
Expand Down
19 changes: 6 additions & 13 deletions netbox/ipam/forms/bulk_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from utilities.forms import add_blank_choice
from utilities.forms.fields import (
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
NumericRangeArrayField,
)
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import BulkEditNullBooleanSelect
Expand Down Expand Up @@ -408,18 +409,6 @@ class FHRPGroupBulkEditForm(NetBoxModelBulkEditForm):


class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
min_vid = forms.IntegerField(
min_value=VLAN_VID_MIN,
max_value=VLAN_VID_MAX,
required=False,
label=_('Minimum child VLAN VID')
)
max_vid = forms.IntegerField(
min_value=VLAN_VID_MIN,
max_value=VLAN_VID_MAX,
required=False,
label=_('Maximum child VLAN VID')
)
description = forms.CharField(
label=_('Description'),
max_length=200,
Expand Down Expand Up @@ -483,10 +472,14 @@ class VLANGroupBulkEditForm(NetBoxModelBulkEditForm):
'group_id': '$clustergroup',
}
)
vid_ranges = NumericRangeArrayField(
label=_('VLAN ID ranges'),
required=False
)

model = VLANGroup
fieldsets = (
FieldSet('site', 'min_vid', 'max_vid', 'description'),
FieldSet('site', 'vid_ranges', 'description'),
FieldSet(
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster', name=_('Scope')
),
Expand Down
18 changes: 5 additions & 13 deletions netbox/ipam/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant
from utilities.forms.fields import (
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, SlugField,
NumericRangeArrayField,
)
from virtualization.models import VirtualMachine, VMInterface

Expand Down Expand Up @@ -411,22 +412,13 @@ class VLANGroupImportForm(NetBoxModelImportForm):
required=False,
label=_('Scope type (app & model)')
)
min_vid = forms.IntegerField(
min_value=VLAN_VID_MIN,
max_value=VLAN_VID_MAX,
required=False,
label=_('Minimum child VLAN VID (default: {minimum})').format(minimum=VLAN_VID_MIN)
)
max_vid = forms.IntegerField(
min_value=VLAN_VID_MIN,
max_value=VLAN_VID_MAX,
required=False,
label=_('Maximum child VLAN VID (default: {maximum})').format(maximum=VLAN_VID_MIN)
vid_ranges = NumericRangeArrayField(
required=False
)

class Meta:
model = VLANGroup
fields = ('name', 'slug', 'scope_type', 'scope_id', 'min_vid', 'max_vid', 'description', 'tags')
fields = ('name', 'slug', 'scope_type', 'scope_id', 'vid_ranges', 'description', 'tags')
labels = {
'scope_id': 'Scope ID',
}
Expand Down
19 changes: 6 additions & 13 deletions netbox/ipam/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
FieldSet('q', 'filter_id', 'tag'),
FieldSet('region', 'sitegroup', 'site', 'location', 'rack', name=_('Location')),
FieldSet('cluster_group', 'cluster', name=_('Cluster')),
FieldSet('min_vid', 'max_vid', name=_('VLAN ID')),
FieldSet('contains_vid', name=_('VLANs')),
)
model = VLANGroup
region = DynamicModelMultipleChoiceField(
Expand Down Expand Up @@ -441,18 +441,6 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('Rack')
)
min_vid = forms.IntegerField(
required=False,
min_value=VLAN_VID_MIN,
max_value=VLAN_VID_MAX,
label=_('Minimum VID')
)
max_vid = forms.IntegerField(
required=False,
min_value=VLAN_VID_MIN,
max_value=VLAN_VID_MAX,
label=_('Maximum VID')
)
cluster = DynamicModelMultipleChoiceField(
queryset=Cluster.objects.all(),
required=False,
Expand All @@ -463,6 +451,11 @@ class VLANGroupFilterForm(NetBoxModelFilterSetForm):
required=False,
label=_('Cluster group')
)
contains_vid = forms.IntegerField(
min_value=0,
required=False,
label=_('Contains VLAN ID')
)

tag = TagFilterField(model)

Expand Down
10 changes: 7 additions & 3 deletions netbox/ipam/forms/model_forms.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.forms import IntegerRangeField, SimpleArrayField
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

Expand All @@ -14,7 +15,7 @@
from utilities.forms import add_blank_choice
from utilities.forms.fields import (
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, NumericArrayField,
SlugField,
NumericRangeArrayField, SlugField
)
from utilities.forms.rendering import FieldSet, InlineFields, ObjectAttribute, TabbedGroups
from utilities.forms.widgets import DatePicker
Expand Down Expand Up @@ -632,10 +633,13 @@ class VLANGroupForm(NetBoxModelForm):
}
)
slug = SlugField()
vid_ranges = NumericRangeArrayField(
label=_('VLAN IDs')
)

fieldsets = (
FieldSet('name', 'slug', 'description', 'tags', name=_('VLAN Group')),
FieldSet('min_vid', 'max_vid', name=_('Child VLANs')),
FieldSet('vid_ranges', name=_('Child VLANs')),
FieldSet(
'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack', 'clustergroup', 'cluster',
name=_('Scope')
Expand All @@ -646,7 +650,7 @@ class Meta:
model = VLANGroup
fields = [
'name', 'slug', 'description', 'scope_type', 'region', 'sitegroup', 'site', 'location', 'rack',
'clustergroup', 'cluster', 'min_vid', 'max_vid', 'tags',
'clustergroup', 'cluster', 'vid_ranges', 'tags',
]

def __init__(self, *args, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions netbox/ipam/graphql/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ class VLANType(NetBoxObjectType):
class VLANGroupType(OrganizationalObjectType):

vlans: List[VLANType]
vid_ranges: List[str]

@strawberry_django.field
def scope(self) -> Annotated[Union[
Expand Down
55 changes: 55 additions & 0 deletions netbox/ipam/migrations/0070_vlangroup_vlan_id_ranges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import django.contrib.postgres.fields
import django.contrib.postgres.fields.ranges
from django.db import migrations, models
from django.db.backends.postgresql.psycopg_any import NumericRange

import ipam.models.vlans


def set_vid_ranges(apps, schema_editor):
"""
Convert the min_vid & max_vid fields to a range in the new vid_ranges ArrayField.
"""
VLANGroup = apps.get_model('ipam', 'VLANGroup')
for group in VLANGroup.objects.all():
group.vid_ranges = [
NumericRange(group.min_vid, group.max_vid, bounds='[]')
]
group._total_vlan_ids = group.max_vid - group.min_vid + 1
group.save()


class Migration(migrations.Migration):

dependencies = [
('ipam', '0069_gfk_indexes'),
]

operations = [
migrations.AddField(
model_name='vlangroup',
name='vid_ranges',
field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ranges.IntegerRangeField(),
default=ipam.models.vlans.default_vid_ranges,
size=None
),
),
migrations.AddField(
model_name='vlangroup',
name='_total_vlan_ids',
field=models.PositiveBigIntegerField(default=4094),
),
migrations.RunPython(
code=set_vid_ranges,
reverse_code=migrations.RunPython.noop
),
migrations.RemoveField(
model_name='vlangroup',
name='max_vid',
),
migrations.RemoveField(
model_name='vlangroup',
name='min_vid',
),
]
Loading