mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into 3840-limit-vlan-choices
This commit is contained in:
@@ -15,16 +15,16 @@ router = routers.DefaultRouter()
|
||||
router.APIRootView = VirtualizationRootView
|
||||
|
||||
# Field choices
|
||||
router.register(r'_choices', views.VirtualizationFieldChoicesViewSet, basename='field-choice')
|
||||
router.register('_choices', views.VirtualizationFieldChoicesViewSet, basename='field-choice')
|
||||
|
||||
# Clusters
|
||||
router.register(r'cluster-types', views.ClusterTypeViewSet)
|
||||
router.register(r'cluster-groups', views.ClusterGroupViewSet)
|
||||
router.register(r'clusters', views.ClusterViewSet)
|
||||
router.register('cluster-types', views.ClusterTypeViewSet)
|
||||
router.register('cluster-groups', views.ClusterGroupViewSet)
|
||||
router.register('clusters', views.ClusterViewSet)
|
||||
|
||||
# VirtualMachines
|
||||
router.register(r'virtual-machines', views.VirtualMachineViewSet)
|
||||
router.register(r'interfaces', views.InterfaceViewSet)
|
||||
router.register('virtual-machines', views.VirtualMachineViewSet)
|
||||
router.register('interfaces', views.InterfaceViewSet)
|
||||
|
||||
app_name = 'virtualization-api'
|
||||
urlpatterns = router.urls
|
||||
|
@@ -1,170 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Public Cloud",
|
||||
"slug": "public-cloud"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "vSphere",
|
||||
"slug": "vsphere"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Hyper-V",
|
||||
"slug": "hyper-v"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "libvirt",
|
||||
"slug": "libvirt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "LXD",
|
||||
"slug": "lxd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustertype",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Docker",
|
||||
"slug": "docker"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.clustergroup",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "VM Host",
|
||||
"slug": "vm-host"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Digital Ocean",
|
||||
"type": 1,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Amazon EC2",
|
||||
"type": 1,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "Microsoft Azure",
|
||||
"type": 1,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.cluster",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"name": "vSphere Cluster",
|
||||
"type": 2,
|
||||
"group": 1,
|
||||
"tenant": null,
|
||||
"site": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.virtualmachine",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"local_context_data": null,
|
||||
"created": "2019-12-19",
|
||||
"last_updated": "2019-12-19T05:24:19.146Z",
|
||||
"cluster": 2,
|
||||
"tenant": null,
|
||||
"platform": null,
|
||||
"name": "vm1",
|
||||
"status": "active",
|
||||
"role": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"vcpus": null,
|
||||
"memory": null,
|
||||
"disk": null,
|
||||
"comments": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "virtualization.virtualmachine",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"local_context_data": null,
|
||||
"created": "2019-12-19",
|
||||
"last_updated": "2019-12-19T05:24:41.478Z",
|
||||
"cluster": 1,
|
||||
"tenant": null,
|
||||
"platform": null,
|
||||
"name": "vm2",
|
||||
"status": "active",
|
||||
"role": null,
|
||||
"primary_ip4": null,
|
||||
"primary_ip6": null,
|
||||
"vcpus": null,
|
||||
"memory": null,
|
||||
"disk": null,
|
||||
"comments": ""
|
||||
}
|
||||
}
|
||||
]
|
@@ -6,15 +6,17 @@ from dcim.choices import InterfaceModeChoices
|
||||
from dcim.constants import INTERFACE_MTU_MAX, INTERFACE_MTU_MIN
|
||||
from dcim.forms import INTERFACE_MODE_HELP_TEXT
|
||||
from dcim.models import Device, DeviceRole, Interface, Platform, Rack, Region, Site
|
||||
from extras.forms import AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldForm, CustomFieldFilterForm
|
||||
from extras.forms import (
|
||||
AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm, CustomFieldFilterForm,
|
||||
)
|
||||
from ipam.models import IPAddress, VLANGroup, VLAN
|
||||
from tenancy.forms import TenancyFilterForm, TenancyForm
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
add_blank_choice, APISelect, APISelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect,
|
||||
ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ComponentForm,
|
||||
ConfirmationForm, CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField,
|
||||
SmallTextarea, StaticSelect2, StaticSelect2Multiple
|
||||
ChainedFieldsMixin, ChainedModelChoiceField, ChainedModelMultipleChoiceField, CommentField, ConfirmationForm,
|
||||
CSVChoiceField, ExpandableNameField, FilterChoiceField, JSONField, SlugField, SmallTextarea, StaticSelect2,
|
||||
StaticSelect2Multiple, TagFilterField,
|
||||
)
|
||||
from .choices import *
|
||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||
@@ -74,7 +76,7 @@ class ClusterGroupCSVForm(forms.ModelForm):
|
||||
# Clusters
|
||||
#
|
||||
|
||||
class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
comments = CommentField()
|
||||
tags = TagField(
|
||||
required=False
|
||||
@@ -98,7 +100,7 @@ class ClusterForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
}
|
||||
|
||||
|
||||
class ClusterCSVForm(forms.ModelForm):
|
||||
class ClusterCSVForm(CustomFieldModelCSVForm):
|
||||
type = forms.ModelChoiceField(
|
||||
queryset=ClusterType.objects.all(),
|
||||
to_field_name='name',
|
||||
@@ -171,7 +173,8 @@ class ClusterBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldBulkEdit
|
||||
)
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea()
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -229,6 +232,7 @@ class ClusterFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm
|
||||
null_option=True,
|
||||
)
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
class ClusterAddDevicesForm(BootstrapMixin, ChainedFieldsMixin, forms.Form):
|
||||
@@ -326,7 +330,7 @@ class ClusterRemoveDevicesForm(ConfirmationForm):
|
||||
# Virtual Machines
|
||||
#
|
||||
|
||||
class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
||||
cluster_group = forms.ModelChoiceField(
|
||||
queryset=ClusterGroup.objects.all(),
|
||||
required=False,
|
||||
@@ -429,7 +433,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
||||
self.fields['primary_ip6'].widget.attrs['readonly'] = True
|
||||
|
||||
|
||||
class VirtualMachineCSVForm(forms.ModelForm):
|
||||
class VirtualMachineCSVForm(CustomFieldModelCSVForm):
|
||||
status = CSVChoiceField(
|
||||
choices=VirtualMachineStatusChoices,
|
||||
required=False,
|
||||
@@ -535,7 +539,8 @@ class VirtualMachineBulkEditForm(BootstrapMixin, AddRemoveTagsForm, CustomFieldB
|
||||
label='Disk (GB)'
|
||||
)
|
||||
comments = CommentField(
|
||||
widget=SmallTextarea()
|
||||
widget=SmallTextarea,
|
||||
label='Comments'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
@@ -635,6 +640,7 @@ class VirtualMachineFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFil
|
||||
required=False,
|
||||
label='MAC address'
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
|
||||
#
|
||||
@@ -699,7 +705,11 @@ class InterfaceForm(BootstrapMixin, forms.ModelForm):
|
||||
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
|
||||
|
||||
|
||||
class InterfaceCreateForm(ComponentForm):
|
||||
class InterfaceCreateForm(BootstrapMixin, forms.Form):
|
||||
virtual_machine = forms.ModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
name_pattern = ExpandableNameField(
|
||||
label='Name'
|
||||
)
|
||||
@@ -709,7 +719,8 @@ class InterfaceCreateForm(ComponentForm):
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
enabled = forms.BooleanField(
|
||||
required=False
|
||||
required=False,
|
||||
initial=True
|
||||
)
|
||||
mtu = forms.IntegerField(
|
||||
required=False,
|
||||
@@ -759,15 +770,13 @@ class InterfaceCreateForm(ComponentForm):
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
# Set interfaces enabled by default
|
||||
kwargs['initial'] = kwargs.get('initial', {}).copy()
|
||||
kwargs['initial'].update({'enabled': True})
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Add current site to VLANs query params
|
||||
site = getattr(self.parent.cluster, 'site', None)
|
||||
virtual_machine = VirtualMachine.objects.get(
|
||||
pk=self.initial.get('virtual_machine') or self.data.get('virtual_machine')
|
||||
)
|
||||
|
||||
site = getattr(virtual_machine.cluster, 'site', None)
|
||||
if site is not None:
|
||||
# Add current site to VLANs query params
|
||||
self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk)
|
||||
@@ -779,6 +788,10 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
queryset=Interface.objects.all(),
|
||||
widget=forms.MultipleHiddenInput()
|
||||
)
|
||||
virtual_machine = forms.ModelChoiceField(
|
||||
queryset=VirtualMachine.objects.all(),
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
enabled = forms.NullBooleanField(
|
||||
required=False,
|
||||
widget=BulkEditNullBooleanSelect()
|
||||
@@ -831,12 +844,15 @@ class InterfaceBulkEditForm(BootstrapMixin, BulkEditForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Add current site to VLANs query params
|
||||
site = getattr(self.parent_obj.cluster, 'site', None)
|
||||
if site is not None:
|
||||
# Add current site to VLANs query params
|
||||
self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
|
||||
# Limit available VLANs based on the parent VirtualMachine
|
||||
if 'virtual_machine' in self.initial:
|
||||
parent_obj = VirtualMachine.objects.filter(pk=self.initial['virtual_machine']).first()
|
||||
|
||||
site = getattr(parent_obj.cluster, 'site', None)
|
||||
if site is not None:
|
||||
# Add current site to VLANs query params
|
||||
self.fields['untagged_vlan'].widget.add_additional_query_param('site_id', site.pk)
|
||||
self.fields['tagged_vlans'].widget.add_additional_query_param('site_id', site.pk)
|
||||
|
||||
|
||||
#
|
||||
|
@@ -1,23 +1,23 @@
|
||||
import urllib.parse
|
||||
from netaddr import EUI
|
||||
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from utilities.testing import create_test_user
|
||||
from dcim.choices import InterfaceModeChoices
|
||||
from dcim.models import DeviceRole, Interface, Platform, Site
|
||||
from ipam.models import VLAN
|
||||
from utilities.testing import StandardTestCases
|
||||
from virtualization.choices import *
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||
|
||||
|
||||
class ClusterGroupTestCase(TestCase):
|
||||
class ClusterGroupTestCase(StandardTestCases.Views):
|
||||
model = ClusterGroup
|
||||
|
||||
def setUp(self):
|
||||
user = create_test_user(
|
||||
permissions=[
|
||||
'virtualization.view_clustergroup',
|
||||
'virtualization.add_clustergroup',
|
||||
]
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
# Disable inapplicable tests
|
||||
test_get_object = None
|
||||
test_delete_object = None
|
||||
test_bulk_edit_objects = None
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
ClusterGroup.objects.bulk_create([
|
||||
ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
|
||||
@@ -25,39 +25,29 @@ class ClusterGroupTestCase(TestCase):
|
||||
ClusterGroup(name='Cluster Group 3', slug='cluster-group-3'),
|
||||
])
|
||||
|
||||
def test_clustergroup_list(self):
|
||||
cls.form_data = {
|
||||
'name': 'Cluster Group X',
|
||||
'slug': 'cluster-group-x',
|
||||
}
|
||||
|
||||
url = reverse('virtualization:clustergroup_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_clustergroup_import(self):
|
||||
|
||||
csv_data = (
|
||||
cls.csv_data = (
|
||||
"name,slug",
|
||||
"Cluster Group 4,cluster-group-4",
|
||||
"Cluster Group 5,cluster-group-5",
|
||||
"Cluster Group 6,cluster-group-6",
|
||||
)
|
||||
|
||||
response = self.client.post(reverse('virtualization:clustergroup_import'), {'csv': '\n'.join(csv_data)})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(ClusterGroup.objects.count(), 6)
|
||||
class ClusterTypeTestCase(StandardTestCases.Views):
|
||||
model = ClusterType
|
||||
|
||||
# Disable inapplicable tests
|
||||
test_get_object = None
|
||||
test_delete_object = None
|
||||
test_bulk_edit_objects = None
|
||||
|
||||
class ClusterTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
user = create_test_user(
|
||||
permissions=[
|
||||
'virtualization.view_clustertype',
|
||||
'virtualization.add_clustertype',
|
||||
]
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
ClusterType.objects.bulk_create([
|
||||
ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
|
||||
@@ -65,134 +55,229 @@ class ClusterTypeTestCase(TestCase):
|
||||
ClusterType(name='Cluster Type 3', slug='cluster-type-3'),
|
||||
])
|
||||
|
||||
def test_clustertype_list(self):
|
||||
cls.form_data = {
|
||||
'name': 'Cluster Type X',
|
||||
'slug': 'cluster-type-x',
|
||||
}
|
||||
|
||||
url = reverse('virtualization:clustertype_list')
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_clustertype_import(self):
|
||||
|
||||
csv_data = (
|
||||
cls.csv_data = (
|
||||
"name,slug",
|
||||
"Cluster Type 4,cluster-type-4",
|
||||
"Cluster Type 5,cluster-type-5",
|
||||
"Cluster Type 6,cluster-type-6",
|
||||
)
|
||||
|
||||
response = self.client.post(reverse('virtualization:clustertype_import'), {'csv': '\n'.join(csv_data)})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(ClusterType.objects.count(), 6)
|
||||
class ClusterTestCase(StandardTestCases.Views):
|
||||
model = Cluster
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
class ClusterTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
user = create_test_user(
|
||||
permissions=[
|
||||
'virtualization.view_cluster',
|
||||
'virtualization.add_cluster',
|
||||
]
|
||||
sites = (
|
||||
Site(name='Site 1', slug='site-1'),
|
||||
Site(name='Site 2', slug='site-2'),
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
Site.objects.bulk_create(sites)
|
||||
|
||||
clustergroup = ClusterGroup(name='Cluster Group 1', slug='cluster-group-1')
|
||||
clustergroup.save()
|
||||
clustergroups = (
|
||||
ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
|
||||
ClusterGroup(name='Cluster Group 2', slug='cluster-group-2'),
|
||||
)
|
||||
ClusterGroup.objects.bulk_create(clustergroups)
|
||||
|
||||
clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1')
|
||||
clustertype.save()
|
||||
clustertypes = (
|
||||
ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
|
||||
ClusterType(name='Cluster Type 2', slug='cluster-type-2'),
|
||||
)
|
||||
ClusterType.objects.bulk_create(clustertypes)
|
||||
|
||||
Cluster.objects.bulk_create([
|
||||
Cluster(name='Cluster 1', group=clustergroup, type=clustertype),
|
||||
Cluster(name='Cluster 2', group=clustergroup, type=clustertype),
|
||||
Cluster(name='Cluster 3', group=clustergroup, type=clustertype),
|
||||
Cluster(name='Cluster 1', group=clustergroups[0], type=clustertypes[0], site=sites[0]),
|
||||
Cluster(name='Cluster 2', group=clustergroups[0], type=clustertypes[0], site=sites[0]),
|
||||
Cluster(name='Cluster 3', group=clustergroups[0], type=clustertypes[0], site=sites[0]),
|
||||
])
|
||||
|
||||
def test_cluster_list(self):
|
||||
|
||||
url = reverse('virtualization:cluster_list')
|
||||
params = {
|
||||
"group": ClusterGroup.objects.first().slug,
|
||||
"type": ClusterType.objects.first().slug,
|
||||
cls.form_data = {
|
||||
'name': 'Cluster X',
|
||||
'group': clustergroups[1].pk,
|
||||
'type': clustertypes[1].pk,
|
||||
'tenant': None,
|
||||
'site': sites[1].pk,
|
||||
'comments': 'Some comments',
|
||||
'tags': 'Alpha,Bravo,Charlie',
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_cluster(self):
|
||||
|
||||
cluster = Cluster.objects.first()
|
||||
response = self.client.get(cluster.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_cluster_import(self):
|
||||
|
||||
csv_data = (
|
||||
cls.csv_data = (
|
||||
"name,type",
|
||||
"Cluster 4,Cluster Type 1",
|
||||
"Cluster 5,Cluster Type 1",
|
||||
"Cluster 6,Cluster Type 1",
|
||||
)
|
||||
|
||||
response = self.client.post(reverse('virtualization:cluster_import'), {'csv': '\n'.join(csv_data)})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(Cluster.objects.count(), 6)
|
||||
|
||||
|
||||
class VirtualMachineTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
user = create_test_user(
|
||||
permissions=[
|
||||
'virtualization.view_virtualmachine',
|
||||
'virtualization.add_virtualmachine',
|
||||
]
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1')
|
||||
clustertype.save()
|
||||
|
||||
cluster = Cluster(name='Cluster 1', type=clustertype)
|
||||
cluster.save()
|
||||
|
||||
VirtualMachine.objects.bulk_create([
|
||||
VirtualMachine(name='Virtual Machine 1', cluster=cluster),
|
||||
VirtualMachine(name='Virtual Machine 2', cluster=cluster),
|
||||
VirtualMachine(name='Virtual Machine 3', cluster=cluster),
|
||||
])
|
||||
|
||||
def test_virtualmachine_list(self):
|
||||
|
||||
url = reverse('virtualization:virtualmachine_list')
|
||||
params = {
|
||||
"cluster_id": Cluster.objects.first().pk,
|
||||
cls.bulk_edit_data = {
|
||||
'group': clustergroups[1].pk,
|
||||
'type': clustertypes[1].pk,
|
||||
'tenant': None,
|
||||
'site': sites[1].pk,
|
||||
'comments': 'New comments',
|
||||
}
|
||||
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_virtualmachine(self):
|
||||
class VirtualMachineTestCase(StandardTestCases.Views):
|
||||
model = VirtualMachine
|
||||
|
||||
virtualmachine = VirtualMachine.objects.first()
|
||||
response = self.client.get(virtualmachine.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
def test_virtualmachine_import(self):
|
||||
deviceroles = (
|
||||
DeviceRole(name='Device Role 1', slug='device-role-1'),
|
||||
DeviceRole(name='Device Role 2', slug='device-role-2'),
|
||||
)
|
||||
DeviceRole.objects.bulk_create(deviceroles)
|
||||
|
||||
csv_data = (
|
||||
platforms = (
|
||||
Platform(name='Platform 1', slug='platform-1'),
|
||||
Platform(name='Platform 2', slug='platform-2'),
|
||||
)
|
||||
Platform.objects.bulk_create(platforms)
|
||||
|
||||
clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
|
||||
clusters = (
|
||||
Cluster(name='Cluster 1', type=clustertype),
|
||||
Cluster(name='Cluster 2', type=clustertype),
|
||||
)
|
||||
Cluster.objects.bulk_create(clusters)
|
||||
|
||||
VirtualMachine.objects.bulk_create([
|
||||
VirtualMachine(name='Virtual Machine 1', cluster=clusters[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 3', cluster=clusters[0], role=deviceroles[0], platform=platforms[0]),
|
||||
])
|
||||
|
||||
cls.form_data = {
|
||||
'cluster': clusters[1].pk,
|
||||
'tenant': None,
|
||||
'platform': platforms[1].pk,
|
||||
'name': 'Virtual Machine X',
|
||||
'status': VirtualMachineStatusChoices.STATUS_STAGED,
|
||||
'role': deviceroles[1].pk,
|
||||
'primary_ip4': None,
|
||||
'primary_ip6': None,
|
||||
'vcpus': 4,
|
||||
'memory': 32768,
|
||||
'disk': 4000,
|
||||
'comments': 'Some comments',
|
||||
'tags': 'Alpha,Bravo,Charlie',
|
||||
'local_context_data': None,
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"name,cluster",
|
||||
"Virtual Machine 4,Cluster 1",
|
||||
"Virtual Machine 5,Cluster 1",
|
||||
"Virtual Machine 6,Cluster 1",
|
||||
)
|
||||
|
||||
response = self.client.post(reverse('virtualization:virtualmachine_import'), {'csv': '\n'.join(csv_data)})
|
||||
cls.bulk_edit_data = {
|
||||
'cluster': clusters[1].pk,
|
||||
'tenant': None,
|
||||
'platform': platforms[1].pk,
|
||||
'status': VirtualMachineStatusChoices.STATUS_STAGED,
|
||||
'role': deviceroles[1].pk,
|
||||
'vcpus': 8,
|
||||
'memory': 65535,
|
||||
'disk': 8000,
|
||||
'comments': 'New comments',
|
||||
}
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(VirtualMachine.objects.count(), 6)
|
||||
|
||||
class InterfaceTestCase(StandardTestCases.Views):
|
||||
model = Interface
|
||||
|
||||
# Disable inapplicable tests
|
||||
test_list_objects = None
|
||||
test_create_object = None
|
||||
test_import_objects = None
|
||||
|
||||
def test_bulk_create_objects(self):
|
||||
return self._test_bulk_create_objects(expected_count=3)
|
||||
|
||||
def _get_base_url(self):
|
||||
# Interface belongs to the DCIM app, so we have to override the base URL
|
||||
return 'virtualization:interface_{}'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||
devicerole = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
|
||||
clustertype = ClusterType.objects.create(name='Cluster Type 1', slug='cluster-type-1')
|
||||
cluster = Cluster.objects.create(name='Cluster 1', type=clustertype, site=site)
|
||||
virtualmachines = (
|
||||
VirtualMachine(name='Virtual Machine 1', cluster=cluster, role=devicerole),
|
||||
VirtualMachine(name='Virtual Machine 2', cluster=cluster, role=devicerole),
|
||||
)
|
||||
VirtualMachine.objects.bulk_create(virtualmachines)
|
||||
|
||||
Interface.objects.bulk_create([
|
||||
Interface(virtual_machine=virtualmachines[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||
Interface(virtual_machine=virtualmachines[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||
Interface(virtual_machine=virtualmachines[0], name='Interface 3', type=InterfaceTypeChoices.TYPE_VIRTUAL),
|
||||
])
|
||||
|
||||
vlans = (
|
||||
VLAN(vid=1, name='VLAN1', site=site),
|
||||
VLAN(vid=101, name='VLAN101', site=site),
|
||||
VLAN(vid=102, name='VLAN102', site=site),
|
||||
VLAN(vid=103, name='VLAN103', site=site),
|
||||
)
|
||||
VLAN.objects.bulk_create(vlans)
|
||||
|
||||
cls.form_data = {
|
||||
'virtual_machine': virtualmachines[1].pk,
|
||||
'name': 'Interface X',
|
||||
'type': InterfaceTypeChoices.TYPE_VIRTUAL,
|
||||
'enabled': False,
|
||||
'mgmt_only': False,
|
||||
'mac_address': EUI('01-02-03-04-05-06'),
|
||||
'mtu': 2000,
|
||||
'description': 'New description',
|
||||
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||
'untagged_vlan': vlans[0].pk,
|
||||
'tagged_vlans': [v.pk for v in vlans[1:4]],
|
||||
'tags': 'Alpha,Bravo,Charlie',
|
||||
}
|
||||
|
||||
cls.bulk_create_data = {
|
||||
'virtual_machine': virtualmachines[1].pk,
|
||||
'name_pattern': 'Interface [4-6]',
|
||||
'type': InterfaceTypeChoices.TYPE_VIRTUAL,
|
||||
'enabled': False,
|
||||
'mgmt_only': False,
|
||||
'mac_address': EUI('01-02-03-04-05-06'),
|
||||
'mtu': 2000,
|
||||
'description': 'New description',
|
||||
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||
'untagged_vlan': vlans[0].pk,
|
||||
'tagged_vlans': [v.pk for v in vlans[1:4]],
|
||||
'tags': 'Alpha,Bravo,Charlie',
|
||||
}
|
||||
|
||||
cls.bulk_edit_data = {
|
||||
'virtual_machine': virtualmachines[1].pk,
|
||||
'enabled': False,
|
||||
'mtu': 2000,
|
||||
'description': 'New description',
|
||||
'mode': InterfaceModeChoices.MODE_TAGGED,
|
||||
# 'untagged_vlan': vlans[0].pk,
|
||||
# 'tagged_vlans': [v.pk for v in vlans[1:4]],
|
||||
}
|
||||
|
||||
cls.csv_data = (
|
||||
"device,name,type",
|
||||
"Device 1,Interface 4,1000BASE-T (1GE)",
|
||||
"Device 1,Interface 5,1000BASE-T (1GE)",
|
||||
"Device 1,Interface 6,1000BASE-T (1GE)",
|
||||
)
|
||||
|
@@ -9,53 +9,53 @@ app_name = 'virtualization'
|
||||
urlpatterns = [
|
||||
|
||||
# Cluster types
|
||||
path(r'cluster-types/', views.ClusterTypeListView.as_view(), name='clustertype_list'),
|
||||
path(r'cluster-types/add/', views.ClusterTypeCreateView.as_view(), name='clustertype_add'),
|
||||
path(r'cluster-types/import/', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'),
|
||||
path(r'cluster-types/delete/', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'),
|
||||
path(r'cluster-types/<slug:slug>/edit/', views.ClusterTypeEditView.as_view(), name='clustertype_edit'),
|
||||
path(r'cluster-types/<slug:slug>/changelog/', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}),
|
||||
path('cluster-types/', views.ClusterTypeListView.as_view(), name='clustertype_list'),
|
||||
path('cluster-types/add/', views.ClusterTypeCreateView.as_view(), name='clustertype_add'),
|
||||
path('cluster-types/import/', views.ClusterTypeBulkImportView.as_view(), name='clustertype_import'),
|
||||
path('cluster-types/delete/', views.ClusterTypeBulkDeleteView.as_view(), name='clustertype_bulk_delete'),
|
||||
path('cluster-types/<slug:slug>/edit/', views.ClusterTypeEditView.as_view(), name='clustertype_edit'),
|
||||
path('cluster-types/<slug:slug>/changelog/', ObjectChangeLogView.as_view(), name='clustertype_changelog', kwargs={'model': ClusterType}),
|
||||
|
||||
# Cluster groups
|
||||
path(r'cluster-groups/', views.ClusterGroupListView.as_view(), name='clustergroup_list'),
|
||||
path(r'cluster-groups/add/', views.ClusterGroupCreateView.as_view(), name='clustergroup_add'),
|
||||
path(r'cluster-groups/import/', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'),
|
||||
path(r'cluster-groups/delete/', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'),
|
||||
path(r'cluster-groups/<slug:slug>/edit/', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'),
|
||||
path(r'cluster-groups/<slug:slug>/changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}),
|
||||
path('cluster-groups/', views.ClusterGroupListView.as_view(), name='clustergroup_list'),
|
||||
path('cluster-groups/add/', views.ClusterGroupCreateView.as_view(), name='clustergroup_add'),
|
||||
path('cluster-groups/import/', views.ClusterGroupBulkImportView.as_view(), name='clustergroup_import'),
|
||||
path('cluster-groups/delete/', views.ClusterGroupBulkDeleteView.as_view(), name='clustergroup_bulk_delete'),
|
||||
path('cluster-groups/<slug:slug>/edit/', views.ClusterGroupEditView.as_view(), name='clustergroup_edit'),
|
||||
path('cluster-groups/<slug:slug>/changelog/', ObjectChangeLogView.as_view(), name='clustergroup_changelog', kwargs={'model': ClusterGroup}),
|
||||
|
||||
# Clusters
|
||||
path(r'clusters/', views.ClusterListView.as_view(), name='cluster_list'),
|
||||
path(r'clusters/add/', views.ClusterCreateView.as_view(), name='cluster_add'),
|
||||
path(r'clusters/import/', views.ClusterBulkImportView.as_view(), name='cluster_import'),
|
||||
path(r'clusters/edit/', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'),
|
||||
path(r'clusters/delete/', views.ClusterBulkDeleteView.as_view(), name='cluster_bulk_delete'),
|
||||
path(r'clusters/<int:pk>/', views.ClusterView.as_view(), name='cluster'),
|
||||
path(r'clusters/<int:pk>/edit/', views.ClusterEditView.as_view(), name='cluster_edit'),
|
||||
path(r'clusters/<int:pk>/delete/', views.ClusterDeleteView.as_view(), name='cluster_delete'),
|
||||
path(r'clusters/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}),
|
||||
path(r'clusters/<int:pk>/devices/add/', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'),
|
||||
path(r'clusters/<int:pk>/devices/remove/', views.ClusterRemoveDevicesView.as_view(), name='cluster_remove_devices'),
|
||||
path('clusters/', views.ClusterListView.as_view(), name='cluster_list'),
|
||||
path('clusters/add/', views.ClusterCreateView.as_view(), name='cluster_add'),
|
||||
path('clusters/import/', views.ClusterBulkImportView.as_view(), name='cluster_import'),
|
||||
path('clusters/edit/', views.ClusterBulkEditView.as_view(), name='cluster_bulk_edit'),
|
||||
path('clusters/delete/', views.ClusterBulkDeleteView.as_view(), name='cluster_bulk_delete'),
|
||||
path('clusters/<int:pk>/', views.ClusterView.as_view(), name='cluster'),
|
||||
path('clusters/<int:pk>/edit/', views.ClusterEditView.as_view(), name='cluster_edit'),
|
||||
path('clusters/<int:pk>/delete/', views.ClusterDeleteView.as_view(), name='cluster_delete'),
|
||||
path('clusters/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='cluster_changelog', kwargs={'model': Cluster}),
|
||||
path('clusters/<int:pk>/devices/add/', views.ClusterAddDevicesView.as_view(), name='cluster_add_devices'),
|
||||
path('clusters/<int:pk>/devices/remove/', views.ClusterRemoveDevicesView.as_view(), name='cluster_remove_devices'),
|
||||
|
||||
# Virtual machines
|
||||
path(r'virtual-machines/', views.VirtualMachineListView.as_view(), name='virtualmachine_list'),
|
||||
path(r'virtual-machines/add/', views.VirtualMachineCreateView.as_view(), name='virtualmachine_add'),
|
||||
path(r'virtual-machines/import/', views.VirtualMachineBulkImportView.as_view(), name='virtualmachine_import'),
|
||||
path(r'virtual-machines/edit/', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'),
|
||||
path(r'virtual-machines/delete/', views.VirtualMachineBulkDeleteView.as_view(), name='virtualmachine_bulk_delete'),
|
||||
path(r'virtual-machines/<int:pk>/', views.VirtualMachineView.as_view(), name='virtualmachine'),
|
||||
path(r'virtual-machines/<int:pk>/edit/', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
|
||||
path(r'virtual-machines/<int:pk>/delete/', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
|
||||
path(r'virtual-machines/<int:pk>/config-context/', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'),
|
||||
path(r'virtual-machines/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}),
|
||||
path(r'virtual-machines/<int:virtualmachine>/services/assign/', ServiceCreateView.as_view(), name='virtualmachine_service_assign'),
|
||||
path('virtual-machines/', views.VirtualMachineListView.as_view(), name='virtualmachine_list'),
|
||||
path('virtual-machines/add/', views.VirtualMachineCreateView.as_view(), name='virtualmachine_add'),
|
||||
path('virtual-machines/import/', views.VirtualMachineBulkImportView.as_view(), name='virtualmachine_import'),
|
||||
path('virtual-machines/edit/', views.VirtualMachineBulkEditView.as_view(), name='virtualmachine_bulk_edit'),
|
||||
path('virtual-machines/delete/', views.VirtualMachineBulkDeleteView.as_view(), name='virtualmachine_bulk_delete'),
|
||||
path('virtual-machines/<int:pk>/', views.VirtualMachineView.as_view(), name='virtualmachine'),
|
||||
path('virtual-machines/<int:pk>/edit/', views.VirtualMachineEditView.as_view(), name='virtualmachine_edit'),
|
||||
path('virtual-machines/<int:pk>/delete/', views.VirtualMachineDeleteView.as_view(), name='virtualmachine_delete'),
|
||||
path('virtual-machines/<int:pk>/config-context/', views.VirtualMachineConfigContextView.as_view(), name='virtualmachine_configcontext'),
|
||||
path('virtual-machines/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='virtualmachine_changelog', kwargs={'model': VirtualMachine}),
|
||||
path('virtual-machines/<int:virtualmachine>/services/assign/', ServiceCreateView.as_view(), name='virtualmachine_service_assign'),
|
||||
|
||||
# VM interfaces
|
||||
path(r'virtual-machines/interfaces/add/', views.VirtualMachineBulkAddInterfaceView.as_view(), name='virtualmachine_bulk_add_interface'),
|
||||
path(r'virtual-machines/<int:pk>/interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'),
|
||||
path(r'virtual-machines/<int:pk>/interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
|
||||
path(r'virtual-machines/<int:pk>/interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
||||
path(r'vm-interfaces/<int:pk>/edit/', views.InterfaceEditView.as_view(), name='interface_edit'),
|
||||
path(r'vm-interfaces/<int:pk>/delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'),
|
||||
path('interfaces/add/', views.InterfaceCreateView.as_view(), name='interface_add'),
|
||||
path('interfaces/edit/', views.InterfaceBulkEditView.as_view(), name='interface_bulk_edit'),
|
||||
path('interfaces/delete/', views.InterfaceBulkDeleteView.as_view(), name='interface_bulk_delete'),
|
||||
path('interfaces/<int:pk>/edit/', views.InterfaceEditView.as_view(), name='interface_edit'),
|
||||
path('interfaces/<int:pk>/delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'),
|
||||
path('virtual-machines/interfaces/add/', views.VirtualMachineBulkAddInterfaceView.as_view(), name='virtualmachine_bulk_add_interface'),
|
||||
|
||||
]
|
||||
|
@@ -330,8 +330,6 @@ class VirtualMachineBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
|
||||
class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
|
||||
permission_required = 'dcim.add_interface'
|
||||
parent_model = VirtualMachine
|
||||
parent_field = 'virtual_machine'
|
||||
model = Interface
|
||||
form = forms.InterfaceCreateForm
|
||||
model_form = forms.InterfaceForm
|
||||
@@ -353,7 +351,6 @@ class InterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_interface'
|
||||
queryset = Interface.objects.all()
|
||||
parent_model = VirtualMachine
|
||||
table = tables.InterfaceTable
|
||||
form = forms.InterfaceBulkEditForm
|
||||
|
||||
@@ -361,7 +358,6 @@ class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dcim.delete_interface'
|
||||
queryset = Interface.objects.all()
|
||||
parent_model = VirtualMachine
|
||||
table = tables.InterfaceTable
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user