Merge ~mpontillo/maas:bug-1704501--2.3 into maas:2.3

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: cd5f92a6481eeae6240a1e0f3a955cfb3233daab
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~mpontillo/maas:bug-1704501--2.3
Merge into: maas:2.3
Diff against target: 190 lines (+94/-7)
4 files modified
src/maasserver/forms/tests/test_vlan.py (+65/-0)
src/maasserver/forms/vlan.py (+5/-0)
src/maasserver/models/vlan.py (+16/-0)
src/maasserver/static/partials/vlan-details.html (+8/-7)
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+342604@code.launchpad.net

Commit message

LP: #1704501 - Allow users to change which Fabric a VLAN is on.

Backports: 4083dc0635958109a28ef44a84df63e855007335

To post a comment you must log in.
~mpontillo/maas:bug-1704501--2.3 updated
cd5f92a... by Mike Pontillo

Fix fabric field formatting.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Self-approve backport.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/forms/tests/test_vlan.py b/src/maasserver/forms/tests/test_vlan.py
index 8db5f5a..5c5aacb 100644
--- a/src/maasserver/forms/tests/test_vlan.py
+++ b/src/maasserver/forms/tests/test_vlan.py
@@ -13,6 +13,11 @@ from maasserver.models.vlan import DEFAULT_MTU
13from maasserver.testing.factory import factory13from maasserver.testing.factory import factory
14from maasserver.testing.testcase import MAASServerTestCase14from maasserver.testing.testcase import MAASServerTestCase
15from maasserver.utils.orm import reload_object15from maasserver.utils.orm import reload_object
16from testtools import ExpectedException
17from testtools.matchers import (
18 Equals,
19 Not,
20)
1621
1722
18class TestVLANForm(MAASServerTestCase):23class TestVLANForm(MAASServerTestCase):
@@ -371,3 +376,63 @@ class TestVLANForm(MAASServerTestCase):
371 self.assertEqual(secondary_rack, vlan.primary_rack)376 self.assertEqual(secondary_rack, vlan.primary_rack)
372 self.assertEqual(None, vlan.secondary_rack)377 self.assertEqual(None, vlan.secondary_rack)
373 self.assertTrue(vlan.dhcp_on)378 self.assertTrue(vlan.dhcp_on)
379
380
381class TestVLANFormFabricModification(MAASServerTestCase):
382
383 def test__cannot_move_vlan_with_overlapping_vid(self):
384 fabric0 = Fabric.objects.get_default_fabric()
385 fabric1 = factory.make_Fabric()
386 fabric1_untagged = fabric1.get_default_vlan()
387 form = VLANForm(instance=fabric1_untagged, data={
388 "fabric": fabric0.id
389 })
390 is_valid = form.is_valid()
391 self.assertThat(is_valid, Equals(False))
392 self.assertThat(dict(form.errors), Equals(
393 {'__all__': [
394 'A VLAN with the specified VID already '
395 'exists in the destination fabric.'
396 ]}
397 ))
398 with ExpectedException(ValueError):
399 form.save()
400
401 def test__allows_moving_vlan_to_new_fabric_if_vid_is_unique(self):
402 fabric0 = Fabric.objects.get_default_fabric()
403 fabric1 = factory.make_Fabric()
404 fabric1_untagged = fabric1.get_default_vlan()
405 form = VLANForm(instance=fabric1_untagged, data={
406 "fabric": fabric0.id,
407 "vid": 10
408 })
409 is_valid = form.is_valid()
410 self.assertThat(is_valid, Equals(True))
411 form.save()
412
413 def test__deletes_empty_fabrics(self):
414 fabric0 = Fabric.objects.get_default_fabric()
415 fabric1 = factory.make_Fabric()
416 fabric1_untagged = fabric1.get_default_vlan()
417 form = VLANForm(instance=fabric1_untagged, data={
418 "fabric": fabric0.id,
419 "vid": 10
420 })
421 is_valid = form.is_valid()
422 self.assertThat(is_valid, Equals(True))
423 form.save()
424 self.assertThat(reload_object(fabric1), Equals(None))
425
426 def test__does_not_delete_non_empty_fabrics(self):
427 fabric0 = Fabric.objects.get_default_fabric()
428 fabric1 = factory.make_Fabric()
429 factory.make_VLAN(fabric=fabric1)
430 fabric1_untagged = fabric1.get_default_vlan()
431 form = VLANForm(instance=fabric1_untagged, data={
432 "fabric": fabric0.id,
433 "vid": 10
434 })
435 is_valid = form.is_valid()
436 form.save()
437 self.assertThat(is_valid, Equals(True))
438 self.assertThat(reload_object(fabric1), Not(Equals(None)))
diff --git a/src/maasserver/forms/vlan.py b/src/maasserver/forms/vlan.py
index 13fad27..89e5f4d 100644
--- a/src/maasserver/forms/vlan.py
+++ b/src/maasserver/forms/vlan.py
@@ -15,6 +15,7 @@ from maasserver.fields import (
15)15)
16from maasserver.forms import MAASModelForm16from maasserver.forms import MAASModelForm
17from maasserver.models import (17from maasserver.models import (
18 Fabric,
18 RackController,19 RackController,
19 Space,20 Space,
20)21)
@@ -30,6 +31,9 @@ class VLANForm(MAASModelForm):
30 space = SpecifierOrModelChoiceField(31 space = SpecifierOrModelChoiceField(
31 queryset=Space.objects.all(), required=False, empty_label="")32 queryset=Space.objects.all(), required=False, empty_label="")
3233
34 fabric = SpecifierOrModelChoiceField(
35 queryset=Fabric.objects.all(), required=False, empty_label="")
36
33 class Meta:37 class Meta:
34 model = VLAN38 model = VLAN
35 fields = (39 fields = (
@@ -42,6 +46,7 @@ class VLANForm(MAASModelForm):
42 'secondary_rack',46 'secondary_rack',
43 'relay_vlan',47 'relay_vlan',
44 'space',48 'space',
49 'fabric',
45 )50 )
4651
47 def __init__(self, *args, **kwargs):52 def __init__(self, *args, **kwargs):
diff --git a/src/maasserver/models/vlan.py b/src/maasserver/models/vlan.py
index 28e9f42..35e34a9 100644
--- a/src/maasserver/models/vlan.py
+++ b/src/maasserver/models/vlan.py
@@ -15,6 +15,7 @@ from django.db.models import (
15 BooleanField,15 BooleanField,
16 CASCADE,16 CASCADE,
17 CharField,17 CharField,
18 Count,
18 deletion,19 deletion,
19 ForeignKey,20 ForeignKey,
20 IntegerField,21 IntegerField,
@@ -243,6 +244,15 @@ class VLAN(CleanSave, TimestampedModel):
243 subnet.vlan = self.fabric.get_default_vlan()244 subnet.vlan = self.fabric.get_default_vlan()
244 subnet.save()245 subnet.save()
245246
247 def unique_error_message(self, model_class, unique_check):
248 if set(unique_check) == {'vid', 'fabric'}:
249 return (
250 'A VLAN with the specified VID already exists in the '
251 'destination fabric.'
252 )
253 else:
254 return super().unique_error_message(model_class, unique_check)
255
246 def delete(self):256 def delete(self):
247 if self.is_fabric_default():257 if self.is_fabric_default():
248 raise ValidationError(258 raise ValidationError(
@@ -268,3 +278,9 @@ class VLAN(CleanSave, TimestampedModel):
268 "DHCP server is being used.",278 "DHCP server is being used.",
269 ident="dhcp_disabled_all_vlans")279 ident="dhcp_disabled_all_vlans")
270 super().save(*args, **kwargs)280 super().save(*args, **kwargs)
281 # Circular dependencies.
282 from maasserver.models import Fabric
283 # Delete any now-empty fabrics.
284 fabrics_with_vlan_count = Fabric.objects.annotate(
285 vlan_count=Count("vlan"))
286 fabrics_with_vlan_count.filter(vlan_count=0).delete()
diff --git a/src/maasserver/static/partials/vlan-details.html b/src/maasserver/static/partials/vlan-details.html
index a526ed9..b02043f 100644
--- a/src/maasserver/static/partials/vlan-details.html
+++ b/src/maasserver/static/partials/vlan-details.html
@@ -220,7 +220,7 @@
220 </div>220 </div>
221 </header>221 </header>
222 <section class="row">222 <section class="row">
223 <div class="wrapper--inner">223 <fieldset class="wrapper--inner">
224 <div class="twelve-col">224 <div class="twelve-col">
225 <h2 class="u-float--left">VLAN Summary</h2>225 <h2 class="u-float--left">VLAN Summary</h2>
226 <button type="button" name="button" class="button--secondary button--inline u-float--right" data-ng-click="vlanDetails.enterEditSummary()" data-ng-if="!vlanDetails.editSummary">Edit</button>226 <button type="button" name="button" class="button--secondary button--inline u-float--right" data-ng-click="vlanDetails.enterEditSummary()" data-ng-if="!vlanDetails.editSummary">Edit</button>
@@ -268,12 +268,10 @@
268 <maas-obj-field type="textarea" key="description" label="Description" placeholder="VLAN description"268 <maas-obj-field type="textarea" key="description" label="Description" placeholder="VLAN description"
269 label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>269 label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
270 </fieldset>270 </fieldset>
271 <div class="six-col last-col">271 <fieldset class="form__fieldset six-col last-col">
272 <dl>272 <dl>
273 <dt class="two-col">Fabric</dt>273 <maas-obj-field type="options" key="fabric" label="Fabric" label-width="two" input-width="three"
274 <dd class="four-col last-col">274 options="fabric.id as fabric.name for fabric in vlanDetails.fabrics"></maas-obj-field>
275 <a href="#/fabric/{$ vlanDetails.fabric.id $}">{$ vlanDetails.fabric.name $}</a>
276 </dd>
277 <div data-ng-if="vlanDetails.relatedControllers">275 <div data-ng-if="vlanDetails.relatedControllers">
278 <dt class="two-col">Rack controllers <span class="icon icon--help tooltip" aria-label="A rack controller controls hosts and images and runs network services&#xa;like DHCP for connected VLANs."></span></dt>276 <dt class="two-col">Rack controllers <span class="icon icon--help tooltip" aria-label="A rack controller controls hosts and images and runs network services&#xa;like DHCP for connected VLANs."></span></dt>
279 <dd class="four-col last-col">277 <dd class="four-col last-col">
@@ -283,8 +281,11 @@
283 </dd>281 </dd>
284 </div>282 </div>
285 </dl>283 </dl>
284 </fieldset>
285 <div class="six-col">
286 <maas-obj-errors></maas-obj-errors>
286 </div>287 </div>
287 <div class="twelve-col u-align--right">288 <div class="six-col last-col u-align--right">
288 <button class="button--base button--inline"289 <button class="button--base button--inline"
289 data-ng-click="vlanDetails.exitEditSummary()">Cancel</button>290 data-ng-click="vlanDetails.exitEditSummary()">Cancel</button>
290 <button class="button--positive button--inline" maas-obj-save>Save summary</button>291 <button class="button--positive button--inline" maas-obj-save>Save summary</button>

Subscribers

People subscribed via source and target branches