diff --git a/netbox/ipam/migrations/0009_add_service_port.py b/netbox/ipam/migrations/0009_add_service_port.py new file mode 100644 index 000000000..4d5a28068 --- /dev/null +++ b/netbox/ipam/migrations/0009_add_service_port.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10 on 2016-10-01 13:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('ipam', '0008_prefix_change_order'), + ] + + operations = [ + migrations.CreateModel( + name='ServicePort', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateField(auto_now_add=True)), + ('last_updated', models.DateTimeField(auto_now=True)), + ('type', models.PositiveSmallIntegerField(choices=[(0, b'TCP'), (1, b'UDP')], default=0)), + ('port', models.PositiveIntegerField()), + ('name', models.CharField(max_length=30)), + ('description', models.TextField(blank=True)), + ('ip_address', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='service_ports', to='ipam.IPAddress', verbose_name=b'ip_address')), + ], + options={ + 'ordering': ['ip_address', 'port'], + 'verbose_name': 'Service Port', + 'verbose_name_plural': 'Service Ports', + }, + ), + migrations.AlterUniqueTogether( + name='serviceport', + unique_together={('ip_address', 'port', 'type')}, + ), + ] diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py index 1538251cf..fb7b4e862 100644 --- a/netbox/ipam/models.py +++ b/netbox/ipam/models.py @@ -22,6 +22,11 @@ AF_CHOICES = ( (6, 'IPv6'), ) +SERVICE_PORT_CHOICES = ( + (0, 'TCP'), + (1, 'UDP'), +) + PREFIX_STATUS_CONTAINER = 0 PREFIX_STATUS_ACTIVE = 1 PREFIX_STATUS_RESERVED = 2 @@ -436,6 +441,62 @@ 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 types - 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/24' IPAddress. + + The combination of IPAddress, Port Number and Port Type is always unique for ServicePort. + + If a port number + port type combination is assigned to '0.0.0.0/24' IPAddress, + it cannot be assigned to any other IPAddress on the same Device. + """ + ip_address = models.ForeignKey('IPAddress', related_name='service_ports', on_delete=models.CASCADE, + blank=False, null=False, verbose_name='ip_address') + type = 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 = ['ip_address', 'port'] + verbose_name = 'Service Port' + verbose_name_plural = 'Service Ports' + unique_together = ['ip_address', 'port', 'type'] + + def __unicode__(self): + port_type = dict(SERVICE_PORT_CHOICES).get(self.type) + return u'{}/{}'.format(self.port, port_type) + + def get_absolute_url(self): + return reverse('ipam:serviceport', args=[self.pk]) + + def clean(self): + # if port is already assigned on '0.0.0.0/24' + # that means it is assigned on all IPs on the device + port_assigned_on_all_ips = bool(ServicePort.objects.filter( + ip_address__address='0.0.0.0/24', port=self.port, type=self.type).exclude(pk=self.id)) + if port_assigned_on_all_ips: + raise ValidationError('Port already assigned on address 0.0.0.0/24') + + 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.