mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Enabled services on virtual machines
This commit is contained in:
@@ -12,6 +12,7 @@ from ipam.models import (
|
||||
)
|
||||
from tenancy.api.serializers import NestedTenantSerializer
|
||||
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
||||
from virtualization.api.serializers import NestedVirtualMachineSerializer
|
||||
|
||||
|
||||
#
|
||||
@@ -295,12 +296,13 @@ class AvailableIPSerializer(serializers.Serializer):
|
||||
|
||||
class ServiceSerializer(serializers.ModelSerializer):
|
||||
device = NestedDeviceSerializer()
|
||||
virtual_machine = NestedVirtualMachineSerializer()
|
||||
protocol = ChoiceFieldSerializer(choices=IP_PROTOCOL_CHOICES)
|
||||
ipaddresses = NestedIPAddressSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = ['id', 'device', 'name', 'port', 'protocol', 'ipaddresses', 'description']
|
||||
fields = ['id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description']
|
||||
|
||||
|
||||
# TODO: Figure out how to use model validation with ManyToManyFields. Calling clean() yields a ValueError.
|
||||
@@ -308,4 +310,4 @@ class WritableServiceSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Service
|
||||
fields = ['id', 'device', 'name', 'port', 'protocol', 'ipaddresses', 'description']
|
||||
fields = ['id', 'device', 'virtual_machine', 'name', 'port', 'protocol', 'ipaddresses', 'description']
|
||||
|
@@ -861,5 +861,14 @@ class ServiceForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
super(ServiceForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Limit IP address choices to those assigned to interfaces of the parent device
|
||||
self.fields['ipaddresses'].queryset = IPAddress.objects.filter(interface__device=self.instance.device)
|
||||
# Limit IP address choices to those assigned to interfaces of the parent device/VM
|
||||
if self.instance.device:
|
||||
self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
|
||||
interface__device=self.instance.device
|
||||
)
|
||||
elif self.instance.virtual_machine:
|
||||
self.fields['ipaddresses'].queryset = IPAddress.objects.filter(
|
||||
interface__virtual_machine=self.instance.virtual_machine
|
||||
)
|
||||
else:
|
||||
self.fields['ipaddresses'].choices = []
|
||||
|
31
netbox/ipam/migrations/0019_virtualization.py
Normal file
31
netbox/ipam/migrations/0019_virtualization.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-31 15:44
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('virtualization', '0001_virtualization'),
|
||||
('ipam', '0018_remove_service_uniqueness_constraint'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='service',
|
||||
options={'ordering': ['protocol', 'port']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='service',
|
||||
name='virtual_machine',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='services', to='virtualization.VirtualMachine'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='device',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='services', to='dcim.Device', verbose_name='device'),
|
||||
),
|
||||
]
|
@@ -588,20 +588,59 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
|
||||
@python_2_unicode_compatible
|
||||
class Service(CreatedUpdatedModel):
|
||||
"""
|
||||
A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device. A Service may optionally be tied
|
||||
to one or more specific IPAddresses belonging to the Device.
|
||||
A Service represents a layer-four service (e.g. HTTP or SSH) running on a Device or VirtualMachine. A Service may
|
||||
optionally be tied to one or more specific IPAddresses belonging to its parent.
|
||||
"""
|
||||
device = models.ForeignKey('dcim.Device', related_name='services', on_delete=models.CASCADE, verbose_name='device')
|
||||
name = models.CharField(max_length=30)
|
||||
protocol = models.PositiveSmallIntegerField(choices=IP_PROTOCOL_CHOICES)
|
||||
port = models.PositiveIntegerField(validators=[MinValueValidator(1), MaxValueValidator(65535)],
|
||||
verbose_name='Port number')
|
||||
ipaddresses = models.ManyToManyField('ipam.IPAddress', related_name='services', blank=True,
|
||||
verbose_name='IP addresses')
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
device = models.ForeignKey(
|
||||
to='dcim.Device',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='services',
|
||||
verbose_name='device',
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
virtual_machine = models.ForeignKey(
|
||||
to='virtualization.VirtualMachine',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='services',
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=30
|
||||
)
|
||||
protocol = models.PositiveSmallIntegerField(
|
||||
choices=IP_PROTOCOL_CHOICES
|
||||
)
|
||||
port = models.PositiveIntegerField(
|
||||
validators=[MinValueValidator(1), MaxValueValidator(65535)],
|
||||
verbose_name='Port number'
|
||||
)
|
||||
ipaddresses = models.ManyToManyField(
|
||||
to='ipam.IPAddress',
|
||||
related_name='services',
|
||||
blank=True,
|
||||
verbose_name='IP addresses'
|
||||
)
|
||||
description = models.CharField(
|
||||
max_length=100,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ['device', 'protocol', 'port']
|
||||
ordering = ['protocol', 'port']
|
||||
|
||||
def __str__(self):
|
||||
return '{} ({}/{})'.format(self.name, self.port, self.get_protocol_display())
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
return self.device or self.virtual_machine
|
||||
|
||||
def clean(self):
|
||||
|
||||
# A Service must belong to a Device *or* to a VirtualMachine
|
||||
if self.device and self.virtual_machine:
|
||||
raise ValidationError("A service cannot be associated with both a device and a virtual machine.")
|
||||
if not self.device and not self.virtual_machine:
|
||||
raise ValidationError("A service must be associated with either a device or a virtual machine.")
|
||||
|
@@ -15,6 +15,7 @@ from utilities.paginator import EnhancedPaginator
|
||||
from utilities.views import (
|
||||
BulkCreateView, BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
from . import filters, forms, tables
|
||||
from .constants import IPADDRESS_ROLE_ANYCAST
|
||||
from .models import (
|
||||
@@ -838,10 +839,12 @@ class ServiceCreateView(PermissionRequiredMixin, ObjectEditView):
|
||||
def alter_obj(self, obj, request, url_args, url_kwargs):
|
||||
if 'device' in url_kwargs:
|
||||
obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
|
||||
elif 'virtualmachine' in url_kwargs:
|
||||
obj.virtual_machine = get_object_or_404(VirtualMachine, pk=url_kwargs['virtualmachine'])
|
||||
return obj
|
||||
|
||||
def get_return_url(self, request, obj):
|
||||
return obj.device.get_absolute_url()
|
||||
return obj.parent.get_absolute_url()
|
||||
|
||||
|
||||
class ServiceEditView(ServiceCreateView):
|
||||
|
Reference in New Issue
Block a user