services.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. from django.contrib.postgres.fields import ArrayField
  2. from django.core.exceptions import ValidationError
  3. from django.core.validators import MaxValueValidator, MinValueValidator
  4. from django.db import models
  5. from django.urls import reverse
  6. from extras.utils import extras_features
  7. from ipam.choices import *
  8. from ipam.constants import *
  9. from netbox.models import PrimaryModel
  10. from utilities.querysets import RestrictedQuerySet
  11. from utilities.utils import array_to_string
  12. __all__ = (
  13. 'Service',
  14. )
  15. @extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
  16. class Service(PrimaryModel):
  17. """
  18. A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
  19. optionally be tied to one or more specific IPAddresses belonging to its parent.
  20. """
  21. device = models.ForeignKey(
  22. to='dcim.Device',
  23. on_delete=models.CASCADE,
  24. related_name='services',
  25. verbose_name='device',
  26. null=True,
  27. blank=True
  28. )
  29. virtual_machine = models.ForeignKey(
  30. to='virtualization.VirtualMachine',
  31. on_delete=models.CASCADE,
  32. related_name='services',
  33. null=True,
  34. blank=True
  35. )
  36. name = models.CharField(
  37. max_length=100
  38. )
  39. protocol = models.CharField(
  40. max_length=50,
  41. choices=ServiceProtocolChoices
  42. )
  43. ports = ArrayField(
  44. base_field=models.PositiveIntegerField(
  45. validators=[
  46. MinValueValidator(SERVICE_PORT_MIN),
  47. MaxValueValidator(SERVICE_PORT_MAX)
  48. ]
  49. ),
  50. verbose_name='Port numbers'
  51. )
  52. ipaddresses = models.ManyToManyField(
  53. to='ipam.IPAddress',
  54. related_name='services',
  55. blank=True,
  56. verbose_name='IP addresses'
  57. )
  58. description = models.CharField(
  59. max_length=200,
  60. blank=True
  61. )
  62. objects = RestrictedQuerySet.as_manager()
  63. csv_headers = ['device', 'virtual_machine', 'name', 'protocol', 'ports', 'description']
  64. class Meta:
  65. ordering = ('protocol', 'ports', 'pk') # (protocol, port) may be non-unique
  66. def __str__(self):
  67. return f'{self.name} ({self.get_protocol_display()}/{self.port_list})'
  68. def get_absolute_url(self):
  69. return reverse('ipam:service', args=[self.pk])
  70. @property
  71. def parent(self):
  72. return self.device or self.virtual_machine
  73. def clean(self):
  74. super().clean()
  75. # A Service must belong to a Device *or* to a VirtualMachine
  76. if self.device and self.virtual_machine:
  77. raise ValidationError("A service cannot be associated with both a device and a virtual machine.")
  78. if not self.device and not self.virtual_machine:
  79. raise ValidationError("A service must be associated with either a device or a virtual machine.")
  80. def to_csv(self):
  81. return (
  82. self.device.name if self.device else None,
  83. self.virtual_machine.name if self.virtual_machine else None,
  84. self.name,
  85. self.get_protocol_display(),
  86. self.ports,
  87. self.description,
  88. )
  89. @property
  90. def port_list(self):
  91. return array_to_string(self.ports)