Closes #1493: Added functional roles for virtual machines

This commit is contained in:
Jeremy Stretch 2017-09-29 11:13:41 -04:00
parent f49d7ce1da
commit 6243fbfd0d
8 changed files with 115 additions and 25 deletions

View File

@ -58,24 +58,20 @@
</td> </td>
</tr> </tr>
<tr> <tr>
<td>Cluster</td> <td>Role</td>
<td> <td>
{% if vm.cluster.group %} {% if vm.role %}
<a href="{{ vm.cluster.group.get_absolute_url }}">{{ vm.cluster.group }}</a> <a href="{% url 'virtualization:virtualmachine_list' %}?role={{ vm.role.slug }}">{{ vm.role }}</a>
<i class="fa fa-angle-right"></i> {% else %}
<span class="text-muted">None</span>
{% endif %} {% endif %}
<a href="{{ vm.cluster.get_absolute_url }}">{{ vm.cluster }}</a>
</td> </td>
</tr> </tr>
<tr>
<td>Cluster Type</td>
<td>{{ vm.cluster.type }}</td>
</tr>
<tr> <tr>
<td>Platform</td> <td>Platform</td>
<td> <td>
{% if vm.platform %} {% if vm.platform %}
<span>{{ vm.platform }}</span> <a href="{% url 'virtualization:virtualmachine_list' %}?platform={{ vm.platform.slug }}">{{ vm.platform }}</a>
{% else %} {% else %}
<span class="text-muted">None</span> <span class="text-muted">None</span>
{% endif %} {% endif %}
@ -127,6 +123,27 @@
</tr> </tr>
</table> </table>
</div> </div>
<div class="panel panel-default">
<div class="panel-heading">
<strong>Cluster</strong>
</div>
<table class="table table-hover panel-body attr-table">
<tr>
<td>Cluster</td>
<td>
{% if vm.cluster.group %}
<a href="{{ vm.cluster.group.get_absolute_url }}">{{ vm.cluster.group }}</a>
<i class="fa fa-angle-right"></i>
{% endif %}
<a href="{{ vm.cluster.get_absolute_url }}">{{ vm.cluster }}</a>
</td>
</tr>
<tr>
<td>Cluster Type</td>
<td>{{ vm.cluster.type }}</td>
</tr>
</table>
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<strong>Resources</strong> <strong>Resources</strong>

View File

@ -7,9 +7,15 @@
<div class="panel-body"> <div class="panel-body">
{% render_field form.name %} {% render_field form.name %}
{% render_field form.status %} {% render_field form.status %}
{% render_field form.role %}
{% render_field form.platform %}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>Cluster</strong></div>
<div class="panel-body">
{% render_field form.cluster_group %} {% render_field form.cluster_group %}
{% render_field form.cluster %} {% render_field form.cluster %}
{% render_field form.platform %}
</div> </div>
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">

View File

@ -2,7 +2,7 @@ from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from dcim.api.serializers import NestedPlatformSerializer, NestedSiteSerializer from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
from dcim.constants import VIFACE_FF_CHOICES from dcim.constants import VIFACE_FF_CHOICES
from dcim.models import Interface from dcim.models import Interface
from extras.api.customfields import CustomFieldModelSerializer from extras.api.customfields import CustomFieldModelSerializer
@ -86,14 +86,15 @@ class WritableClusterSerializer(CustomFieldModelSerializer):
class VirtualMachineSerializer(CustomFieldModelSerializer): class VirtualMachineSerializer(CustomFieldModelSerializer):
status = ChoiceFieldSerializer(choices=STATUS_CHOICES) status = ChoiceFieldSerializer(choices=STATUS_CHOICES)
cluster = NestedClusterSerializer() cluster = NestedClusterSerializer()
role = NestedDeviceRoleSerializer()
tenant = NestedTenantSerializer() tenant = NestedTenantSerializer()
platform = NestedPlatformSerializer() platform = NestedPlatformSerializer()
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus',
'disk', 'comments', 'custom_fields', 'memory', 'disk', 'comments', 'custom_fields',
] ]
@ -110,8 +111,8 @@ class WritableVirtualMachineSerializer(CustomFieldModelSerializer):
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'id', 'name', 'status', 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'vcpus',
'disk', 'comments', 'custom_fields', 'memory', 'disk', 'comments', 'custom_fields',
] ]

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import django_filters import django_filters
from django.db.models import Q from django.db.models import Q
from dcim.models import Platform, Site from dcim.models import DeviceRole, Platform, Site
from extras.filters import CustomFieldFilterSet from extras.filters import CustomFieldFilterSet
from tenancy.models import Tenant from tenancy.models import Tenant
from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter from utilities.filters import NullableModelMultipleChoiceFilter, NumericInFilter
@ -80,10 +80,21 @@ class VirtualMachineFilter(CustomFieldFilterSet):
to_field_name='slug', to_field_name='slug',
label='Cluster group (slug)', label='Cluster group (slug)',
) )
cluster_id = NullableModelMultipleChoiceFilter( cluster_id = django_filters.ModelMultipleChoiceFilter(
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
label='Cluster (ID)', label='Cluster (ID)',
) )
role_id = NullableModelMultipleChoiceFilter(
name='role_id',
queryset=DeviceRole.objects.all(),
label='Role (ID)',
)
role = NullableModelMultipleChoiceFilter(
name='role__slug',
queryset=DeviceRole.objects.all(),
to_field_name='slug',
label='Role (slug)',
)
tenant_id = NullableModelMultipleChoiceFilter( tenant_id = NullableModelMultipleChoiceFilter(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
label='Tenant (ID)', label='Tenant (ID)',

View File

@ -8,7 +8,7 @@ from django.db.models import Count
from dcim.constants import IFACE_FF_VIRTUAL, VIFACE_FF_CHOICES from dcim.constants import IFACE_FF_VIRTUAL, VIFACE_FF_CHOICES
from dcim.formfields import MACAddressFormField from dcim.formfields import MACAddressFormField
from dcim.models import Device, Interface, Platform, Rack, Region, Site from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm from extras.forms import CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
from tenancy.forms import TenancyForm from tenancy.forms import TenancyForm
from tenancy.models import Tenant from tenancy.models import Tenant
@ -222,7 +222,8 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'name', 'status', 'cluster_group', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', 'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
'comments',
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -251,6 +252,15 @@ class VirtualMachineCSVForm(forms.ModelForm):
'invalid_choice': 'Invalid cluster name.', 'invalid_choice': 'Invalid cluster name.',
} }
) )
role = forms.ModelChoiceField(
queryset=DeviceRole.objects.all(),
required=False,
to_field_name='name',
help_text='Name of functional role',
error_messages={
'invalid_choice': 'Invalid role name.'
}
)
tenant = forms.ModelChoiceField( tenant = forms.ModelChoiceField(
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
required=False, required=False,
@ -272,13 +282,14 @@ class VirtualMachineCSVForm(forms.ModelForm):
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = ['name', 'status', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments'] fields = ['name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm): class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput) pk = forms.ModelMultipleChoiceField(queryset=VirtualMachine.objects.all(), widget=forms.MultipleHiddenInput)
status = forms.ChoiceField(choices=add_blank_choice(STATUS_CHOICES), required=False, initial='') status = forms.ChoiceField(choices=add_blank_choice(STATUS_CHOICES), required=False, initial='')
cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False) cluster = forms.ModelChoiceField(queryset=Cluster.objects.all(), required=False)
role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), required=False)
tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False) tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False) platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False)
vcpus = forms.IntegerField(required=False, label='vCPUs') vcpus = forms.IntegerField(required=False, label='vCPUs')
@ -287,7 +298,7 @@ class VirtualMachineBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
comments = CommentField(widget=SmallTextarea) comments = CommentField(widget=SmallTextarea)
class Meta: class Meta:
nullable_fields = ['tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments'] nullable_fields = ['role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments']
def vm_status_choices(): def vm_status_choices():
@ -303,13 +314,28 @@ class VirtualMachineFilterForm(BootstrapMixin, CustomFieldFilterForm):
cluster_group = FilterChoiceField( cluster_group = FilterChoiceField(
queryset=ClusterGroup.objects.all(), queryset=ClusterGroup.objects.all(),
to_field_name='slug', to_field_name='slug',
null_option=(0, 'None'), null_option=(0, 'None')
) )
cluster_id = FilterChoiceField( cluster_id = FilterChoiceField(
queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')), queryset=Cluster.objects.annotate(filter_count=Count('virtual_machines')),
label='Cluster' label='Cluster'
) )
role = FilterChoiceField(
queryset=DeviceRole.objects.annotate(filter_count=Count('virtual_machines')),
to_field_name='slug',
null_option=(0, 'None')
)
status = forms.MultipleChoiceField(choices=vm_status_choices, required=False) status = forms.MultipleChoiceField(choices=vm_status_choices, required=False)
tenant = FilterChoiceField(
queryset=Tenant.objects.annotate(filter_count=Count('virtual_machines')),
to_field_name='slug',
null_option=(0, 'None')
)
platform = FilterChoiceField(
queryset=Platform.objects.annotate(filter_count=Count('virtual_machines')),
to_field_name='slug',
null_option=(0, 'None')
)
# #

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.4 on 2017-09-29 14:32
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0044_virtualization'),
('virtualization', '0003_cluster_add_site'),
]
operations = [
migrations.AddField(
model_name='virtualmachine',
name='role',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_machines', to='dcim.DeviceRole'),
),
]

View File

@ -179,6 +179,13 @@ class VirtualMachine(CreatedUpdatedModel, CustomFieldModel):
default=STATUS_ACTIVE, default=STATUS_ACTIVE,
verbose_name='Status' verbose_name='Status'
) )
role = models.ForeignKey(
to='dcim.DeviceRole',
on_delete=models.PROTECT,
related_name='virtual_machines',
blank=True,
null=True
)
primary_ip4 = models.OneToOneField( primary_ip4 = models.OneToOneField(
to='ipam.IPAddress', to='ipam.IPAddress',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,

View File

@ -95,7 +95,7 @@ class VirtualMachineTable(BaseTable):
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = VirtualMachine model = VirtualMachine
fields = ('pk', 'name', 'status', 'cluster', 'tenant', 'vcpus', 'memory', 'disk') fields = ('pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk')
class VirtualMachineDetailTable(VirtualMachineTable): class VirtualMachineDetailTable(VirtualMachineTable):
@ -105,7 +105,7 @@ class VirtualMachineDetailTable(VirtualMachineTable):
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = VirtualMachine model = VirtualMachine
fields = ('pk', 'name', 'status', 'cluster', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip') fields = ('pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip')
# #