Closes #1865: Add console port and console server port types

This commit is contained in:
Jeremy Stretch 2019-10-30 14:25:55 -04:00
parent c4dd76a748
commit bb30f64252
13 changed files with 253 additions and 24 deletions

View File

@ -85,11 +85,16 @@ Full connection details are required in both sections, even if they are the same
## Enhancements
* [#1865](https://github.com/digitalocean/netbox/issues/1865) - Add console port and console server port types
* [#2902](https://github.com/digitalocean/netbox/issues/2902) - Replace supervisord with systemd
* [#3455](https://github.com/digitalocean/netbox/issues/3455) - Add tenant assignment to cluster
* [#3538](https://github.com/digitalocean/netbox/issues/3538) -
## API Changes
* Introduced `/api/extras/scripts/` endpoint for retreiving and executing custom scripts
* Introduced `/api/extras/scripts/` endpoint for retrieving and executing custom scripts
* dcim.ConsolePort: Added field `type`
* dcim.ConsolePortTemplate: Added field `type`
* dcim.ConsoleServerPort: Added field `type`
* dcim.ConsoleServerPortTemplate: Added field `type`
* virtualization.Cluster: Added field `tenant`

View File

@ -200,18 +200,20 @@ class DeviceTypeSerializer(TaggitSerializer, CustomFieldModelSerializer):
class ConsolePortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
class Meta:
model = ConsolePortTemplate
fields = ['id', 'device_type', 'name']
fields = ['id', 'device_type', 'name', 'type']
class ConsoleServerPortTemplateSerializer(ValidatedModelSerializer):
device_type = NestedDeviceTypeSerializer()
type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
class Meta:
model = ConsoleServerPortTemplate
fields = ['id', 'device_type', 'name']
fields = ['id', 'device_type', 'name', 'type']
class PowerPortTemplateSerializer(ValidatedModelSerializer):
@ -370,27 +372,29 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
class ConsoleServerPortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = ConsoleServerPort
fields = [
'id', 'device', 'name', 'description', 'connected_endpoint_type', 'connected_endpoint', 'connection_status',
'cable', 'tags',
'id', 'device', 'name', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
'connection_status', 'cable', 'tags',
]
class ConsolePortSerializer(TaggitSerializer, ConnectedEndpointSerializer):
device = NestedDeviceSerializer()
type = ChoiceField(choices=CONSOLE_TYPE_CHOICES, required=False)
cable = NestedCableSerializer(read_only=True)
tags = TagListSerializerField(required=False)
class Meta:
model = ConsolePort
fields = [
'id', 'device', 'name', 'description', 'connected_endpoint_type', 'connected_endpoint', 'connection_status',
'cable', 'tags',
'id', 'device', 'name', 'type', 'description', 'connected_endpoint_type', 'connected_endpoint',
'connection_status', 'cable', 'tags',
]

View File

@ -42,7 +42,10 @@ from .exceptions import MissingFilterException
class DCIMFieldChoicesViewSet(FieldChoicesViewSet):
fields = (
(Cable, ['length_unit', 'status', 'termination_a_type', 'termination_b_type', 'type']),
(ConsolePort, ['connection_status']),
(ConsolePort, ['type', 'connection_status']),
(ConsolePortTemplate, ['type']),
(ConsoleServerPort, ['type']),
(ConsoleServerPortTemplate, ['type']),
(Device, ['face', 'status']),
(DeviceType, ['subdevice_role']),
(FrontPort, ['type']),

View File

@ -57,6 +57,41 @@ SUBDEVICE_ROLE_CHOICES = (
(SUBDEVICE_ROLE_CHILD, 'Child'),
)
#
# Numeric console port types
#
CONSOLE_TYPE_DE9 = 1000
CONSOLE_TYPE_DB25 = 1100
CONSOLE_TYPE_RJ45 = 2000
CONSOLE_TYPE_USB_A = 3000
CONSOLE_TYPE_USB_B = 3010
CONSOLE_TYPE_USB_C = 3020
CONSOLE_TYPE_USB_MINI_A = 3100
CONSOLE_TYPE_USB_MINI_B = 3110
CONSOLE_TYPE_USB_MICRO_A = 3200
CONSOLE_TYPE_USB_MICRO_B = 3210
CONSOLE_TYPE_OTHER = 32767
CONSOLE_TYPE_CHOICES = [
['Serial', [
[CONSOLE_TYPE_DE9, 'DE-9'],
[CONSOLE_TYPE_DB25, 'DB-25'],
[CONSOLE_TYPE_RJ45, 'RJ-45'],
]],
['USB', [
[CONSOLE_TYPE_USB_A, 'USB Type A'],
[CONSOLE_TYPE_USB_B, 'USB Type B'],
[CONSOLE_TYPE_USB_C, 'USB Type C'],
[CONSOLE_TYPE_USB_MINI_A, 'USB Mini A'],
[CONSOLE_TYPE_USB_MINI_B, 'USB Mini B'],
[CONSOLE_TYPE_USB_MICRO_A, 'USB Micro A'],
[CONSOLE_TYPE_USB_MICRO_B, 'USB Micro B'],
]],
['Other', [
[CONSOLE_TYPE_OTHER, 'Other'],
]],
]
#
# Numeric interface types
#
@ -515,6 +550,67 @@ POWERFEED_LEG_CHOICES = (
)
#
# Console port type values
#
class ConsolePortTypes:
"""
ConsolePort/ConsoleServerPort.type slugs
"""
TYPE_DE9 = 'de-9'
TYPE_DB25 = 'db-25'
TYPE_RJ45 = 'rj-45'
TYPE_USB_A = 'usb-a'
TYPE_USB_B = 'usb-b'
TYPE_USB_C = 'usb-c'
TYPE_USB_MINI_A = 'usb-mini-a'
TYPE_USB_MINI_B = 'usb-mini-b'
TYPE_USB_MICRO_A = 'usb-micro-a'
TYPE_USB_MICRO_B = 'usb-micro-b'
TYPE_OTHER = 'other'
TYPE_CHOICES = (
('Serial', (
(TYPE_DE9, 'DE-9'),
(TYPE_DB25, 'DB-25'),
(TYPE_RJ45, 'RJ-45'),
)),
('USB', (
(TYPE_USB_A, 'USB Type A'),
(TYPE_USB_B, 'USB Type B'),
(TYPE_USB_C, 'USB Type C'),
(TYPE_USB_MINI_A, 'USB Mini A'),
(TYPE_USB_MINI_B, 'USB Mini B'),
(TYPE_USB_MICRO_A, 'USB Micro A'),
(TYPE_USB_MICRO_B, 'USB Micro B'),
)),
('Other', (
(TYPE_OTHER, 'Other'),
)),
)
@classmethod
def slug_to_integer(cls, slug):
"""
Provide backward-compatible mapping of the type slug to integer.
"""
return {
# Slug: integer
cls.TYPE_DE9: CONSOLE_TYPE_DE9,
cls.TYPE_DB25: CONSOLE_TYPE_DB25,
cls.TYPE_RJ45: CONSOLE_TYPE_RJ45,
cls.TYPE_USB_A: CONSOLE_TYPE_USB_A,
cls.TYPE_USB_B: CONSOLE_TYPE_USB_B,
cls.TYPE_USB_C: CONSOLE_TYPE_USB_C,
cls.TYPE_USB_MINI_A: CONSOLE_TYPE_USB_MINI_A,
cls.TYPE_USB_MINI_B: CONSOLE_TYPE_USB_MINI_B,
cls.TYPE_USB_MICRO_A: CONSOLE_TYPE_USB_MICRO_A,
cls.TYPE_USB_MICRO_B: CONSOLE_TYPE_USB_MICRO_B,
cls.TYPE_OTHER: CONSOLE_TYPE_OTHER,
}.get(slug)
#
# Interface type values
#

View File

@ -346,14 +346,14 @@ class ConsolePortTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = ConsolePortTemplate
fields = ['id', 'name']
fields = ['id', 'name', 'type']
class ConsoleServerPortTemplateFilter(DeviceTypeComponentFilterSet):
class Meta:
model = ConsoleServerPortTemplate
fields = ['id', 'name']
fields = ['id', 'name', 'type']
class PowerPortTemplateFilter(DeviceTypeComponentFilterSet):
@ -641,6 +641,10 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
class ConsolePortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=CONSOLE_TYPE_CHOICES,
null_value=None
)
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
@ -649,10 +653,14 @@ class ConsolePortFilter(DeviceComponentFilterSet):
class Meta:
model = ConsolePort
fields = ['id', 'name', 'description', 'connection_status']
fields = ['id', 'name', 'type', 'description', 'connection_status']
class ConsoleServerPortFilter(DeviceComponentFilterSet):
type = django_filters.MultipleChoiceFilter(
choices=CONSOLE_TYPE_CHOICES,
null_value=None
)
cabled = django_filters.BooleanFilter(
field_name='cable',
lookup_expr='isnull',
@ -661,7 +669,7 @@ class ConsoleServerPortFilter(DeviceComponentFilterSet):
class Meta:
model = ConsoleServerPort
fields = ['id', 'name', 'description', 'connection_status']
fields = ['id', 'name', 'type', 'description', 'connection_status']
class PowerPortFilter(DeviceComponentFilterSet):

View File

@ -941,7 +941,7 @@ class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ConsolePortTemplate
fields = [
'device_type', 'name',
'device_type', 'name', 'type',
]
widgets = {
'device_type': forms.HiddenInput(),
@ -952,6 +952,10 @@ class ConsolePortTemplateCreateForm(ComponentForm):
name_pattern = ExpandableNameField(
label='Name'
)
type = forms.ChoiceField(
choices=CONSOLE_TYPE_CHOICES,
widget=StaticSelect2()
)
class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
@ -959,7 +963,7 @@ class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ConsoleServerPortTemplate
fields = [
'device_type', 'name',
'device_type', 'name', 'type',
]
widgets = {
'device_type': forms.HiddenInput(),
@ -970,6 +974,10 @@ class ConsoleServerPortTemplateCreateForm(ComponentForm):
name_pattern = ExpandableNameField(
label='Name'
)
type = forms.ChoiceField(
choices=CONSOLE_TYPE_CHOICES,
widget=StaticSelect2()
)
class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
@ -1248,22 +1256,38 @@ class ComponentTemplateImportForm(BootstrapMixin, forms.ModelForm):
class ConsolePortTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField(
choices=ConsolePortTypes.TYPE_CHOICES
)
class Meta:
model = ConsolePortTemplate
fields = [
'device_type', 'name',
'device_type', 'name', 'type',
]
def clean_type(self):
# Convert slug value to field integer value
slug = self.cleaned_data['type']
return ConsolePortTypes.slug_to_integer(slug)
class ConsoleServerPortTemplateImportForm(ComponentTemplateImportForm):
type = forms.ChoiceField(
choices=ConsolePortTypes.TYPE_CHOICES
)
class Meta:
model = ConsoleServerPortTemplate
fields = [
'device_type', 'name',
'device_type', 'name', 'type',
]
def clean_type(self):
# Convert slug value to field integer value
slug = self.cleaned_data['type']
return ConsolePortTypes.slug_to_integer(slug)
class PowerPortTemplateImportForm(ComponentTemplateImportForm):
@ -2054,7 +2078,7 @@ class ConsolePortForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ConsolePort
fields = [
'device', 'name', 'description', 'tags',
'device', 'name', 'type', 'description', 'tags',
]
widgets = {
'device': forms.HiddenInput(),
@ -2065,6 +2089,10 @@ class ConsolePortCreateForm(ComponentForm):
name_pattern = ExpandableNameField(
label='Name'
)
type = forms.ChoiceField(
choices=CONSOLE_TYPE_CHOICES,
widget=StaticSelect2(),
)
description = forms.CharField(
max_length=100,
required=False
@ -2086,7 +2114,7 @@ class ConsoleServerPortForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = ConsoleServerPort
fields = [
'device', 'name', 'description', 'tags',
'device', 'name', 'type', 'description', 'tags',
]
widgets = {
'device': forms.HiddenInput(),
@ -2097,6 +2125,10 @@ class ConsoleServerPortCreateForm(ComponentForm):
name_pattern = ExpandableNameField(
label='Name'
)
type = forms.ChoiceField(
choices=CONSOLE_TYPE_CHOICES,
widget=StaticSelect2(),
)
description = forms.CharField(
max_length=100,
required=False
@ -2111,6 +2143,11 @@ class ConsoleServerPortBulkEditForm(BootstrapMixin, AddRemoveTagsForm, BulkEditF
queryset=ConsoleServerPort.objects.all(),
widget=forms.MultipleHiddenInput()
)
type = forms.ChoiceField(
choices=add_blank_choice(CONSOLE_TYPE_CHOICES),
required=False,
widget=StaticSelect2()
)
description = forms.CharField(
max_length=100,
required=False

View File

@ -0,0 +1,33 @@
# Generated by Django 2.2.6 on 2019-10-30 17:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0075_cable_devices'),
]
operations = [
migrations.AddField(
model_name='consoleport',
name='type',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='consoleporttemplate',
name='type',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='consoleserverport',
name='type',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='consoleserverporttemplate',
name='type',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
]

View File

@ -1014,6 +1014,11 @@ class ConsolePortTemplate(ComponentTemplateModel):
name = models.CharField(
max_length=50
)
type = models.PositiveSmallIntegerField(
choices=CONSOLE_TYPE_CHOICES,
blank=True,
null=True
)
objects = NaturalOrderingManager()
@ -1027,7 +1032,8 @@ class ConsolePortTemplate(ComponentTemplateModel):
def instantiate(self, device):
return ConsolePort(
device=device,
name=self.name
name=self.name,
type=self.type
)
@ -1043,6 +1049,11 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
name = models.CharField(
max_length=50
)
type = models.PositiveSmallIntegerField(
choices=CONSOLE_TYPE_CHOICES,
blank=True,
null=True
)
objects = NaturalOrderingManager()
@ -1056,7 +1067,8 @@ class ConsoleServerPortTemplate(ComponentTemplateModel):
def instantiate(self, device):
return ConsoleServerPort(
device=device,
name=self.name
name=self.name,
type=self.type
)
@ -1846,6 +1858,11 @@ class ConsolePort(CableTermination, ComponentModel):
name = models.CharField(
max_length=50
)
type = models.PositiveSmallIntegerField(
choices=CONSOLE_TYPE_CHOICES,
blank=True,
null=True
)
connected_endpoint = models.OneToOneField(
to='dcim.ConsoleServerPort',
on_delete=models.SET_NULL,
@ -1861,7 +1878,7 @@ class ConsolePort(CableTermination, ComponentModel):
objects = NaturalOrderingManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'description']
csv_headers = ['device', 'name', 'type', 'description']
class Meta:
ordering = ['device', 'name']
@ -1877,6 +1894,7 @@ class ConsolePort(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.type,
self.description,
)
@ -1897,6 +1915,11 @@ class ConsoleServerPort(CableTermination, ComponentModel):
name = models.CharField(
max_length=50
)
type = models.PositiveSmallIntegerField(
choices=CONSOLE_TYPE_CHOICES,
blank=True,
null=True
)
connection_status = models.NullBooleanField(
choices=CONNECTION_STATUS_CHOICES,
blank=True
@ -1905,7 +1928,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
objects = NaturalOrderingManager()
tags = TaggableManager(through=TaggedItem)
csv_headers = ['device', 'name', 'description']
csv_headers = ['device', 'name', 'type', 'description']
class Meta:
unique_together = ['device', 'name']
@ -1920,6 +1943,7 @@ class ConsoleServerPort(CableTermination, ComponentModel):
return (
self.device.identifier,
self.name,
self.type,
self.description,
)

View File

@ -422,7 +422,7 @@ class ConsolePortTemplateTable(BaseTable):
class Meta(BaseTable.Meta):
model = ConsolePortTemplate
fields = ('pk', 'name', 'actions')
fields = ('pk', 'name', 'type', 'actions')
empty_text = "None"
@ -647,7 +647,7 @@ class ConsolePortTable(BaseTable):
class Meta(BaseTable.Meta):
model = ConsolePort
fields = ('name',)
fields = ('name', 'type')
class ConsoleServerPortTable(BaseTable):

View File

@ -231,12 +231,18 @@ slug: test-1000
u_height: 2
console-ports:
- name: Console Port 1
type: de-9
- name: Console Port 2
type: de-9
- name: Console Port 3
type: de-9
console-server-ports:
- name: Console Server Port 1
type: rj-45
- name: Console Server Port 2
type: rj-45
- name: Console Server Port 3
type: rj-45
power-ports:
- name: Power Port 1
- name: Power Port 2
@ -313,10 +319,12 @@ device-bays:
self.assertEqual(dt.consoleport_templates.count(), 3)
cp1 = ConsolePortTemplate.objects.first()
self.assertEqual(cp1.name, 'Console Port 1')
self.assertEqual(cp1.type, CONSOLE_TYPE_DE9)
self.assertEqual(dt.consoleserverport_templates.count(), 3)
csp1 = ConsoleServerPortTemplate.objects.first()
self.assertEqual(csp1.name, 'Console Server Port 1')
self.assertEqual(csp1.type, CONSOLE_TYPE_RJ45)
self.assertEqual(dt.powerport_templates.count(), 3)
pp1 = PowerPortTemplate.objects.first()

View File

@ -628,6 +628,7 @@
<th class="pk"><input type="checkbox" class="toggle" title="Toggle all" /></th>
{% endif %}
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Cable</th>
<th colspan="2">Connection</th>

View File

@ -6,6 +6,11 @@
</td>
<td></td>
{# Type #}
<td>
{% if cp.type %}{{ cp.get_type_display }}{% else %}&mdash;{% endif %}
</td>
{# Description #}
<td>
{{ cp.description }}

View File

@ -14,6 +14,11 @@
<i class="fa fa-fw fa-keyboard-o"></i> {{ csp }}
</td>
{# Type #}
<td>
{% if csp.type %}{{ csp.get_type_display }}{% else %}&mdash;{% endif %}
</td>
{# Description #}
<td>
{{ csp.description|placeholder }}