=== modified file 'src/maasserver/models/fabric.py' --- src/maasserver/models/fabric.py 2016-04-11 16:23:26 +0000 +++ src/maasserver/models/fabric.py 2016-04-27 03:54:09 +0000 @@ -26,6 +26,7 @@ from maasserver import DefaultMeta from maasserver.models.cleansave import CleanSave from maasserver.models.interface import Interface +from maasserver.models.subnet import Subnet from maasserver.models.timestampedmodel import TimestampedModel from maasserver.utils.orm import MAASQueriesMixin @@ -193,11 +194,20 @@ if self.is_default(): raise ValidationError( "This fabric is the default fabric, it cannot be deleted.") - # Circular imports. + if Subnet.objects.filter(vlan__fabric=self).exists(): + subnets = Subnet.objects.filter(vlan__fabric=self).order_by( + 'cidr') + descriptions = [str(subnet.cidr) for subnet in subnets] + raise ValidationError( + "Can't delete fabric; the following subnets are " + "still present: %s" % (", ".join(descriptions))) if Interface.objects.filter(vlan__fabric=self).exists(): + interfaces = Interface.objects.filter(vlan__fabric=self).order_by( + 'node', 'name') + descriptions = [iface.get_log_string() for iface in interfaces] raise ValidationError( - "Can't delete fabric: interfaces are connected to VLANs from " - "this fabric.") + "Can't delete fabric; the following interfaces are " + "still connected: %s" % (", ".join(descriptions))) super(Fabric, self).delete() def _create_default_vlan(self): === modified file 'src/maasserver/models/tests/test_fabric.py' --- src/maasserver/models/tests/test_fabric.py 2016-04-11 16:23:26 +0000 +++ src/maasserver/models/tests/test_fabric.py 2016-04-27 03:54:09 +0000 @@ -27,7 +27,10 @@ from maasserver.testing.factory import factory from maasserver.testing.testcase import MAASServerTestCase from maastesting.matchers import MockCalledOnce -from testtools.matchers import MatchesStructure +from testtools.matchers import ( + Contains, + MatchesStructure, +) class TestFabricManagerGetFabricOr404(MAASServerTestCase): @@ -239,10 +242,20 @@ INTERFACE_TYPE.PHYSICAL, vlan=fabric.get_default_vlan()) error = self.assertRaises(ValidationError, fabric.delete) - self.assertEqual( - "Can't delete fabric: interfaces are connected to VLANs from this " - "fabric.", - error.message) + self.assertThat(error.message, Contains( + "Can't delete fabric; the following interfaces are " + "still connected: ")) + + def test_cant_delete_fabric_if_connected_to_subnet(self): + fabric = factory.make_Fabric() + factory.make_Subnet(vlan=fabric.get_default_vlan()) + factory.make_Interface( + INTERFACE_TYPE.PHYSICAL, + vlan=fabric.get_default_vlan()) + error = self.assertRaises(ValidationError, fabric.delete) + self.assertThat(error.message, Contains( + "Can't delete fabric; the following subnets are " + "still present: ")) def test_cant_delete_default_fabric(self): default_fabric = Fabric.objects.get_default_fabric() === modified file 'src/maasserver/static/js/angular/controllers/fabric_details.js' --- src/maasserver/static/js/angular/controllers/fabric_details.js 2016-04-22 17:28:15 +0000 +++ src/maasserver/static/js/angular/controllers/fabric_details.js 2016-04-27 03:54:09 +0000 @@ -34,13 +34,15 @@ // Called when the fabric has been loaded. function fabricLoaded(fabric) { - $scope.fabric = fabric; - $scope.loaded = true; - $scope.vlans = FabricsManager.getVLANs($scope.fabric); - $scope.racks = getRackControllers(); + if(angular.isObject(fabric)) { + $scope.fabric = fabric; + $scope.loaded = true; + $scope.vlans = FabricsManager.getVLANs($scope.fabric); + $scope.racks = getRackControllers(); - updateTitle(); - updateVLANTable(); + updateTitle(); + updateVLANTable(); + } } // Return the rack controller objects attached to this Fabric. The @@ -80,6 +82,18 @@ }; rows.push(row); }); + } else { + // If there are no subnets, populate the row based on the + // information we have (just the VLAN). + var row = { + vlan: vlan, + vlan_name: VLANsManager.getName(vlan), + subnet: null, + subnet_name: null, + space: null, + space_name: null + }; + rows.push(row); } }); $scope.rows = rows; @@ -129,8 +143,8 @@ FabricsManager.deleteFabric($scope.fabric).then(function() { $scope.confirmingDelete = false; $location.path("/fabrics"); - }, function(error) { - $scope.error = $scope.convertPythonDictToErrorMsg(error); + }, function(reply) { + $scope.error = $scope.convertPythonDictToErrorMsg(reply.error); }); }; === modified file 'src/maasserver/static/js/angular/controllers/tests/test_fabric_details.js' --- src/maasserver/static/js/angular/controllers/tests/test_fabric_details.js 2016-03-25 13:52:27 +0000 +++ src/maasserver/static/js/angular/controllers/tests/test_fabric_details.js 2016-04-27 03:54:09 +0000 @@ -16,7 +16,7 @@ name: makeName("fabric") }; FabricsManager._items.push(fabric); - return fabric; + return fabric; } // Grab the needed angular pieces. @@ -51,14 +51,13 @@ // Makes the NodesListController function makeController(loadManagerDefer) { - spyOn(UsersManager, "isSuperUser").and.returnValue(true); + spyOn(UsersManager, "isSuperUser").and.returnValue(true); var loadManagers = spyOn(ManagerHelperService, "loadManagers"); if(angular.isObject(loadManagerDefer)) { loadManagers.and.returnValue(loadManagerDefer.promise); } else { loadManagers.and.returnValue($q.defer().promise); } - // Create the controller. var controller = $controller("FabricDetailsController", { $scope: $scope, @@ -74,8 +73,7 @@ ManagerHelperService: ManagerHelperService, ErrorService: ErrorService }); - - return controller; + return controller; } // Make the controller and resolve the setActiveItem call. @@ -87,7 +85,9 @@ var controller = makeController(defer); $routeParams.fabric_id = fabric.id; + $rootScope.$digest(); defer.resolve(); + $rootScope.$digest(); setActiveDefer.resolve(fabric); $rootScope.$digest(); @@ -174,6 +174,39 @@ expect($rootScope.title).toBe(fabric.name); }); + it("updates $scope.rows with VLANs containing subnet(s)", function() { + var spaces = [{ id: 0, name: "space-0" }]; + var vlans = [{ id: 1, name: "vlan4", vid: 4, fabric: fabric.id }]; + var subnets = [ + { id: 0, name:"subnet1", vlan: 1, space: 0, cidr: "10.20.0.0/16" } + ]; + fabric.vlan_ids = [1]; + spaces[0].subnet_ids = [0]; + vlans[0].subnet_ids = [0]; + SpacesManager._items.push(spaces[0]); + VLANsManager._items.push(vlans[0]); + SubnetsManager._items.push(subnets[0]); + var controller = makeControllerResolveSetActiveItem(); + $scope.$apply(); + $rootScope.$digest(); + var rows = $scope.rows; + expect(rows[0].vlan).toBe(vlans[0]); + expect(rows[0].subnet_name).toEqual("10.20.0.0/16 (subnet1)"); + expect(rows[0].space_name).toEqual("space-0"); + }); + + it("updates $scope.rows with VLANs containing no subnet(s)", function() { + var vlans = [ { id: 1, name: "vlan4", vid: 4, fabric: fabric.id } ]; + fabric.vlan_ids = [1]; + VLANsManager._items.push(vlans[0]); + var controller = makeControllerResolveSetActiveItem(); + $rootScope.$digest(); + var rows = $scope.rows; + expect(rows[0].vlan).toBe(vlans[0]); + expect(rows[0].subnet_name).toBe(null); + expect(rows[0].space_name).toBe(null); + }); + describe("canBeDeleted", function() { it("returns false if fabric is null", function() { === modified file 'src/maasserver/static/js/angular/factories/fabrics.js' --- src/maasserver/static/js/angular/factories/fabrics.js 2016-03-28 13:54:47 +0000 +++ src/maasserver/static/js/angular/factories/fabrics.js 2016-04-27 03:54:09 +0000 @@ -35,12 +35,14 @@ // instead you should watch this function. FabricsManager.prototype.getVLANs = function(fabric) { var vlans = []; - angular.forEach(fabric.vlan_ids, function(vlan_id) { - var vlan = VLANsManager.getItemFromList(vlan_id); - if(angular.isObject(vlan)) { - vlans.push(vlan); - } - }); + if(angular.isObject(fabric) && angular.isObject(fabric.vlan_ids)) { + angular.forEach(fabric.vlan_ids, function(vlan_id) { + var vlan = VLANsManager.getItemFromList(vlan_id); + if(angular.isObject(vlan)) { + vlans.push(vlan); + } + }); + } return vlans; }; === modified file 'src/maasserver/static/js/angular/services/managerhelper.js' --- src/maasserver/static/js/angular/services/managerhelper.js 2016-03-28 13:54:47 +0000 +++ src/maasserver/static/js/angular/services/managerhelper.js 2016-04-27 03:54:09 +0000 @@ -78,7 +78,7 @@ // Returns a printable version of the specified dictionary (useful // for displaying an error to the user). this.getPrintableString = function(dict, showNames) { - var result = ''; + var result = ''; angular.forEach(dict, function(value, key) { var error = dict[key]; if(showNames === true) { === modified file 'src/maasserver/static/partials/fabric-details.html' --- src/maasserver/static/partials/fabric-details.html 2016-03-21 22:46:23 +0000 +++ src/maasserver/static/partials/fabric-details.html 2016-04-27 03:54:09 +0000 @@ -44,30 +44,6 @@
IP Address | +IP Address | +Node | Owner | Usage | Type | @@ -284,7 +285,15 @@
---|---|---|---|---|---|
{$ ip.ip $} | +{$ ip.ip $} | ++ {$ ip.node_summary.fqdn $} + {$ ip.node_summary.fqdn $} + {$ ip.node_summary.fqdn $} + {$ ip.node_summary.fqdn $} + {$ ip.node_summary.fqdn $} + Unknown + | {$ ip.user ? ip.user : "MAAS" $} | Machine |