1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #8222: Enable the assignment of a VM to a specific host device within a cluster

This commit is contained in:
jeremystretch
2022-05-25 16:01:10 -04:00
parent 31024ce672
commit b331f047af
17 changed files with 155 additions and 48 deletions

View File

@ -1,6 +1,6 @@
# Virtual Machines # Virtual Machines
A virtual machine represents a virtual compute instance hosted within a cluster. Each VM must be assigned to exactly one cluster. A virtual machine represents a virtual compute instance hosted within a cluster. Each VM must be assigned to exactly one cluster, and may optionally be assigned to a particular host device within that cluster.
Like devices, each VM can be assigned a platform and/or functional role, and must have one of the following operational statuses assigned to it: Like devices, each VM can be assigned a platform and/or functional role, and must have one of the following operational statuses assigned to it:

View File

@ -9,6 +9,7 @@
### Enhancements ### Enhancements
* [#1202](https://github.com/netbox-community/netbox/issues/1202) - Support overlapping assignment of NAT IP addresses * [#1202](https://github.com/netbox-community/netbox/issues/1202) - Support overlapping assignment of NAT IP addresses
* [#8222](https://github.com/netbox-community/netbox/issues/8222) - Enable the assignment of a VM to a specific host device within a cluster
* [#8471](https://github.com/netbox-community/netbox/issues/8471) - Add `status` field to Cluster * [#8471](https://github.com/netbox-community/netbox/issues/8471) - Add `status` field to Cluster
* [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping * [#8495](https://github.com/netbox-community/netbox/issues/8495) - Enable custom field grouping
* [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results * [#8995](https://github.com/netbox-community/netbox/issues/8995) - Enable arbitrary ordering of REST API results
@ -26,4 +27,6 @@
* The `nat_inside` field no longer requires a unique value * The `nat_inside` field no longer requires a unique value
* The `nat_outside` field has changed from a single IP address instance to a list of multiple IP addresses * The `nat_outside` field has changed from a single IP address instance to a list of multiple IP addresses
* virtualization.Cluster * virtualization.Cluster
* Add required `status` field (default value: `active`) * Added required `status` field (default value: `active`)
* virtualization.VirtualMachine
* Added `device` field

View File

@ -78,9 +78,7 @@
</div> </div>
<div class="col col-md-6"> <div class="col col-md-6">
<div class="card"> <div class="card">
<h5 class="card-header"> <h5 class="card-header">Cluster</h5>
Cluster
</h5>
<div class="card-body"> <div class="card-body">
<table class="table table-hover attr-table"> <table class="table table-hover attr-table">
<tr> <tr>
@ -96,13 +94,17 @@
<th scope="row">Cluster Type</th> <th scope="row">Cluster Type</th>
<td>{{ object.cluster.type }}</td> <td>{{ object.cluster.type }}</td>
</tr> </tr>
<tr>
<th scope="row">Device</th>
<td>
{{ object.device|linkify|placeholder }}
</td>
</tr>
</table> </table>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h5 class="card-header"> <h5 class="card-header">Resources</h5>
Resources
</h5>
<div class="card-body"> <div class="card-body">
<table class="table table-hover attr-table"> <table class="table table-hover attr-table">
<tr> <tr>

View File

@ -34,7 +34,7 @@ def post_data(data):
return ret return ret
def create_test_device(name): def create_test_device(name, **attrs):
""" """
Convenience method for creating a Device (e.g. for component testing). Convenience method for creating a Device (e.g. for component testing).
""" """
@ -42,7 +42,7 @@ def create_test_device(name):
manufacturer, _ = Manufacturer.objects.get_or_create(name='Manufacturer 1', slug='manufacturer-1') manufacturer, _ = Manufacturer.objects.get_or_create(name='Manufacturer 1', slug='manufacturer-1')
devicetype, _ = DeviceType.objects.get_or_create(model='Device Type 1', manufacturer=manufacturer) devicetype, _ = DeviceType.objects.get_or_create(model='Device Type 1', manufacturer=manufacturer)
devicerole, _ = DeviceRole.objects.get_or_create(name='Device Role 1', slug='device-role-1') devicerole, _ = DeviceRole.objects.get_or_create(name='Device Role 1', slug='device-role-1')
device = Device.objects.create(name=name, site=site, device_type=devicetype, device_role=devicerole) device = Device.objects.create(name=name, site=site, device_type=devicetype, device_role=devicerole, **attrs)
return device return device

View File

@ -1,7 +1,9 @@
from drf_yasg.utils import swagger_serializer_method from drf_yasg.utils import swagger_serializer_method
from rest_framework import serializers from rest_framework import serializers
from dcim.api.nested_serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer from dcim.api.nested_serializers import (
NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer,
)
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer from ipam.api.nested_serializers import NestedIPAddressSerializer, NestedVLANSerializer, NestedVRFSerializer
from ipam.models import VLAN from ipam.models import VLAN
@ -68,6 +70,7 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
status = ChoiceField(choices=VirtualMachineStatusChoices, required=False) status = ChoiceField(choices=VirtualMachineStatusChoices, required=False)
site = NestedSiteSerializer(read_only=True) site = NestedSiteSerializer(read_only=True)
cluster = NestedClusterSerializer() cluster = NestedClusterSerializer()
device = NestedDeviceSerializer(required=False, allow_null=True)
role = NestedDeviceRoleSerializer(required=False, allow_null=True) role = NestedDeviceRoleSerializer(required=False, allow_null=True)
tenant = NestedTenantSerializer(required=False, allow_null=True) tenant = NestedTenantSerializer(required=False, allow_null=True)
platform = NestedPlatformSerializer(required=False, allow_null=True) platform = NestedPlatformSerializer(required=False, allow_null=True)
@ -78,9 +81,9 @@ class VirtualMachineSerializer(NetBoxModelSerializer):
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'platform',
'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'tags', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data',
'custom_fields', 'created', 'last_updated', 'tags', 'custom_fields', 'created', 'last_updated',
] ]
validators = [] validators = []
@ -90,9 +93,9 @@ class VirtualMachineWithConfigContextSerializer(VirtualMachineSerializer):
class Meta(VirtualMachineSerializer.Meta): class Meta(VirtualMachineSerializer.Meta):
fields = [ fields = [
'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'role', 'tenant', 'platform', 'primary_ip', 'id', 'url', 'display', 'name', 'status', 'site', 'cluster', 'device', 'role', 'tenant', 'platform',
'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data', 'tags', 'primary_ip', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'local_context_data',
'custom_fields', 'config_context', 'created', 'last_updated', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
] ]
@swagger_serializer_method(serializer_or_field=serializers.DictField) @swagger_serializer_method(serializer_or_field=serializers.DictField)

View File

@ -54,7 +54,7 @@ class ClusterViewSet(NetBoxModelViewSet):
class VirtualMachineViewSet(ConfigContextQuerySetMixin, NetBoxModelViewSet): class VirtualMachineViewSet(ConfigContextQuerySetMixin, NetBoxModelViewSet):
queryset = VirtualMachine.objects.prefetch_related( queryset = VirtualMachine.objects.prefetch_related(
'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags' 'cluster__site', 'device', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags'
) )
filterset_class = filtersets.VirtualMachineFilterSet filterset_class = filtersets.VirtualMachineFilterSet

View File

@ -1,7 +1,7 @@
import django_filters import django_filters
from django.db.models import Q from django.db.models import Q
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
from extras.filtersets import LocalConfigContextFilterSet from extras.filtersets import LocalConfigContextFilterSet
from ipam.models import VRF from ipam.models import VRF
from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet from netbox.filtersets import OrganizationalModelFilterSet, NetBoxModelFilterSet
@ -150,6 +150,16 @@ class VirtualMachineFilterSet(
to_field_name='name', to_field_name='name',
label='Cluster', label='Cluster',
) )
device_id = django_filters.ModelMultipleChoiceFilter(
queryset=Device.objects.all(),
label='Device (ID)',
)
device = django_filters.ModelMultipleChoiceFilter(
field_name='device__name',
queryset=Device.objects.all(),
to_field_name='name',
label='Device',
)
region_id = TreeNodeMultipleChoiceFilter( region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(), queryset=Region.objects.all(),
field_name='cluster__site__region', field_name='cluster__site__region',

View File

@ -2,7 +2,7 @@ from django import forms
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
from ipam.models import VLAN, VRF from ipam.models import VLAN, VRF
from netbox.forms import NetBoxModelBulkEditForm from netbox.forms import NetBoxModelBulkEditForm
from tenancy.models import Tenant from tenancy.models import Tenant
@ -110,6 +110,13 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
queryset=Cluster.objects.all(), queryset=Cluster.objects.all(),
required=False required=False
) )
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
query_params={
'cluster_id': '$cluster'
}
)
role = DynamicModelChoiceField( role = DynamicModelChoiceField(
queryset=DeviceRole.objects.filter( queryset=DeviceRole.objects.filter(
vm_role=True vm_role=True
@ -146,11 +153,11 @@ class VirtualMachineBulkEditForm(NetBoxModelBulkEditForm):
model = VirtualMachine model = VirtualMachine
fieldsets = ( fieldsets = (
(None, ('cluster', 'status', 'role', 'tenant', 'platform')), (None, ('cluster', 'device', 'status', 'role', 'tenant', 'platform')),
('Resources', ('vcpus', 'memory', 'disk')) ('Resources', ('vcpus', 'memory', 'disk'))
) )
nullable_fields = ( nullable_fields = (
'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
) )

View File

@ -1,5 +1,5 @@
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from dcim.models import DeviceRole, Platform, Site from dcim.models import Device, DeviceRole, Platform, Site
from ipam.models import VRF from ipam.models import VRF
from netbox.forms import NetBoxModelCSVForm from netbox.forms import NetBoxModelCSVForm
from tenancy.models import Tenant from tenancy.models import Tenant
@ -76,6 +76,12 @@ class VirtualMachineCSVForm(NetBoxModelCSVForm):
to_field_name='name', to_field_name='name',
help_text='Assigned cluster' help_text='Assigned cluster'
) )
device = CSVModelChoiceField(
queryset=Device.objects.all(),
to_field_name='name',
required=False,
help_text='Assigned device within cluster'
)
role = CSVModelChoiceField( role = CSVModelChoiceField(
queryset=DeviceRole.objects.filter( queryset=DeviceRole.objects.filter(
vm_role=True vm_role=True
@ -100,7 +106,7 @@ class VirtualMachineCSVForm(NetBoxModelCSVForm):
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = ( fields = (
'name', 'status', 'role', 'cluster', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments', 'name', 'status', 'role', 'cluster', 'device', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'comments',
) )

View File

@ -1,7 +1,7 @@
from django import forms from django import forms
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
from extras.forms import LocalConfigContextFilterForm from extras.forms import LocalConfigContextFilterForm
from ipam.models import VRF from ipam.models import VRF
from netbox.forms import NetBoxModelFilterSetForm from netbox.forms import NetBoxModelFilterSetForm
@ -87,7 +87,7 @@ class VirtualMachineFilterForm(
model = VirtualMachine model = VirtualMachine
fieldsets = ( fieldsets = (
(None, ('q', 'tag')), (None, ('q', 'tag')),
('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id')), ('Cluster', ('cluster_group_id', 'cluster_type_id', 'cluster_id', 'device_id')),
('Location', ('region_id', 'site_group_id', 'site_id')), ('Location', ('region_id', 'site_group_id', 'site_id')),
('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')), ('Attriubtes', ('status', 'role_id', 'platform_id', 'mac_address', 'has_primary_ip', 'local_context_data')),
('Tenant', ('tenant_group_id', 'tenant_id')), ('Tenant', ('tenant_group_id', 'tenant_id')),
@ -110,6 +110,11 @@ class VirtualMachineFilterForm(
required=False, required=False,
label=_('Cluster') label=_('Cluster')
) )
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
label=_('Device')
)
region_id = DynamicModelMultipleChoiceField( region_id = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False, required=False,

View File

@ -179,6 +179,13 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
'group_id': '$cluster_group' 'group_id': '$cluster_group'
} }
) )
device = DynamicModelChoiceField(
queryset=Device.objects.all(),
required=False,
query_params={
'cluster_id': '$cluster'
}
)
role = DynamicModelChoiceField( role = DynamicModelChoiceField(
queryset=DeviceRole.objects.all(), queryset=DeviceRole.objects.all(),
required=False, required=False,
@ -197,7 +204,7 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
fieldsets = ( fieldsets = (
('Virtual Machine', ('name', 'role', 'status', 'tags')), ('Virtual Machine', ('name', 'role', 'status', 'tags')),
('Cluster', ('cluster_group', 'cluster')), ('Cluster', ('cluster_group', 'cluster', 'device')),
('Tenancy', ('tenant_group', 'tenant')), ('Tenancy', ('tenant_group', 'tenant')),
('Management', ('platform', 'primary_ip4', 'primary_ip6')), ('Management', ('platform', 'primary_ip4', 'primary_ip6')),
('Resources', ('vcpus', 'memory', 'disk')), ('Resources', ('vcpus', 'memory', 'disk')),
@ -207,8 +214,8 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
class Meta: class Meta:
model = VirtualMachine model = VirtualMachine
fields = [ fields = [
'name', 'status', 'cluster_group', 'cluster', 'role', 'tenant_group', 'tenant', 'platform', 'primary_ip4', 'name', 'status', 'cluster_group', 'cluster', 'device', 'role', 'tenant_group', 'tenant', 'platform',
'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data', 'primary_ip4', 'primary_ip6', 'vcpus', 'memory', 'disk', 'comments', 'tags', 'local_context_data',
] ]
help_texts = { help_texts = {
'local_context_data': "Local config context data overwrites all sources contexts in the final rendered " 'local_context_data': "Local config context data overwrites all sources contexts in the final rendered "

View File

@ -0,0 +1,20 @@
# Generated by Django 4.0.4 on 2022-05-25 19:30
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0153_created_datetimefield'),
('virtualization', '0030_cluster_status'),
]
operations = [
migrations.AddField(
model_name='virtualmachine',
name='device',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='virtual_machines', to='dcim.device'),
),
]

View File

@ -200,6 +200,13 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='virtual_machines' related_name='virtual_machines'
) )
device = models.ForeignKey(
to='dcim.Device',
on_delete=models.PROTECT,
related_name='virtual_machines',
blank=True,
null=True
)
tenant = models.ForeignKey( tenant = models.ForeignKey(
to='tenancy.Tenant', to='tenancy.Tenant',
on_delete=models.PROTECT, on_delete=models.PROTECT,
@ -316,6 +323,12 @@ class VirtualMachine(NetBoxModel, ConfigContextModel):
def clean(self): def clean(self):
super().clean() super().clean()
# Validate assigned cluster device
if self.device and self.device not in self.cluster.devices.all():
raise ValidationError({
'device': f'The selected device ({self.device} is not assigned to this cluster ({self.cluster}).'
})
# Validate primary IP addresses # Validate primary IP addresses
interfaces = self.interfaces.all() interfaces = self.interfaces.all()
for field in ['primary_ip4', 'primary_ip6']: for field in ['primary_ip4', 'primary_ip6']:

View File

@ -33,6 +33,9 @@ class VirtualMachineTable(NetBoxTable):
cluster = tables.Column( cluster = tables.Column(
linkify=True linkify=True
) )
device = tables.Column(
linkify=True
)
role = columns.ColoredLabelColumn() role = columns.ColoredLabelColumn()
tenant = TenantColumn() tenant = TenantColumn()
comments = columns.MarkdownColumn() comments = columns.MarkdownColumn()
@ -56,7 +59,7 @@ class VirtualMachineTable(NetBoxTable):
class Meta(NetBoxTable.Meta): class Meta(NetBoxTable.Meta):
model = VirtualMachine model = VirtualMachine
fields = ( fields = (
'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'pk', 'id', 'name', 'status', 'cluster', 'device', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'tags', 'created', 'last_updated', 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'tags', 'created', 'last_updated',
) )
default_columns = ( default_columns = (

View File

@ -3,7 +3,7 @@ from rest_framework import status
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from ipam.models import VLAN, VRF from ipam.models import VLAN, VRF
from utilities.testing import APITestCase, APIViewTestCases from utilities.testing import APITestCase, APIViewTestCases, create_test_device
from virtualization.choices import * from virtualization.choices import *
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@ -152,8 +152,15 @@ class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
) )
Cluster.objects.bulk_create(clusters) Cluster.objects.bulk_create(clusters)
device1 = create_test_device('device1')
device1.cluster = clusters[0]
device1.save()
device2 = create_test_device('device2')
device2.cluster = clusters[1]
device2.save()
virtual_machines = ( virtual_machines = (
VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], local_context_data={'A': 1}), VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], device=device1, local_context_data={'A': 1}),
VirtualMachine(name='Virtual Machine 2', cluster=clusters[0], local_context_data={'B': 2}), VirtualMachine(name='Virtual Machine 2', cluster=clusters[0], local_context_data={'B': 2}),
VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], local_context_data={'C': 3}), VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], local_context_data={'C': 3}),
) )
@ -163,6 +170,7 @@ class VirtualMachineTest(APIViewTestCases.APIViewTestCase):
{ {
'name': 'Virtual Machine 4', 'name': 'Virtual Machine 4',
'cluster': clusters[1].pk, 'cluster': clusters[1].pk,
'device': device2.pk,
}, },
{ {
'name': 'Virtual Machine 5', 'name': 'Virtual Machine 5',

View File

@ -1,9 +1,9 @@
from django.test import TestCase from django.test import TestCase
from dcim.models import DeviceRole, Platform, Region, Site, SiteGroup from dcim.models import Device, DeviceRole, Platform, Region, Site, SiteGroup
from ipam.models import IPAddress, VRF from ipam.models import IPAddress, VRF
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from utilities.testing import ChangeLoggedFilterSetTests from utilities.testing import ChangeLoggedFilterSetTests, create_test_device
from virtualization.choices import * from virtualization.choices import *
from virtualization.filtersets import * from virtualization.filtersets import *
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@ -225,9 +225,9 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
site_group.save() site_group.save()
sites = ( sites = (
Site(name='Test Site 1', slug='test-site-1', region=regions[0], group=site_groups[0]), Site(name='Site 1', slug='site-1', region=regions[0], group=site_groups[0]),
Site(name='Test Site 2', slug='test-site-2', region=regions[1], group=site_groups[1]), Site(name='Site 2', slug='site-2', region=regions[1], group=site_groups[1]),
Site(name='Test Site 3', slug='test-site-3', region=regions[2], group=site_groups[2]), Site(name='Site 3', slug='site-3', region=regions[2], group=site_groups[2]),
) )
Site.objects.bulk_create(sites) Site.objects.bulk_create(sites)
@ -252,6 +252,12 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
) )
DeviceRole.objects.bulk_create(roles) DeviceRole.objects.bulk_create(roles)
devices = (
create_test_device('device1', cluster=clusters[0]),
create_test_device('device2', cluster=clusters[1]),
create_test_device('device3', cluster=clusters[2]),
)
tenant_groups = ( tenant_groups = (
TenantGroup(name='Tenant group 1', slug='tenant-group-1'), TenantGroup(name='Tenant group 1', slug='tenant-group-1'),
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
@ -268,9 +274,9 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
Tenant.objects.bulk_create(tenants) Tenant.objects.bulk_create(tenants)
vms = ( vms = (
VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], platform=platforms[0], role=roles[0], tenant=tenants[0], status=VirtualMachineStatusChoices.STATUS_ACTIVE, vcpus=1, memory=1, disk=1, local_context_data={"foo": 123}), VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], device=devices[0], platform=platforms[0], role=roles[0], tenant=tenants[0], status=VirtualMachineStatusChoices.STATUS_ACTIVE, vcpus=1, memory=1, disk=1, local_context_data={"foo": 123}),
VirtualMachine(name='Virtual Machine 2', cluster=clusters[1], platform=platforms[1], role=roles[1], tenant=tenants[1], status=VirtualMachineStatusChoices.STATUS_STAGED, vcpus=2, memory=2, disk=2), VirtualMachine(name='Virtual Machine 2', cluster=clusters[1], device=devices[1], platform=platforms[1], role=roles[1], tenant=tenants[1], status=VirtualMachineStatusChoices.STATUS_STAGED, vcpus=2, memory=2, disk=2),
VirtualMachine(name='Virtual Machine 3', cluster=clusters[2], platform=platforms[2], role=roles[2], tenant=tenants[2], status=VirtualMachineStatusChoices.STATUS_OFFLINE, vcpus=3, memory=3, disk=3), VirtualMachine(name='Virtual Machine 3', cluster=clusters[2], device=devices[2], platform=platforms[2], role=roles[2], tenant=tenants[2], status=VirtualMachineStatusChoices.STATUS_OFFLINE, vcpus=3, memory=3, disk=3),
) )
VirtualMachine.objects.bulk_create(vms) VirtualMachine.objects.bulk_create(vms)
@ -331,6 +337,13 @@ class VirtualMachineTestCase(TestCase, ChangeLoggedFilterSetTests):
params = {'cluster': [clusters[0].name, clusters[1].name]} params = {'cluster': [clusters[0].name, clusters[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_device(self):
devices = Device.objects.all()[:2]
params = {'device_id': [devices[0].pk, devices[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'device': [devices[0].name, devices[1].name]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_region(self): def test_region(self):
regions = Region.objects.all()[:2] regions = Region.objects.all()[:2]
params = {'region_id': [regions[0].pk, regions[1].pk]} params = {'region_id': [regions[0].pk, regions[1].pk]}

View File

@ -5,7 +5,7 @@ from netaddr import EUI
from dcim.choices import InterfaceModeChoices from dcim.choices import InterfaceModeChoices
from dcim.models import DeviceRole, Platform, Site from dcim.models import DeviceRole, Platform, Site
from ipam.models import VLAN, VRF from ipam.models import VLAN, VRF
from utilities.testing import ViewTestCases, create_tags from utilities.testing import ViewTestCases, create_tags, create_test_device
from virtualization.choices import * from virtualization.choices import *
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
@ -176,16 +176,22 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
) )
Cluster.objects.bulk_create(clusters) Cluster.objects.bulk_create(clusters)
devices = (
create_test_device('device1', cluster=clusters[0]),
create_test_device('device2', cluster=clusters[1]),
)
VirtualMachine.objects.bulk_create([ VirtualMachine.objects.bulk_create([
VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]), VirtualMachine(name='Virtual Machine 1', cluster=clusters[0], device=devices[0], role=deviceroles[0], platform=platforms[0]),
VirtualMachine(name='Virtual Machine 2', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]), VirtualMachine(name='Virtual Machine 2', cluster=clusters[0], device=devices[0], role=deviceroles[0], platform=platforms[0]),
VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]), VirtualMachine(name='Virtual Machine 3', cluster=clusters[0], device=devices[0], role=deviceroles[0], platform=platforms[0]),
]) ])
tags = create_tags('Alpha', 'Bravo', 'Charlie') tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = { cls.form_data = {
'cluster': clusters[1].pk, 'cluster': clusters[1].pk,
'device': devices[1].pk,
'tenant': None, 'tenant': None,
'platform': platforms[1].pk, 'platform': platforms[1].pk,
'name': 'Virtual Machine X', 'name': 'Virtual Machine X',
@ -202,14 +208,15 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
} }
cls.csv_data = ( cls.csv_data = (
"name,status,cluster", "name,status,cluster,device",
"Virtual Machine 4,active,Cluster 1", "Virtual Machine 4,active,Cluster 1,device1",
"Virtual Machine 5,active,Cluster 1", "Virtual Machine 5,active,Cluster 1,device1",
"Virtual Machine 6,active,Cluster 1", "Virtual Machine 6,active,Cluster 1,",
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {
'cluster': clusters[1].pk, 'cluster': clusters[1].pk,
'device': devices[1].pk,
'tenant': None, 'tenant': None,
'platform': platforms[1].pk, 'platform': platforms[1].pk,
'status': VirtualMachineStatusChoices.STATUS_STAGED, 'status': VirtualMachineStatusChoices.STATUS_STAGED,