diff --git a/src/maasserver/api/interfaces.py b/src/maasserver/api/interfaces.py index bd55ef6..5e96ff4 100644 --- a/src/maasserver/api/interfaces.py +++ b/src/maasserver/api/interfaces.py @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the +# Copyright 2015-2017 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """API handlers: `Interface`.""" @@ -92,7 +92,7 @@ INTERFACES_PREFETCH = [ 'children_relationships__child__vlan'), ] -ALLOWED_STATES = (NODE_STATUS.READY, NODE_STATUS.BROKEN) +ALLOWED_STATES = (NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN) def raise_error_for_invalid_state_on_allocated_operations( @@ -112,8 +112,8 @@ def raise_error_for_invalid_state_on_allocated_operations( allowed.extend(extra_states) if node.status not in allowed: raise NodeStateViolation( - "Cannot %s interface because the machine is not Ready or " - "Broken." % operation) + "Cannot %s interface because the machine is not Ready, Allocated, " + "or Broken." % operation) def raise_error_if_controller(node, operation): @@ -327,16 +327,8 @@ class InterfacesHandler(OperationsHandler): """ machine = Machine.objects.get_node_or_404( system_id, request.user, NODE_PERMISSION.EDIT) - if machine.status not in ( - NODE_STATUS.READY, NODE_STATUS.BROKEN, NODE_STATUS.ALLOCATED): - raise NodeStateViolation( - "Cannot create bridge interface because the machine is not " - "Ready, Broken, or Allocated.") - if (not request.user.is_superuser and - machine.status in ALLOWED_STATES): - raise NodeStateViolation( - "Machine must be alloacted to '%s' to allow bridge " - "creation." % request.user.username) + raise_error_for_invalid_state_on_allocated_operations( + machine, request.user, "create bridge") # Cast parent to parents to make it easier on the user and to make it # work with the form. request.data = request.data.copy() diff --git a/src/maasserver/api/tests/test_interfaces.py b/src/maasserver/api/tests/test_interfaces.py index 6fb1b9f..e6c91a1 100644 --- a/src/maasserver/api/tests/test_interfaces.py +++ b/src/maasserver/api/tests/test_interfaces.py @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the +# Copyright 2015-2017 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Tests for NodeInterfaces API.""" @@ -43,7 +43,6 @@ STATUSES = ( NODE_STATUS.FAILED_COMMISSIONING, NODE_STATUS.MISSING, NODE_STATUS.RESERVED, - NODE_STATUS.ALLOCATED, NODE_STATUS.DEPLOYING, NODE_STATUS.DEPLOYED, NODE_STATUS.RETIRED, @@ -241,7 +240,8 @@ class TestInterfacesAPI(APITestCase.ForUser): def test_create_physical_disabled(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(status=status) mac = factory.make_mac_address() name = factory.make_name("eth") @@ -342,7 +342,8 @@ class TestInterfacesAPI(APITestCase.ForUser): def test_create_bond(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(status=status) vlan = factory.make_VLAN() parent_1_iface = factory.make_Interface( @@ -931,7 +932,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): def test_update_physical_interface(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(status=status) interface = factory.make_Interface( INTERFACE_TYPE.PHYSICAL, node=node) @@ -969,7 +971,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): def test_update_bond_interface(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(status=status) bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface( node) @@ -984,7 +987,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): def test_update_vlan_interface(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(status=status) bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface( node) @@ -1031,7 +1035,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): def test_delete_deletes_interface(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() uri = get_interface_uri(interface) @@ -1086,7 +1091,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): # This just tests that the form is saved and the updated interface # is returned. self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() uri = get_interface_uri(interface) @@ -1324,7 +1330,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): def test_link_subnet_raises_error(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() uri = get_interface_uri(interface) @@ -1366,7 +1373,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): # This just tests that the form is saved and the updated interface # is returned. self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() subnet = factory.make_Subnet() @@ -1403,7 +1411,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): def test_unlink_subnet_raises_error(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() uri = get_interface_uri(interface) @@ -1444,7 +1453,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): # This just tests that the form is saved and the updated interface # is returned. self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() subnet = factory.make_Subnet() @@ -1486,7 +1496,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): # The form that is used is fully tested in test_forms_interface_link. # This just tests that the form is saved and the node link is created. self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() network = factory.make_ipv4_network() @@ -1508,7 +1519,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): # The form that is used is fully tested in test_forms_interface_link. # This just tests that the form is saved and the node link is created. self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() network = factory.make_ipv6_network() @@ -1528,7 +1540,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser): def test_set_default_gateway_raises_error(self): self.become_admin() - for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): + for status in ( + NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN): node = factory.make_Node(interface=True, status=status) interface = node.get_boot_interface() uri = get_interface_uri(interface) diff --git a/src/maasserver/static/js/angular/controllers/node_details_networking.js b/src/maasserver/static/js/angular/controllers/node_details_networking.js index 84b7739..4067dab 100644 --- a/src/maasserver/static/js/angular/controllers/node_details_networking.js +++ b/src/maasserver/static/js/angular/controllers/node_details_networking.js @@ -1,4 +1,4 @@ -/* Copyright 2015-2016 Canonical Ltd. This software is licensed under the +/* Copyright 2015-2017 Canonical Ltd. This software is licensed under the * GNU Affero General Public License version 3 (see the file LICENSE). * * MAAS Node Networking Controller @@ -568,30 +568,6 @@ angular.module('MAAS').controller('NodeNetworkingController', [ updateLoaded(); }; - // Return true if the Node is Ready (or Broken) - $scope.isNodeEditingAllowed = function() { - if (!$scope.isSuperUser()) { - // If the user is not the superuser, pretend it's not Ready. - return false; - } - if ($scope.$parent.isDevice) { - // Devices are always Ready, for our purposes, for now. - return true; - } - if ($scope.$parent.isController) { - // Controllers are always Ready, for our purposes. - return true; - } - if (angular.isObject($scope.node) && - ["Ready", "Broken"].indexOf($scope.node.status) === -1) { - // If a non-controller node is not Ready or Broken, then no - // editing networking. - return false; - } - // All is well, let them edit. - return true; - }; - // Return true if only the name or mac address of an interface can // be edited. $scope.isLimitedEditingAllowed = function(nic) { @@ -623,9 +599,10 @@ angular.module('MAAS').controller('NodeNetworkingController', [ return false; } if (angular.isObject($scope.node) && - ["Ready", "Broken"].indexOf($scope.node.status) === -1) { - // If a non-controller node is not ready or broken, disable - // networking panel. + ["Ready", "Allocated", "Broken"].indexOf( + $scope.node.status) === -1) { + // If a non-controller node is not ready allocated, or broken, + // disable networking panel. return true; } // User must be a superuser and the node must be @@ -1090,7 +1067,7 @@ angular.module('MAAS').controller('NodeNetworkingController', [ $scope.canAddAliasOrVLAN = function(nic) { if($scope.$parent.isController) { return false; - } else if (!$scope.isNodeEditingAllowed()) { + } else if ($scope.isAllNetworkingDisabled()) { return false; } else { return $scope.canAddAlias(nic) || $scope.canAddVLAN(nic); @@ -1148,7 +1125,7 @@ angular.module('MAAS').controller('NodeNetworkingController', [ $scope.canBeRemoved = function() { return ( !$scope.$parent.isController && - $scope.isNodeEditingAllowed()); + !$scope.isAllNetworkingDisabled()); }; // Enter remove mode. diff --git a/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js b/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js index a51834c..77967e7 100644 --- a/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js +++ b/src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js @@ -1,4 +1,4 @@ -/* Copyright 2015-2016 Canonical Ltd. This software is licensed under the +/* Copyright 2015-2017 Canonical Ltd. This software is licensed under the * GNU Affero General Public License version 3 (see the file LICENSE). * * Unit tests for NodeNetworkingController. @@ -2146,7 +2146,7 @@ describe("NodeNetworkingController", function() { it("returns false if isController", function() { var controller = makeController(); $parentScope.isController = true; - spyOn($scope, "isNodeEditingAllowed").and.returnValue(true); + spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false); spyOn($scope, "canAddAlias").and.returnValue(true); spyOn($scope, "canAddVLAN").and.returnValue(true); expect($scope.canAddAliasOrVLAN({})).toBe(false); @@ -2155,7 +2155,7 @@ describe("NodeNetworkingController", function() { it("returns false if no node editing", function() { var controller = makeController(); $parentScope.isController = false; - spyOn($scope, "isNodeEditingAllowed").and.returnValue(false); + spyOn($scope, "isAllNetworkingDisabled").and.returnValue(true); spyOn($scope, "canAddAlias").and.returnValue(true); spyOn($scope, "canAddVLAN").and.returnValue(true); expect($scope.canAddAliasOrVLAN({})).toBe(false); @@ -2164,7 +2164,7 @@ describe("NodeNetworkingController", function() { it("returns true if can edit alias", function() { var controller = makeController(); $parentScope.isController = false; - spyOn($scope, "isNodeEditingAllowed").and.returnValue(true); + spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false); spyOn($scope, "canAddAlias").and.returnValue(true); spyOn($scope, "canAddVLAN").and.returnValue(false); expect($scope.canAddAliasOrVLAN({})).toBe(true); @@ -2173,7 +2173,7 @@ describe("NodeNetworkingController", function() { it("returns true if can edit VLAN", function() { var controller = makeController(); $parentScope.isController = false; - spyOn($scope, "isNodeEditingAllowed").and.returnValue(true); + spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false); spyOn($scope, "canAddAlias").and.returnValue(false); spyOn($scope, "canAddVLAN").and.returnValue(true); expect($scope.canAddAliasOrVLAN({})).toBe(true); @@ -2523,21 +2523,21 @@ describe("NodeNetworkingController", function() { it("false if isController", function() { var controller = makeController(); $parentScope.isController = true; - spyOn($scope, "isNodeEditingAllowed").and.returnValue(true); + spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false); expect($scope.canBeRemoved()).toBe(false); }); it("false if no node editing", function() { var controller = makeController(); $parentScope.isController = false; - spyOn($scope, "isNodeEditingAllowed").and.returnValue(false); + spyOn($scope, "isAllNetworkingDisabled").and.returnValue(true); expect($scope.canBeRemoved()).toBe(false); }); it("true if node can be edited", function() { var controller = makeController(); $parentScope.isController = false; - spyOn($scope, "isNodeEditingAllowed").and.returnValue(true); + spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false); expect($scope.canBeRemoved()).toBe(true); }); }); diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html index 0066e2b..62d379a 100755 --- a/src/maasserver/static/partials/node-details.html +++ b/src/maasserver/static/partials/node-details.html @@ -656,7 +656,7 @@
Interface configuration cannot be modified unless the - node is Ready or Broken.
+ node is Ready, Allocated, or Broken.@@ -698,15 +698,15 @@