Merge lp:~mpontillo/maas/delete-fabric-cleanup into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 4961
Proposed branch: lp:~mpontillo/maas/delete-fabric-cleanup
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 325 lines (+111/-54)
8 files modified
src/maasserver/models/fabric.py (+13/-3)
src/maasserver/models/tests/test_fabric.py (+18/-5)
src/maasserver/static/js/angular/controllers/fabric_details.js (+22/-8)
src/maasserver/static/js/angular/controllers/tests/test_fabric_details.js (+38/-5)
src/maasserver/static/js/angular/factories/fabrics.js (+8/-6)
src/maasserver/static/js/angular/services/managerhelper.js (+1/-1)
src/maasserver/static/partials/fabric-details.html (+0/-24)
src/maasserver/static/partials/subnet-details.html (+11/-2)
To merge this branch: bzr merge lp:~mpontillo/maas/delete-fabric-cleanup
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+292855@code.launchpad.net

Commit message

Clean up "delete fabric" action.

 * UI no longer contains duplicate widgets.
 * Generates a nicer error message when subnets and/or interfaces
   are still present on the fabric.
 * Drive-by fix for an issue that caused VLANs with no subnets to
   be missing from the fabric details page.
 * Add missing test cases for fabric details page.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good other than the missing test. Please add the test before landing, not going to block you on the missing test.

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

Yeah, this page doesn't have any tests at all for creating that table. We have some technical debt to pay off here. My fault because I reviewed the original page and didn't notice they were missing.

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (31.8 KiB)

The attempt to merge lp:~mpontillo/maas/delete-fabric-cleanup into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial InRelease
Get:2 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-updates InRelease [92.2 kB]
Hit:3 http://prodstack-zone-2.clouds.archive.ubuntu.com/ubuntu xenial-backports InRelease
Get:4 http://security.ubuntu.com/ubuntu xenial-security InRelease [92.2 kB]
Fetched 184 kB in 0s (381 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm postgresql pxelinux python3-all python3-apt python3-bson python3-convoy python3-coverage python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-mock python3-netaddr python3-netifaces python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
bind9 is already the newest version (1:9.10.3.dfsg.P4-8).
bind9utils is already the newest version (1:9.10.3.dfsg.P4-8).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.3.dfsg.P4-8).
firefox is already the newest version (45.0.2+build1-0ubuntu1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-f...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/models/fabric.py'
2--- src/maasserver/models/fabric.py 2016-04-11 16:23:26 +0000
3+++ src/maasserver/models/fabric.py 2016-04-27 03:54:09 +0000
4@@ -26,6 +26,7 @@
5 from maasserver import DefaultMeta
6 from maasserver.models.cleansave import CleanSave
7 from maasserver.models.interface import Interface
8+from maasserver.models.subnet import Subnet
9 from maasserver.models.timestampedmodel import TimestampedModel
10 from maasserver.utils.orm import MAASQueriesMixin
11
12@@ -193,11 +194,20 @@
13 if self.is_default():
14 raise ValidationError(
15 "This fabric is the default fabric, it cannot be deleted.")
16- # Circular imports.
17+ if Subnet.objects.filter(vlan__fabric=self).exists():
18+ subnets = Subnet.objects.filter(vlan__fabric=self).order_by(
19+ 'cidr')
20+ descriptions = [str(subnet.cidr) for subnet in subnets]
21+ raise ValidationError(
22+ "Can't delete fabric; the following subnets are "
23+ "still present: %s" % (", ".join(descriptions)))
24 if Interface.objects.filter(vlan__fabric=self).exists():
25+ interfaces = Interface.objects.filter(vlan__fabric=self).order_by(
26+ 'node', 'name')
27+ descriptions = [iface.get_log_string() for iface in interfaces]
28 raise ValidationError(
29- "Can't delete fabric: interfaces are connected to VLANs from "
30- "this fabric.")
31+ "Can't delete fabric; the following interfaces are "
32+ "still connected: %s" % (", ".join(descriptions)))
33 super(Fabric, self).delete()
34
35 def _create_default_vlan(self):
36
37=== modified file 'src/maasserver/models/tests/test_fabric.py'
38--- src/maasserver/models/tests/test_fabric.py 2016-04-11 16:23:26 +0000
39+++ src/maasserver/models/tests/test_fabric.py 2016-04-27 03:54:09 +0000
40@@ -27,7 +27,10 @@
41 from maasserver.testing.factory import factory
42 from maasserver.testing.testcase import MAASServerTestCase
43 from maastesting.matchers import MockCalledOnce
44-from testtools.matchers import MatchesStructure
45+from testtools.matchers import (
46+ Contains,
47+ MatchesStructure,
48+)
49
50
51 class TestFabricManagerGetFabricOr404(MAASServerTestCase):
52@@ -239,10 +242,20 @@
53 INTERFACE_TYPE.PHYSICAL,
54 vlan=fabric.get_default_vlan())
55 error = self.assertRaises(ValidationError, fabric.delete)
56- self.assertEqual(
57- "Can't delete fabric: interfaces are connected to VLANs from this "
58- "fabric.",
59- error.message)
60+ self.assertThat(error.message, Contains(
61+ "Can't delete fabric; the following interfaces are "
62+ "still connected: "))
63+
64+ def test_cant_delete_fabric_if_connected_to_subnet(self):
65+ fabric = factory.make_Fabric()
66+ factory.make_Subnet(vlan=fabric.get_default_vlan())
67+ factory.make_Interface(
68+ INTERFACE_TYPE.PHYSICAL,
69+ vlan=fabric.get_default_vlan())
70+ error = self.assertRaises(ValidationError, fabric.delete)
71+ self.assertThat(error.message, Contains(
72+ "Can't delete fabric; the following subnets are "
73+ "still present: "))
74
75 def test_cant_delete_default_fabric(self):
76 default_fabric = Fabric.objects.get_default_fabric()
77
78=== modified file 'src/maasserver/static/js/angular/controllers/fabric_details.js'
79--- src/maasserver/static/js/angular/controllers/fabric_details.js 2016-04-22 17:28:15 +0000
80+++ src/maasserver/static/js/angular/controllers/fabric_details.js 2016-04-27 03:54:09 +0000
81@@ -34,13 +34,15 @@
82
83 // Called when the fabric has been loaded.
84 function fabricLoaded(fabric) {
85- $scope.fabric = fabric;
86- $scope.loaded = true;
87- $scope.vlans = FabricsManager.getVLANs($scope.fabric);
88- $scope.racks = getRackControllers();
89+ if(angular.isObject(fabric)) {
90+ $scope.fabric = fabric;
91+ $scope.loaded = true;
92+ $scope.vlans = FabricsManager.getVLANs($scope.fabric);
93+ $scope.racks = getRackControllers();
94
95- updateTitle();
96- updateVLANTable();
97+ updateTitle();
98+ updateVLANTable();
99+ }
100 }
101
102 // Return the rack controller objects attached to this Fabric. The
103@@ -80,6 +82,18 @@
104 };
105 rows.push(row);
106 });
107+ } else {
108+ // If there are no subnets, populate the row based on the
109+ // information we have (just the VLAN).
110+ var row = {
111+ vlan: vlan,
112+ vlan_name: VLANsManager.getName(vlan),
113+ subnet: null,
114+ subnet_name: null,
115+ space: null,
116+ space_name: null
117+ };
118+ rows.push(row);
119 }
120 });
121 $scope.rows = rows;
122@@ -129,8 +143,8 @@
123 FabricsManager.deleteFabric($scope.fabric).then(function() {
124 $scope.confirmingDelete = false;
125 $location.path("/fabrics");
126- }, function(error) {
127- $scope.error = $scope.convertPythonDictToErrorMsg(error);
128+ }, function(reply) {
129+ $scope.error = $scope.convertPythonDictToErrorMsg(reply.error);
130 });
131 };
132
133
134=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_fabric_details.js'
135--- src/maasserver/static/js/angular/controllers/tests/test_fabric_details.js 2016-03-25 13:52:27 +0000
136+++ src/maasserver/static/js/angular/controllers/tests/test_fabric_details.js 2016-04-27 03:54:09 +0000
137@@ -16,7 +16,7 @@
138 name: makeName("fabric")
139 };
140 FabricsManager._items.push(fabric);
141- return fabric;
142+ return fabric;
143 }
144
145 // Grab the needed angular pieces.
146@@ -51,14 +51,13 @@
147
148 // Makes the NodesListController
149 function makeController(loadManagerDefer) {
150- spyOn(UsersManager, "isSuperUser").and.returnValue(true);
151+ spyOn(UsersManager, "isSuperUser").and.returnValue(true);
152 var loadManagers = spyOn(ManagerHelperService, "loadManagers");
153 if(angular.isObject(loadManagerDefer)) {
154 loadManagers.and.returnValue(loadManagerDefer.promise);
155 } else {
156 loadManagers.and.returnValue($q.defer().promise);
157 }
158-
159 // Create the controller.
160 var controller = $controller("FabricDetailsController", {
161 $scope: $scope,
162@@ -74,8 +73,7 @@
163 ManagerHelperService: ManagerHelperService,
164 ErrorService: ErrorService
165 });
166-
167- return controller;
168+ return controller;
169 }
170
171 // Make the controller and resolve the setActiveItem call.
172@@ -87,7 +85,9 @@
173 var controller = makeController(defer);
174 $routeParams.fabric_id = fabric.id;
175
176+ $rootScope.$digest();
177 defer.resolve();
178+
179 $rootScope.$digest();
180 setActiveDefer.resolve(fabric);
181 $rootScope.$digest();
182@@ -174,6 +174,39 @@
183 expect($rootScope.title).toBe(fabric.name);
184 });
185
186+ it("updates $scope.rows with VLANs containing subnet(s)", function() {
187+ var spaces = [{ id: 0, name: "space-0" }];
188+ var vlans = [{ id: 1, name: "vlan4", vid: 4, fabric: fabric.id }];
189+ var subnets = [
190+ { id: 0, name:"subnet1", vlan: 1, space: 0, cidr: "10.20.0.0/16" }
191+ ];
192+ fabric.vlan_ids = [1];
193+ spaces[0].subnet_ids = [0];
194+ vlans[0].subnet_ids = [0];
195+ SpacesManager._items.push(spaces[0]);
196+ VLANsManager._items.push(vlans[0]);
197+ SubnetsManager._items.push(subnets[0]);
198+ var controller = makeControllerResolveSetActiveItem();
199+ $scope.$apply();
200+ $rootScope.$digest();
201+ var rows = $scope.rows;
202+ expect(rows[0].vlan).toBe(vlans[0]);
203+ expect(rows[0].subnet_name).toEqual("10.20.0.0/16 (subnet1)");
204+ expect(rows[0].space_name).toEqual("space-0");
205+ });
206+
207+ it("updates $scope.rows with VLANs containing no subnet(s)", function() {
208+ var vlans = [ { id: 1, name: "vlan4", vid: 4, fabric: fabric.id } ];
209+ fabric.vlan_ids = [1];
210+ VLANsManager._items.push(vlans[0]);
211+ var controller = makeControllerResolveSetActiveItem();
212+ $rootScope.$digest();
213+ var rows = $scope.rows;
214+ expect(rows[0].vlan).toBe(vlans[0]);
215+ expect(rows[0].subnet_name).toBe(null);
216+ expect(rows[0].space_name).toBe(null);
217+ });
218+
219 describe("canBeDeleted", function() {
220
221 it("returns false if fabric is null", function() {
222
223=== modified file 'src/maasserver/static/js/angular/factories/fabrics.js'
224--- src/maasserver/static/js/angular/factories/fabrics.js 2016-03-28 13:54:47 +0000
225+++ src/maasserver/static/js/angular/factories/fabrics.js 2016-04-27 03:54:09 +0000
226@@ -35,12 +35,14 @@
227 // instead you should watch this function.
228 FabricsManager.prototype.getVLANs = function(fabric) {
229 var vlans = [];
230- angular.forEach(fabric.vlan_ids, function(vlan_id) {
231- var vlan = VLANsManager.getItemFromList(vlan_id);
232- if(angular.isObject(vlan)) {
233- vlans.push(vlan);
234- }
235- });
236+ if(angular.isObject(fabric) && angular.isObject(fabric.vlan_ids)) {
237+ angular.forEach(fabric.vlan_ids, function(vlan_id) {
238+ var vlan = VLANsManager.getItemFromList(vlan_id);
239+ if(angular.isObject(vlan)) {
240+ vlans.push(vlan);
241+ }
242+ });
243+ }
244 return vlans;
245 };
246
247
248=== modified file 'src/maasserver/static/js/angular/services/managerhelper.js'
249--- src/maasserver/static/js/angular/services/managerhelper.js 2016-03-28 13:54:47 +0000
250+++ src/maasserver/static/js/angular/services/managerhelper.js 2016-04-27 03:54:09 +0000
251@@ -78,7 +78,7 @@
252 // Returns a printable version of the specified dictionary (useful
253 // for displaying an error to the user).
254 this.getPrintableString = function(dict, showNames) {
255- var result = '';
256+ var result = '';
257 angular.forEach(dict, function(value, key) {
258 var error = dict[key];
259 if(showNames === true) {
260
261=== modified file 'src/maasserver/static/partials/fabric-details.html'
262--- src/maasserver/static/partials/fabric-details.html 2016-03-21 22:46:23 +0000
263+++ src/maasserver/static/partials/fabric-details.html 2016-04-27 03:54:09 +0000
264@@ -44,30 +44,6 @@
265 </div>
266 </header>
267 <div data-ng-show="!loading">
268- <div class="page-header__dropdown border ng-hide" data-ng-show="confirmingDelete">
269- <div class="page-header__feedback twelve-col no-margin-bottom">
270- <div class="twelve-col ng-hide" data-ng-show="error">
271- <p class="page-header__feedback-message info">{$ error $}</p>
272- </div>
273- <div class="inner-wrapper">
274- <div class="twelve-col">
275- <div class="table">
276- <div class="table__row table__row--no-hover">
277- <div class="form-inline">
278- <div class="left">
279- Are you sure you want to delete this fabric?
280- </div>
281- <div class="right">
282- <a data-ng-click="cancelDeleteButton()">Cancel</a>
283- <button class="cta-ubuntu secondary" data-ng-click="deleteConfirmButton()">Confirm</button>
284- </div>
285- </div>
286- </div>
287- </div>
288- </div>
289- </div>
290- </div>
291- </div>
292 <section class="row">
293 <div class="inner-wrapper">
294 <div class="tweleve-col">
295
296=== modified file 'src/maasserver/static/partials/subnet-details.html'
297--- src/maasserver/static/partials/subnet-details.html 2016-04-22 17:28:15 +0000
298+++ src/maasserver/static/partials/subnet-details.html 2016-04-27 03:54:09 +0000
299@@ -265,7 +265,8 @@
300 <table class="table-listing">
301 <thead>
302 <tr class="table-listing__row">
303- <th class="table-listing__header table-col--40">IP Address</th>
304+ <th class="table-listing__header table-col--20">IP Address</th>
305+ <th class="table-listing__header table-col--20">Node</th>
306 <th class="table-listing__header table-col--10">Owner</th>
307 <th class="table-listing__header table-col--10">Usage</th>
308 <th class="table-listing__header table-col--15">Type</th>
309@@ -284,7 +285,15 @@
310 </thead>
311 <tbody>
312 <tr data-ng-repeat="ip in subnet.ip_addresses">
313- <td class="table-col--40">{$ ip.ip $}</td>
314+ <td class="table-col--20">{$ ip.ip $}</td>
315+ <td class="table-col--20" data-ng-switch="ip.node_summary.node_type">
316+ <span data-ng-switch-when="0"><a href="#/node/{$ ip.node_summary.system_id $}">{$ ip.node_summary.fqdn $}</a></span>
317+ <span data-ng-switch-when="1">{$ ip.node_summary.fqdn $}</span>
318+ <span data-ng-switch-when="2"><a href="#/node/controller/{$ ip.node_summary.system_id $}">{$ ip.node_summary.fqdn $}</a></span>
319+ <span data-ng-switch-when="3"><a href="#/node/controller/{$ ip.node_summary.system_id $}">{$ ip.node_summary.fqdn $}</a></span>
320+ <span data-ng-switch-when="4"><a href="#/node/controller/{$ ip.node_summary.system_id $}">{$ ip.node_summary.fqdn $}</a></span>
321+ <span data-ng-switch-default>Unknown</span>
322+ </td>
323 <td class="table-col--10">{$ ip.user ? ip.user : "MAAS" $}</td>
324 <td class="table-col--10" data-ng-switch="ip.node_summary.node_type">
325 <span data-ng-switch-when="0">Machine</span>