mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-28 03:16:25 -06:00
Move ServicePort mode definition to dcim
* add service port specific permissions
This commit is contained in:
parent
8360402e0a
commit
f2fef00ed8
@ -1,12 +1,12 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ipam.models import IPAddress, ServicePort
|
||||
from ipam.models import IPAddress
|
||||
from dcim.models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceType,
|
||||
DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Module, Platform, PowerOutlet,
|
||||
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackRole, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
|
||||
SUBDEVICE_ROLE_CHILD, SUBDEVICE_ROLE_PARENT,
|
||||
)
|
||||
ServicePort)
|
||||
from extras.api.serializers import CustomFieldSerializer
|
||||
from tenancy.api.serializers import TenantNestedSerializer
|
||||
|
||||
|
@ -12,11 +12,10 @@ from django.shortcuts import get_object_or_404
|
||||
from dcim.models import (
|
||||
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, DeviceType, IFACE_FF_VIRTUAL, Interface,
|
||||
InterfaceConnection, Manufacturer, Module, Platform, PowerOutlet, PowerPort, Rack, RackGroup, RackRole, Site,
|
||||
)
|
||||
ServicePort)
|
||||
from dcim import filters
|
||||
from extras.api.views import CustomFieldModelAPIView
|
||||
from extras.api.renderers import BINDZoneRenderer, FlatJSONRenderer
|
||||
from ipam.models import ServicePort
|
||||
from utilities.api import ServiceUnavailable
|
||||
from .exceptions import MissingFilterException
|
||||
from . import serializers
|
||||
|
@ -5,7 +5,7 @@ from django.core.exceptions import ValidationError
|
||||
from django.db.models import Count, Q
|
||||
|
||||
from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||
from ipam.models import IPAddress, ServicePort
|
||||
from ipam.models import IPAddress
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelect, add_blank_choice, BootstrapMixin, BulkEditForm, BulkImportForm, CommentField, CSVDataField,
|
||||
@ -18,7 +18,7 @@ from .models import (
|
||||
ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceRole, DeviceType,
|
||||
Interface, IFACE_FF_CHOICES, IFACE_FF_VIRTUAL, InterfaceConnection, InterfaceTemplate, Manufacturer, Module,
|
||||
Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES,
|
||||
Rack, RackGroup, RackRole, Site, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
|
||||
Rack, RackGroup, RackRole, Site, ServicePort, STATUS_CHOICES, SUBDEVICE_ROLE_CHILD
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-11-22 22:05
|
||||
# Generated by Django 1.10 on 2016-11-25 23:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
@ -9,8 +9,8 @@ import django.db.models.deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0021_add_ff_flexstack'),
|
||||
('ipam', '0010_ipaddress_help_texts'),
|
||||
('dcim', '0021_add_ff_flexstack'),
|
||||
]
|
||||
|
||||
operations = [
|
@ -203,6 +203,12 @@ RPC_CLIENT_CHOICES = [
|
||||
]
|
||||
|
||||
|
||||
SERVICE_PORT_CHOICES = (
|
||||
(6, 'TCP'),
|
||||
(17, 'UDP'),
|
||||
)
|
||||
|
||||
|
||||
def order_interfaces(queryset, sql_col, primary_ordering=tuple()):
|
||||
"""
|
||||
Attempt to match interface names by their slot/position identifiers and order according. Matching is done using the
|
||||
@ -1267,3 +1273,67 @@ class Module(models.Model):
|
||||
|
||||
def get_parent_url(self):
|
||||
return reverse('dcim:device_inventory', args=[self.device.pk])
|
||||
|
||||
|
||||
class ServicePort(CreatedUpdatedModel):
|
||||
"""
|
||||
A ServicePort represents a port on a specific IPAddress on which a service is running.
|
||||
The port can be one of 2 predefined protocols - TCP or UDP.
|
||||
A ServicePort is always associated with a specific IPAddress on a Device.
|
||||
|
||||
The combination of IPAddress, Port Number and Port Protocol is always unique for ServicePort.
|
||||
|
||||
If a port number + port protocol combination is already assigned to no specific IPAddress
|
||||
that means it is assigned on all IPs on the device
|
||||
"""
|
||||
|
||||
device = models.ForeignKey('Device', related_name='service_ports', on_delete=models.CASCADE,
|
||||
blank=False, null=False, verbose_name='device')
|
||||
|
||||
ip_address = models.ForeignKey('ipam.IPAddress', related_name='service_ports', on_delete=models.CASCADE,
|
||||
blank=True, null=True, verbose_name='ip_address')
|
||||
protocol = models.PositiveSmallIntegerField(choices=SERVICE_PORT_CHOICES, default=0)
|
||||
|
||||
port = models.PositiveIntegerField()
|
||||
name = models.CharField(max_length=30, blank=False, null=False)
|
||||
description = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'ip_address', 'port']
|
||||
verbose_name = 'Service Port'
|
||||
verbose_name_plural = 'Service Ports'
|
||||
unique_together = ['device', 'ip_address', 'port', 'protocol']
|
||||
|
||||
def __unicode__(self):
|
||||
port_protocol = dict(SERVICE_PORT_CHOICES).get(self.protocol)
|
||||
return u'{}/{}'.format(self.port, port_protocol)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:serviceport', args=[self.pk])
|
||||
|
||||
@property
|
||||
def short_description(self):
|
||||
if self.description:
|
||||
return self.description[:30]
|
||||
return None
|
||||
|
||||
def clean(self):
|
||||
# if port is already assigned to no specific IPAddress
|
||||
# that means it is assigned on all IPs on the device
|
||||
port_assigned_on_all_ips = bool(ServicePort.objects.filter(
|
||||
ip_address__address=None, port=self.port, protocol=self.protocol).exclude(pk=self.id))
|
||||
if port_assigned_on_all_ips:
|
||||
raise ValidationError(
|
||||
'Port already assigned all IPAddresses for this device')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(ServicePort, self).save(*args, **kwargs)
|
||||
|
||||
def to_csv(self):
|
||||
return ','.join([
|
||||
str(self.device_id),
|
||||
self.ip_address,
|
||||
self.port,
|
||||
self.name,
|
||||
self.description,
|
||||
])
|
||||
|
@ -8,14 +8,13 @@ from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.http import urlencode
|
||||
from django.views.generic import View
|
||||
|
||||
from ipam.models import Prefix, IPAddress, VLAN, ServicePort
|
||||
from ipam.models import Prefix, IPAddress, VLAN
|
||||
from circuits.models import Circuit
|
||||
from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
||||
from utilities.forms import ConfirmationForm
|
||||
@ -28,7 +27,7 @@ from .models import (
|
||||
CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
|
||||
DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
|
||||
Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
|
||||
RackRole, Site,
|
||||
RackRole, ServicePort, Site,
|
||||
)
|
||||
|
||||
|
||||
@ -1614,7 +1613,7 @@ def serviceport(request, pk):
|
||||
|
||||
|
||||
class ServicePortEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
permission_required = 'ipam.change_ipaddress'
|
||||
permission_required = 'dcim.change_serviceport'
|
||||
model = ServicePort
|
||||
form_class = forms.ServiceEditForm
|
||||
fields_initial = ['ip_address', 'port' 'protocol', 'name', 'description']
|
||||
@ -1630,7 +1629,7 @@ class ServicePortEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
|
||||
|
||||
class ServicePortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
permission_required = 'ipam.delete_ipaddress'
|
||||
permission_required = 'dcim.delete_serviceport'
|
||||
model = ServicePort
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@ -1640,7 +1639,7 @@ class ServicePortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
return super(ServicePortDeleteView, self).post(request, *args, **kwargs)
|
||||
|
||||
|
||||
@permission_required('ipam.add_ipaddress')
|
||||
@permission_required(['dcim.change_device', 'dcim.add_serviceport'])
|
||||
def serviceport_add(request, pk):
|
||||
device = get_object_or_404(Device, pk=pk)
|
||||
|
||||
|
@ -13,20 +13,13 @@ from extras.models import CustomFieldModel, CustomFieldValue
|
||||
from tenancy.models import Tenant
|
||||
from utilities.models import CreatedUpdatedModel
|
||||
from utilities.sql import NullsFirstQuerySet
|
||||
|
||||
from .fields import IPNetworkField, IPAddressField
|
||||
|
||||
|
||||
AF_CHOICES = (
|
||||
(4, 'IPv4'),
|
||||
(6, 'IPv6'),
|
||||
)
|
||||
|
||||
SERVICE_PORT_CHOICES = (
|
||||
(6, 'TCP'),
|
||||
(17, 'UDP'),
|
||||
)
|
||||
|
||||
PREFIX_STATUS_CONTAINER = 0
|
||||
PREFIX_STATUS_ACTIVE = 1
|
||||
PREFIX_STATUS_RESERVED = 2
|
||||
@ -441,76 +434,6 @@ class IPAddress(CreatedUpdatedModel, CustomFieldModel):
|
||||
return STATUS_CHOICE_CLASSES[self.status]
|
||||
|
||||
|
||||
class ServicePort(CreatedUpdatedModel):
|
||||
"""
|
||||
A ServicePort represents a port on a specific IPAddress on which a service is running.
|
||||
The port can be one of 2 predefined protocols - TCP or UDP.
|
||||
A ServicePort is always associated with a specific IPAddress on a Device.
|
||||
|
||||
If an user wants to specify a service running on all IP Addresses on a device,
|
||||
this can be done by assigning the port to the '0.0.0.0/32' or '::/128' IPAddress.
|
||||
|
||||
The combination of IPAddress, Port Number and Port Protocol is always unique for ServicePort.
|
||||
|
||||
If a port number + port protocol combination is assigned to '0.0.0.0/32' or '::/128' IPAddress,
|
||||
it cannot be assigned to any other IPAddress on the same Device.
|
||||
"""
|
||||
|
||||
device = models.ForeignKey('dcim.Device', related_name='service_ports', on_delete=models.CASCADE,
|
||||
blank=False, null=False, verbose_name='device')
|
||||
|
||||
ip_address = models.ForeignKey('IPAddress', related_name='service_ports', on_delete=models.CASCADE,
|
||||
blank=True, null=True, verbose_name='ip_address')
|
||||
protocol = models.PositiveSmallIntegerField(choices=SERVICE_PORT_CHOICES, default=0)
|
||||
|
||||
port = models.PositiveIntegerField()
|
||||
name = models.CharField(max_length=30, blank=False, null=False)
|
||||
description = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'ip_address', 'port']
|
||||
verbose_name = 'Service Port'
|
||||
verbose_name_plural = 'Service Ports'
|
||||
unique_together = ['device', 'ip_address', 'port', 'protocol']
|
||||
|
||||
def __unicode__(self):
|
||||
port_protocol = dict(SERVICE_PORT_CHOICES).get(self.protocol)
|
||||
return u'{}/{}'.format(self.port, port_protocol)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dcim:serviceport', args=[self.pk])
|
||||
|
||||
@property
|
||||
def short_description(self):
|
||||
if self.description:
|
||||
return self.description[:30]
|
||||
return None
|
||||
|
||||
def clean(self):
|
||||
# if port is already assigned on '0.0.0.0/32' or '::/128'
|
||||
# that means it is assigned on all IPs on the device
|
||||
port_assigned_on_all_ips = bool(ServicePort.objects.filter(
|
||||
ip_address__address='::/128', port=self.port, protocol=self.protocol).exclude(pk=self.id))
|
||||
port_assigned_on_all_v4_ips = bool(ServicePort.objects.filter(
|
||||
ip_address__address='0.0.0.0/32', port=self.port, protocol=self.protocol).exclude(pk=self.id))
|
||||
if port_assigned_on_all_ips:
|
||||
raise ValidationError('Port already assigned on address ::/128')
|
||||
elif port_assigned_on_all_v4_ips and self.ip_address.family == 4:
|
||||
raise ValidationError('Port already assigned on address 0.0.0.0/32')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(ServicePort, self).save(*args, **kwargs)
|
||||
|
||||
def to_csv(self):
|
||||
return ','.join([
|
||||
str(self.device_id),
|
||||
self.ip_address,
|
||||
self.port,
|
||||
self.name,
|
||||
self.description,
|
||||
])
|
||||
|
||||
|
||||
class VLANGroup(models.Model):
|
||||
"""
|
||||
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
||||
|
@ -17,7 +17,7 @@ from utilities.views import (
|
||||
|
||||
from . import filters, forms, tables
|
||||
from .models import (Aggregate, IPAddress, PREFIX_STATUS_ACTIVE, PREFIX_STATUS_DEPRECATED, PREFIX_STATUS_RESERVED,
|
||||
Prefix, RIR, Role, VLAN, VLANGroup, VRF, ServicePort)
|
||||
Prefix, RIR, Role, VLAN, VLANGroup, VRF)
|
||||
|
||||
|
||||
def add_available_prefixes(parent, prefix_list):
|
||||
@ -645,6 +645,7 @@ class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
cls = IPAddress
|
||||
default_redirect_url = 'ipam:ipaddress_list'
|
||||
|
||||
|
||||
#
|
||||
# VLAN groups
|
||||
#
|
||||
|
@ -220,7 +220,7 @@
|
||||
None found
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_ipaddress %}
|
||||
{% if perms.dcim.add_serviceport %}
|
||||
<div class="panel-footer text-right">
|
||||
<a href="{% url 'dcim:serviceport_add' pk=device.pk %}" class="btn btn-xs btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
|
@ -1,6 +1,10 @@
|
||||
<tr>
|
||||
<td>
|
||||
{{ port.ip_address }}
|
||||
{% if port.ip_address %}
|
||||
{{ port.ip_address }}
|
||||
{% else %}
|
||||
All IP addresses
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'dcim:serviceport' pk=port.pk %}"> {{ port }} </a>
|
||||
@ -15,12 +19,12 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if perms.ipam.change_ipaddress %}
|
||||
{% if perms.dcim.change_serviceport%}
|
||||
<a href="{% url 'dcim:serviceport_edit' pk=port.pk %}" class="btn btn-info btn-xs" title="Edit service port">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_ipaddress %}
|
||||
{% if perms.dcim.delete_serviceport %}
|
||||
<a href="{% url 'dcim:serviceport_delete' pk=port.pk %}" class="btn btn-danger btn-xs">
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true" title="Delete port"></i>
|
||||
</a>
|
||||
|
@ -13,13 +13,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.change_ipaddress %}
|
||||
{% if perms.dcim.change_serviceport %}
|
||||
<a href="{% url 'dcim:serviceport_edit' pk=service_port.pk %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this Service Port
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_ipaddress %}
|
||||
{% if perms.dcim.delete_serviceport %}
|
||||
<a href="{% url 'dcim:serviceport_delete' pk=service_port.pk %}" class="btn btn-danger">
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this Service Port
|
||||
|
Loading…
Reference in New Issue
Block a user