Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes: #7854 - Add VDC/Instances/etc #10787

Merged
merged 46 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
11471a2
Work on #7854
DanSheps Oct 28, 2022
8b07cc1
Move to new URL scheme.
DanSheps Oct 28, 2022
ecf88f0
Fix PEP8 errors
DanSheps Oct 28, 2022
f366edc
Fix PEP8 errors
DanSheps Oct 28, 2022
9aef6fe
Add GraphQL and fix primary_ip missing
DanSheps Oct 28, 2022
04af1e1
Fix PEP8 on GQL Type
DanSheps Oct 28, 2022
b78eebe
Fix missing NestedSerializer.
DanSheps Oct 28, 2022
2e1f915
Fix missing NestedSerializer & rename VDC to VDCs
DanSheps Oct 28, 2022
969bca6
Fix migration
DanSheps Oct 31, 2022
52fbd84
Change Validation for identifier
DanSheps Nov 1, 2022
cf5482c
Fix missing migration
DanSheps Nov 1, 2022
0896b84
Rebase to feature
DanSheps Nov 1, 2022
0d3b859
Post-review changes
DanSheps Nov 2, 2022
a33b2a6
Interface related changes
DanSheps Nov 2, 2022
c3ed3ab
Remove VirtualDeviceContextTypeChoices
DanSheps Nov 2, 2022
2832dc7
Merge branch 'feature' into 7854-vdc
jeremystretch Nov 4, 2022
341243c
Accommodate recent changes in feature branch
jeremystretch Nov 4, 2022
14f3eda
Add tests
DanSheps Nov 4, 2022
c9afb28
Merge branch '7854-vdc' of https://github.com/netbox-community/netbox…
DanSheps Nov 4, 2022
51a7e9b
Update tests, and fix model form
DanSheps Nov 4, 2022
9b3794e
Update test_api
DanSheps Nov 9, 2022
18754fe
Update test_api.InterfaceTest create_data
DanSheps Nov 9, 2022
5f73eb9
Fix issue with tests
DanSheps Nov 9, 2022
0a8a6d3
Update interface serializer
DanSheps Nov 9, 2022
82b5e0e
Update serializer and tests
DanSheps Nov 9, 2022
03a56c0
Update status to be required
DanSheps Nov 10, 2022
c282b08
Remove error message for constraint
DanSheps Nov 10, 2022
6df4a38
Remove extraneous import
DanSheps Nov 10, 2022
b745438
Re-ordered devices menu to place VDC below virtual chassis
DanSheps Nov 10, 2022
909f075
Add helptext for `identifier` field
DanSheps Nov 10, 2022
b8592d1
Fix breadcrumb link
DanSheps Nov 10, 2022
95f8340
Remove add interface link
DanSheps Nov 10, 2022
b093f43
Add missing tenant and status fields
DanSheps Nov 10, 2022
ccd3132
Changes to tests as per Jeremy
DanSheps Nov 10, 2022
b05725c
Change for #9623
DanSheps Nov 10, 2022
f6a87df
Merge branch '7854-vdc' of https://github.com/netbox-community/netbox…
DanSheps Nov 10, 2022
9edd76b
Update filterset form for status field
DanSheps Nov 10, 2022
2f58962
Remove Rename View
DanSheps Nov 10, 2022
a17acda
Change tabs to spaces
DanSheps Nov 10, 2022
484ed84
Update netbox/dcim/tables/devices.py
DanSheps Nov 10, 2022
99e36f2
Update netbox/dcim/tables/devices.py
DanSheps Nov 10, 2022
a0ea7bc
Fix tenant in bulk_edit
DanSheps Nov 10, 2022
690c011
Merge remote-tracking branch 'origin/7854-vdc' into 7854-vdc
DanSheps Nov 10, 2022
e9c2155
Apply suggestions from code review
DanSheps Nov 10, 2022
1bac964
Add status field to table.
DanSheps Nov 10, 2022
3a39635
Re-order table fields.
DanSheps Nov 10, 2022
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
10 changes: 10 additions & 0 deletions netbox/dcim/api/nested_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
'NestedSiteSerializer',
'NestedSiteGroupSerializer',
'NestedVirtualChassisSerializer',
'NestedVirtualDeviceContextSerializer',
]


Expand Down Expand Up @@ -466,3 +467,12 @@ class NestedPowerFeedSerializer(WritableNestedSerializer):
class Meta:
model = models.PowerFeed
fields = ['id', 'url', 'display', 'name', 'cable', '_occupied']


class NestedVirtualDeviceContextSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualdevicecontext-detail')
device = NestedDeviceSerializer()

class Meta:
model = models.VirtualDeviceContext
fields = ['id', 'url', 'display', 'name', 'identifier', 'device']
36 changes: 29 additions & 7 deletions netbox/dcim/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,22 @@ def get_parent_device(self, obj):
return data


class VirtualDeviceContextSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:device-detail')
device = NestedDeviceSerializer()
tenant = NestedTenantSerializer(required=False, allow_null=True, default=None)
primary_ip = NestedIPAddressSerializer(read_only=True)
primary_ip4 = NestedIPAddressSerializer(required=False, allow_null=True)
primary_ip6 = NestedIPAddressSerializer(required=False, allow_null=True)

class Meta:
model = VirtualDeviceContext
fields = [
'id', 'url', 'display', 'name', 'device', 'identifier', 'tenant', 'primary_ip', 'primary_ip4',
'primary_ip6', 'status', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
]


class ModuleSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:module-detail')
device = NestedDeviceSerializer()
Expand Down Expand Up @@ -823,6 +839,12 @@ class Meta:
class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, ConnectedEndpointsSerializer):
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:interface-detail')
device = NestedDeviceSerializer()
vdcs = SerializedPKRelatedField(
queryset=VirtualDeviceContext.objects.all(),
serializer=NestedVirtualDeviceContextSerializer,
required=False,
many=True
)
module = ComponentNestedModuleSerializer(
required=False,
allow_null=True
Expand Down Expand Up @@ -859,13 +881,13 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
class Meta:
model = Interface
fields = [
'id', 'url', 'display', 'device', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge', 'lag',
'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel',
'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan',
'tagged_vlans', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers', 'link_peers_type',
'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints', 'connected_endpoints_type',
'connected_endpoints_reachable', 'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses',
'count_fhrp_groups', '_occupied',
'id', 'url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'enabled', 'parent', 'bridge',
'lag', 'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description', 'mode', 'rf_role',
'rf_channel', 'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
'untagged_vlan', 'tagged_vlans', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers',
'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
]

def validate(self, data):
Expand Down
1 change: 1 addition & 0 deletions netbox/dcim/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
router.register('device-roles', views.DeviceRoleViewSet)
router.register('platforms', views.PlatformViewSet)
router.register('devices', views.DeviceViewSet)
router.register('vdcs', views.VirtualDeviceContextViewSet)
router.register('modules', views.ModuleViewSet)

# Device components
Expand Down
8 changes: 8 additions & 0 deletions netbox/dcim/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,14 @@ def napalm(self, request, pk):
return Response(response)


class VirtualDeviceContextViewSet(NetBoxModelViewSet):
queryset = VirtualDeviceContext.objects.prefetch_related(
'device__device_type', 'device', 'tenant', 'tags',
)
serializer_class = serializers.VirtualDeviceContextSerializer
filterset_class = filtersets.VirtualDeviceContextFilterSet


class ModuleViewSet(NetBoxModelViewSet):
queryset = Module.objects.prefetch_related(
'device', 'module_bay', 'module_type__manufacturer', 'tags',
Expand Down
17 changes: 17 additions & 0 deletions netbox/dcim/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -1399,3 +1399,20 @@ class PowerFeedPhaseChoices(ChoiceSet):
(PHASE_SINGLE, 'Single phase'),
(PHASE_3PHASE, 'Three-phase'),
)


#
# VDC
#
class VirtualDeviceContextStatusChoices(ChoiceSet):
key = 'VirtualDeviceContext.status'

STATUS_PLANNED = 'planned'
STATUS_ACTIVE = 'active'
STATUS_OFFLINE = 'offline'

CHOICES = [
(STATUS_PLANNED, 'Planned', 'cyan'),
(STATUS_ACTIVE, 'Active', 'green'),
(STATUS_OFFLINE, 'Offline', 'red'),
]
58 changes: 57 additions & 1 deletion netbox/dcim/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
'SiteFilterSet',
'SiteGroupFilterSet',
'VirtualChassisFilterSet',
'VirtualDeviceContextFilterSet',
)


Expand Down Expand Up @@ -482,7 +483,7 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet):
class Meta:
model = DeviceType
fields = [
'id', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit'
'id', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'airflow', 'weight', 'weight_unit',
]

def search(self, queryset, name, value):
Expand Down Expand Up @@ -1009,6 +1010,44 @@ def _device_bays(self, queryset, name, value):
return queryset.exclude(devicebays__isnull=value)


class VirtualDeviceContextFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
device_id = django_filters.ModelMultipleChoiceFilter(
field_name='device',
queryset=Device.objects.all(),
label='VDC (ID)',
)
device = django_filters.ModelMultipleChoiceFilter(
field_name='device',
queryset=Device.objects.all(),
label='Device model',
)
status = django_filters.MultipleChoiceFilter(
choices=VirtualDeviceContextStatusChoices
)
has_primary_ip = django_filters.BooleanFilter(
method='_has_primary_ip',
label='Has a primary IP',
)

class Meta:
model = VirtualDeviceContext
fields = ['id', 'device', 'name', ]

def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(name__icontains=value) |
Q(identifier=value.strip())
).distinct()

def _has_primary_ip(self, queryset, name, value):
params = Q(primary_ip4__isnull=False) | Q(primary_ip6__isnull=False)
if value:
return queryset.filter(params)
return queryset.exclude(params)


class ModuleFilterSet(NetBoxModelFilterSet):
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
field_name='module_type__manufacturer',
Expand Down Expand Up @@ -1342,6 +1381,23 @@ class InterfaceFilterSet(
to_field_name='rd',
label='VRF (RD)',
)
vdc_id = django_filters.ModelMultipleChoiceFilter(
field_name='vdcs',
queryset=VirtualDeviceContext.objects.all(),
label='Virtual Device Context',
)
vdc_identifier = django_filters.ModelMultipleChoiceFilter(
field_name='vdcs__identifier',
queryset=VirtualDeviceContext.objects.all(),
to_field_name='identifier',
label='Virtual Device Context (Identifier)',
)
vdc = django_filters.ModelMultipleChoiceFilter(
field_name='vdcs__name',
queryset=VirtualDeviceContext.objects.all(),
to_field_name='name',
label='Virtual Device Context',
)

class Meta:
model = Interface
Expand Down
18 changes: 18 additions & 0 deletions netbox/dcim/forms/bulk_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
'SiteBulkEditForm',
'SiteGroupBulkEditForm',
'VirtualChassisBulkEditForm',
'VirtualDeviceContextBulkEditForm'
)


Expand Down Expand Up @@ -1398,3 +1399,20 @@ class InventoryItemRoleBulkEditForm(NetBoxModelBulkEditForm):
(None, ('color', 'description')),
)
nullable_fields = ('color', 'description')


class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False
)
status = forms.ChoiceField(
required=False,
choices=add_blank_choice(VirtualDeviceContextStatusChoices),
widget=StaticSelect()
)
model = VirtualDeviceContext
fieldsets = (
(None, ('device', 'status', 'tenant')),
DanSheps marked this conversation as resolved.
Show resolved Hide resolved
)
nullable_fields = ('device', )
23 changes: 23 additions & 0 deletions netbox/dcim/forms/bulk_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
'SiteCSVForm',
'SiteGroupCSVForm',
'VirtualChassisCSVForm',
'VirtualDeviceContextCSVForm'
)


Expand Down Expand Up @@ -1084,3 +1085,25 @@ def __init__(self, data=None, *args, **kwargs):
f"location__{self.fields['location'].to_field_name}": data.get('location'),
}
self.fields['rack'].queryset = self.fields['rack'].queryset.filter(**params)


class VirtualDeviceContextCSVForm(NetBoxModelCSVForm):

device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name',
help_text='Assigned role'
)
tenant = CSVModelChoiceField(
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text='Assigned tenant'
)

class Meta:
fields = [
'name', 'device', 'status', 'tenant', 'identifier', 'comments',
]
model = VirtualDeviceContext
help_texts = {}
43 changes: 42 additions & 1 deletion netbox/dcim/forms/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
'SiteFilterForm',
'SiteGroupFilterForm',
'VirtualChassisFilterForm',
'VirtualDeviceContextFilterForm'
)


Expand Down Expand Up @@ -728,6 +729,37 @@ class DeviceFilterForm(
tag = TagFilterField(model)


class VirtualDeviceContextFilterForm(
TenancyFilterForm,
NetBoxModelFilterSetForm
):
model = VirtualDeviceContext
fieldsets = (
DanSheps marked this conversation as resolved.
Show resolved Hide resolved
(None, ('q', 'filter', 'tag')),
('Hardware', ('device', 'status', )),
('Tenant', ('tenant_group_id', 'tenant_id')),
('Miscellaneous', ('has_primary_ip',))
)
device = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
label=_('Device'),
fetch_trigger='open'
)
status = MultipleChoiceField(
required=False,
choices=add_blank_choice(VirtualDeviceContextStatusChoices)
)
has_primary_ip = forms.NullBooleanField(
required=False,
label='Has a primary IP',
widget=StaticSelect(
choices=BOOLEAN_WITH_BLANK_CHOICES
)
)
tag = TagFilterField(model)


class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxModelFilterSetForm):
model = Module
fieldsets = (
Expand Down Expand Up @@ -1075,9 +1107,18 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
('Addressing', ('vrf_id', 'mac_address', 'wwn')),
('PoE', ('poe_mode', 'poe_type')),
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id',
'device_id', 'vdc_id')),
('Connection', ('cabled', 'connected', 'occupied')),
)
vdc_id = DynamicModelMultipleChoiceField(
queryset=VirtualDeviceContext.objects.all(),
required=False,
query_params={
'device_id': '$device_id',
},
label=_('Virtual Device Context')
)
kind = MultipleChoiceField(
choices=InterfaceKindChoices,
required=False
Expand Down
Loading