Merge lp:~mpontillo/maas/delete-fabric-cleanup into lp:~maas-committers/maas/trunk
- delete-fabric-cleanup
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
Description of the change
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.
MAAS Lander (maas-lander) wrote : | # |
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://
Get:2 http://
Hit:3 http://
Get:4 http://
Fetched 184 kB in 0s (381 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
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.
bind9utils is already the newest version (1:9.10.
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.20160115ubun
distro-info is already the newest version (0.14build1).
dnsutils is already the newest version (1:9.10.
firefox is already the newest version (45.0.2+
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
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> |
Looks good other than the missing test. Please add the test before landing, not going to block you on the missing test.