Merge ~ltrager/maas:lp1650396 into maas:master

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: fa3a3006598c6fedb68ac6e7fcf957a5e9fd36da
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:lp1650396
Merge into: maas:master
Diff against target: 441 lines (+61/-79)
5 files modified
src/maasserver/api/interfaces.py (+6/-14)
src/maasserver/api/tests/test_interfaces.py (+29/-16)
src/maasserver/static/js/angular/controllers/node_details_networking.js (+7/-30)
src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js (+8/-8)
src/maasserver/static/partials/node-details.html (+11/-11)
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Needs Information
Данило Шеган (community) Approve
Review via email: mp+326332@code.launchpad.net

Commit message

Allow editing of network settings while a machine is in an allocated state.

This fixes LP:1650396

To post a comment you must log in.
Revision history for this message
Данило Шеган (danilo) wrote :

Looks good, but I do have a question about dropping isLimitedEditingAllowed removal: it seems to be about "Deployed" status, so I think we still want to keep that. Did I miss something there (the bug only talks about allowing editing in allocated state)?

I also ain't too happy about how we create multiple test scenarios with a for loop over 3 different node statuses, but you didn't introduce this originally, so no need to fix it either, though I have to rant a bit about it :-)

review: Needs Fixing
~ltrager/maas:lp1650396 updated
a75bb01... by Lee Trager

Merge branch 'master' into lp1650396

bcf2525... by Lee Trager

Squashed commit of the following:

commit a4f9627af7727bb96f4aad276c7f4a8d88b06d6a
Author: Lee Trager <email address hidden>
Date: Tue Jun 27 19:31:06 2017 +0000

    Remove link-junk from Makefile

    make lint ran link-junk which checks for bzr files accidentally committed.
    MAAS is no longer in bzr so this check is no longer needed and keeping it
    causes bzr to complain its not being run from a bzr branch.

0e05b6c... by Lee Trager

danilo fixes

d5b995b... by Lee Trager

danilo fixes

Revision history for this message
Lee Trager (ltrager) wrote :

Thanks for the review. I missed that MAAS allows editing of an interface name and MAC while deployed. I've brought back isLimitedEditingAllowed to restore this functionality. Now all this should change is allowing interfaces to be edited while the node is allocated.

Revision history for this message
Данило Шеган (danilo) wrote :

Looks good, thanks. It seems there's one change from isLimited... to isAllNetworking... that you missed to revert. Not blocking on it, though.

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

this needs a backport to 2.2

Revision history for this message
Andres Rodriguez (andreserl) wrote :

I have a comment inline

review: Needs Information
~ltrager/maas:lp1650396 updated
2cece51... by Lee Trager

Danillo fixes

fa3a300... by Lee Trager

Merge branch 'lp1650396' of git+ssh://git.launchpad.net/~ltrager/maas into lp1650396

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""API handlers: `Interface`."""4"""API handlers: `Interface`."""
@@ -92,7 +92,7 @@ INTERFACES_PREFETCH = [
92 'children_relationships__child__vlan'),92 'children_relationships__child__vlan'),
93]93]
9494
95ALLOWED_STATES = (NODE_STATUS.READY, NODE_STATUS.BROKEN)95ALLOWED_STATES = (NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN)
9696
9797
98def raise_error_for_invalid_state_on_allocated_operations(98def raise_error_for_invalid_state_on_allocated_operations(
@@ -112,8 +112,8 @@ def raise_error_for_invalid_state_on_allocated_operations(
112 allowed.extend(extra_states)112 allowed.extend(extra_states)
113 if node.status not in allowed:113 if node.status not in allowed:
114 raise NodeStateViolation(114 raise NodeStateViolation(
115 "Cannot %s interface because the machine is not Ready or "115 "Cannot %s interface because the machine is not Ready, Allocated, "
116 "Broken." % operation)116 "or Broken." % operation)
117117
118118
119def raise_error_if_controller(node, operation):119def raise_error_if_controller(node, operation):
@@ -327,16 +327,8 @@ class InterfacesHandler(OperationsHandler):
327 """327 """
328 machine = Machine.objects.get_node_or_404(328 machine = Machine.objects.get_node_or_404(
329 system_id, request.user, NODE_PERMISSION.EDIT)329 system_id, request.user, NODE_PERMISSION.EDIT)
330 if machine.status not in (330 raise_error_for_invalid_state_on_allocated_operations(
331 NODE_STATUS.READY, NODE_STATUS.BROKEN, NODE_STATUS.ALLOCATED):331 machine, request.user, "create bridge")
332 raise NodeStateViolation(
333 "Cannot create bridge interface because the machine is not "
334 "Ready, Broken, or Allocated.")
335 if (not request.user.is_superuser and
336 machine.status in ALLOWED_STATES):
337 raise NodeStateViolation(
338 "Machine must be alloacted to '%s' to allow bridge "
339 "creation." % request.user.username)
340 # Cast parent to parents to make it easier on the user and to make it332 # Cast parent to parents to make it easier on the user and to make it
341 # work with the form.333 # work with the form.
342 request.data = request.data.copy()334 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 @@
1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the1# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for NodeInterfaces API."""4"""Tests for NodeInterfaces API."""
@@ -43,7 +43,6 @@ STATUSES = (
43 NODE_STATUS.FAILED_COMMISSIONING,43 NODE_STATUS.FAILED_COMMISSIONING,
44 NODE_STATUS.MISSING,44 NODE_STATUS.MISSING,
45 NODE_STATUS.RESERVED,45 NODE_STATUS.RESERVED,
46 NODE_STATUS.ALLOCATED,
47 NODE_STATUS.DEPLOYING,46 NODE_STATUS.DEPLOYING,
48 NODE_STATUS.DEPLOYED,47 NODE_STATUS.DEPLOYED,
49 NODE_STATUS.RETIRED,48 NODE_STATUS.RETIRED,
@@ -241,7 +240,8 @@ class TestInterfacesAPI(APITestCase.ForUser):
241240
242 def test_create_physical_disabled(self):241 def test_create_physical_disabled(self):
243 self.become_admin()242 self.become_admin()
244 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):243 for status in (
244 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
245 node = factory.make_Node(status=status)245 node = factory.make_Node(status=status)
246 mac = factory.make_mac_address()246 mac = factory.make_mac_address()
247 name = factory.make_name("eth")247 name = factory.make_name("eth")
@@ -342,7 +342,8 @@ class TestInterfacesAPI(APITestCase.ForUser):
342342
343 def test_create_bond(self):343 def test_create_bond(self):
344 self.become_admin()344 self.become_admin()
345 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):345 for status in (
346 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
346 node = factory.make_Node(status=status)347 node = factory.make_Node(status=status)
347 vlan = factory.make_VLAN()348 vlan = factory.make_VLAN()
348 parent_1_iface = factory.make_Interface(349 parent_1_iface = factory.make_Interface(
@@ -931,7 +932,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
931932
932 def test_update_physical_interface(self):933 def test_update_physical_interface(self):
933 self.become_admin()934 self.become_admin()
934 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):935 for status in (
936 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
935 node = factory.make_Node(status=status)937 node = factory.make_Node(status=status)
936 interface = factory.make_Interface(938 interface = factory.make_Interface(
937 INTERFACE_TYPE.PHYSICAL, node=node)939 INTERFACE_TYPE.PHYSICAL, node=node)
@@ -969,7 +971,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
969971
970 def test_update_bond_interface(self):972 def test_update_bond_interface(self):
971 self.become_admin()973 self.become_admin()
972 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):974 for status in (
975 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
973 node = factory.make_Node(status=status)976 node = factory.make_Node(status=status)
974 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(977 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(
975 node)978 node)
@@ -984,7 +987,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
984987
985 def test_update_vlan_interface(self):988 def test_update_vlan_interface(self):
986 self.become_admin()989 self.become_admin()
987 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):990 for status in (
991 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
988 node = factory.make_Node(status=status)992 node = factory.make_Node(status=status)
989 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(993 bond, [nic_0, nic_1], [vlan_10, vlan_11] = make_complex_interface(
990 node)994 node)
@@ -1031,7 +1035,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
10311035
1032 def test_delete_deletes_interface(self):1036 def test_delete_deletes_interface(self):
1033 self.become_admin()1037 self.become_admin()
1034 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1038 for status in (
1039 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1035 node = factory.make_Node(interface=True, status=status)1040 node = factory.make_Node(interface=True, status=status)
1036 interface = node.get_boot_interface()1041 interface = node.get_boot_interface()
1037 uri = get_interface_uri(interface)1042 uri = get_interface_uri(interface)
@@ -1086,7 +1091,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
1086 # This just tests that the form is saved and the updated interface1091 # This just tests that the form is saved and the updated interface
1087 # is returned.1092 # is returned.
1088 self.become_admin()1093 self.become_admin()
1089 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1094 for status in (
1095 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1090 node = factory.make_Node(interface=True, status=status)1096 node = factory.make_Node(interface=True, status=status)
1091 interface = node.get_boot_interface()1097 interface = node.get_boot_interface()
1092 uri = get_interface_uri(interface)1098 uri = get_interface_uri(interface)
@@ -1324,7 +1330,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
13241330
1325 def test_link_subnet_raises_error(self):1331 def test_link_subnet_raises_error(self):
1326 self.become_admin()1332 self.become_admin()
1327 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1333 for status in (
1334 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1328 node = factory.make_Node(interface=True, status=status)1335 node = factory.make_Node(interface=True, status=status)
1329 interface = node.get_boot_interface()1336 interface = node.get_boot_interface()
1330 uri = get_interface_uri(interface)1337 uri = get_interface_uri(interface)
@@ -1366,7 +1373,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
1366 # This just tests that the form is saved and the updated interface1373 # This just tests that the form is saved and the updated interface
1367 # is returned.1374 # is returned.
1368 self.become_admin()1375 self.become_admin()
1369 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1376 for status in (
1377 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1370 node = factory.make_Node(interface=True, status=status)1378 node = factory.make_Node(interface=True, status=status)
1371 interface = node.get_boot_interface()1379 interface = node.get_boot_interface()
1372 subnet = factory.make_Subnet()1380 subnet = factory.make_Subnet()
@@ -1403,7 +1411,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
14031411
1404 def test_unlink_subnet_raises_error(self):1412 def test_unlink_subnet_raises_error(self):
1405 self.become_admin()1413 self.become_admin()
1406 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1414 for status in (
1415 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1407 node = factory.make_Node(interface=True, status=status)1416 node = factory.make_Node(interface=True, status=status)
1408 interface = node.get_boot_interface()1417 interface = node.get_boot_interface()
1409 uri = get_interface_uri(interface)1418 uri = get_interface_uri(interface)
@@ -1444,7 +1453,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
1444 # This just tests that the form is saved and the updated interface1453 # This just tests that the form is saved and the updated interface
1445 # is returned.1454 # is returned.
1446 self.become_admin()1455 self.become_admin()
1447 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1456 for status in (
1457 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1448 node = factory.make_Node(interface=True, status=status)1458 node = factory.make_Node(interface=True, status=status)
1449 interface = node.get_boot_interface()1459 interface = node.get_boot_interface()
1450 subnet = factory.make_Subnet()1460 subnet = factory.make_Subnet()
@@ -1486,7 +1496,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
1486 # The form that is used is fully tested in test_forms_interface_link.1496 # The form that is used is fully tested in test_forms_interface_link.
1487 # This just tests that the form is saved and the node link is created.1497 # This just tests that the form is saved and the node link is created.
1488 self.become_admin()1498 self.become_admin()
1489 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1499 for status in (
1500 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1490 node = factory.make_Node(interface=True, status=status)1501 node = factory.make_Node(interface=True, status=status)
1491 interface = node.get_boot_interface()1502 interface = node.get_boot_interface()
1492 network = factory.make_ipv4_network()1503 network = factory.make_ipv4_network()
@@ -1508,7 +1519,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
1508 # The form that is used is fully tested in test_forms_interface_link.1519 # The form that is used is fully tested in test_forms_interface_link.
1509 # This just tests that the form is saved and the node link is created.1520 # This just tests that the form is saved and the node link is created.
1510 self.become_admin()1521 self.become_admin()
1511 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1522 for status in (
1523 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1512 node = factory.make_Node(interface=True, status=status)1524 node = factory.make_Node(interface=True, status=status)
1513 interface = node.get_boot_interface()1525 interface = node.get_boot_interface()
1514 network = factory.make_ipv6_network()1526 network = factory.make_ipv6_network()
@@ -1528,7 +1540,8 @@ class TestNodeInterfaceAPI(APITransactionTestCase.ForUser):
15281540
1529 def test_set_default_gateway_raises_error(self):1541 def test_set_default_gateway_raises_error(self):
1530 self.become_admin()1542 self.become_admin()
1531 for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN):1543 for status in (
1544 NODE_STATUS.READY, NODE_STATUS.ALLOCATED, NODE_STATUS.BROKEN):
1532 node = factory.make_Node(interface=True, status=status)1545 node = factory.make_Node(interface=True, status=status)
1533 interface = node.get_boot_interface()1546 interface = node.get_boot_interface()
1534 uri = get_interface_uri(interface)1547 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 @@
1/* Copyright 2015-2016 Canonical Ltd. This software is licensed under the1/* Copyright 2015-2017 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *3 *
4 * MAAS Node Networking Controller4 * MAAS Node Networking Controller
@@ -568,30 +568,6 @@ angular.module('MAAS').controller('NodeNetworkingController', [
568 updateLoaded();568 updateLoaded();
569 };569 };
570570
571 // Return true if the Node is Ready (or Broken)
572 $scope.isNodeEditingAllowed = function() {
573 if (!$scope.isSuperUser()) {
574 // If the user is not the superuser, pretend it's not Ready.
575 return false;
576 }
577 if ($scope.$parent.isDevice) {
578 // Devices are always Ready, for our purposes, for now.
579 return true;
580 }
581 if ($scope.$parent.isController) {
582 // Controllers are always Ready, for our purposes.
583 return true;
584 }
585 if (angular.isObject($scope.node) &&
586 ["Ready", "Broken"].indexOf($scope.node.status) === -1) {
587 // If a non-controller node is not Ready or Broken, then no
588 // editing networking.
589 return false;
590 }
591 // All is well, let them edit.
592 return true;
593 };
594
595 // Return true if only the name or mac address of an interface can571 // Return true if only the name or mac address of an interface can
596 // be edited.572 // be edited.
597 $scope.isLimitedEditingAllowed = function(nic) {573 $scope.isLimitedEditingAllowed = function(nic) {
@@ -623,9 +599,10 @@ angular.module('MAAS').controller('NodeNetworkingController', [
623 return false;599 return false;
624 }600 }
625 if (angular.isObject($scope.node) &&601 if (angular.isObject($scope.node) &&
626 ["Ready", "Broken"].indexOf($scope.node.status) === -1) {602 ["Ready", "Allocated", "Broken"].indexOf(
627 // If a non-controller node is not ready or broken, disable603 $scope.node.status) === -1) {
628 // networking panel.604 // If a non-controller node is not ready allocated, or broken,
605 // disable networking panel.
629 return true;606 return true;
630 }607 }
631 // User must be a superuser and the node must be608 // User must be a superuser and the node must be
@@ -1090,7 +1067,7 @@ angular.module('MAAS').controller('NodeNetworkingController', [
1090 $scope.canAddAliasOrVLAN = function(nic) {1067 $scope.canAddAliasOrVLAN = function(nic) {
1091 if($scope.$parent.isController) {1068 if($scope.$parent.isController) {
1092 return false;1069 return false;
1093 } else if (!$scope.isNodeEditingAllowed()) {1070 } else if ($scope.isAllNetworkingDisabled()) {
1094 return false;1071 return false;
1095 } else {1072 } else {
1096 return $scope.canAddAlias(nic) || $scope.canAddVLAN(nic);1073 return $scope.canAddAlias(nic) || $scope.canAddVLAN(nic);
@@ -1148,7 +1125,7 @@ angular.module('MAAS').controller('NodeNetworkingController', [
1148 $scope.canBeRemoved = function() {1125 $scope.canBeRemoved = function() {
1149 return (1126 return (
1150 !$scope.$parent.isController &&1127 !$scope.$parent.isController &&
1151 $scope.isNodeEditingAllowed());1128 !$scope.isAllNetworkingDisabled());
1152 };1129 };
11531130
1154 // Enter remove mode.1131 // 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 @@
1/* Copyright 2015-2016 Canonical Ltd. This software is licensed under the1/* Copyright 2015-2017 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *3 *
4 * Unit tests for NodeNetworkingController.4 * Unit tests for NodeNetworkingController.
@@ -2146,7 +2146,7 @@ describe("NodeNetworkingController", function() {
2146 it("returns false if isController", function() {2146 it("returns false if isController", function() {
2147 var controller = makeController();2147 var controller = makeController();
2148 $parentScope.isController = true;2148 $parentScope.isController = true;
2149 spyOn($scope, "isNodeEditingAllowed").and.returnValue(true);2149 spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false);
2150 spyOn($scope, "canAddAlias").and.returnValue(true);2150 spyOn($scope, "canAddAlias").and.returnValue(true);
2151 spyOn($scope, "canAddVLAN").and.returnValue(true);2151 spyOn($scope, "canAddVLAN").and.returnValue(true);
2152 expect($scope.canAddAliasOrVLAN({})).toBe(false);2152 expect($scope.canAddAliasOrVLAN({})).toBe(false);
@@ -2155,7 +2155,7 @@ describe("NodeNetworkingController", function() {
2155 it("returns false if no node editing", function() {2155 it("returns false if no node editing", function() {
2156 var controller = makeController();2156 var controller = makeController();
2157 $parentScope.isController = false;2157 $parentScope.isController = false;
2158 spyOn($scope, "isNodeEditingAllowed").and.returnValue(false);2158 spyOn($scope, "isAllNetworkingDisabled").and.returnValue(true);
2159 spyOn($scope, "canAddAlias").and.returnValue(true);2159 spyOn($scope, "canAddAlias").and.returnValue(true);
2160 spyOn($scope, "canAddVLAN").and.returnValue(true);2160 spyOn($scope, "canAddVLAN").and.returnValue(true);
2161 expect($scope.canAddAliasOrVLAN({})).toBe(false);2161 expect($scope.canAddAliasOrVLAN({})).toBe(false);
@@ -2164,7 +2164,7 @@ describe("NodeNetworkingController", function() {
2164 it("returns true if can edit alias", function() {2164 it("returns true if can edit alias", function() {
2165 var controller = makeController();2165 var controller = makeController();
2166 $parentScope.isController = false;2166 $parentScope.isController = false;
2167 spyOn($scope, "isNodeEditingAllowed").and.returnValue(true);2167 spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false);
2168 spyOn($scope, "canAddAlias").and.returnValue(true);2168 spyOn($scope, "canAddAlias").and.returnValue(true);
2169 spyOn($scope, "canAddVLAN").and.returnValue(false);2169 spyOn($scope, "canAddVLAN").and.returnValue(false);
2170 expect($scope.canAddAliasOrVLAN({})).toBe(true);2170 expect($scope.canAddAliasOrVLAN({})).toBe(true);
@@ -2173,7 +2173,7 @@ describe("NodeNetworkingController", function() {
2173 it("returns true if can edit VLAN", function() {2173 it("returns true if can edit VLAN", function() {
2174 var controller = makeController();2174 var controller = makeController();
2175 $parentScope.isController = false;2175 $parentScope.isController = false;
2176 spyOn($scope, "isNodeEditingAllowed").and.returnValue(true);2176 spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false);
2177 spyOn($scope, "canAddAlias").and.returnValue(false);2177 spyOn($scope, "canAddAlias").and.returnValue(false);
2178 spyOn($scope, "canAddVLAN").and.returnValue(true);2178 spyOn($scope, "canAddVLAN").and.returnValue(true);
2179 expect($scope.canAddAliasOrVLAN({})).toBe(true);2179 expect($scope.canAddAliasOrVLAN({})).toBe(true);
@@ -2523,21 +2523,21 @@ describe("NodeNetworkingController", function() {
2523 it("false if isController", function() {2523 it("false if isController", function() {
2524 var controller = makeController();2524 var controller = makeController();
2525 $parentScope.isController = true;2525 $parentScope.isController = true;
2526 spyOn($scope, "isNodeEditingAllowed").and.returnValue(true);2526 spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false);
2527 expect($scope.canBeRemoved()).toBe(false);2527 expect($scope.canBeRemoved()).toBe(false);
2528 });2528 });
25292529
2530 it("false if no node editing", function() {2530 it("false if no node editing", function() {
2531 var controller = makeController();2531 var controller = makeController();
2532 $parentScope.isController = false;2532 $parentScope.isController = false;
2533 spyOn($scope, "isNodeEditingAllowed").and.returnValue(false);2533 spyOn($scope, "isAllNetworkingDisabled").and.returnValue(true);
2534 expect($scope.canBeRemoved()).toBe(false);2534 expect($scope.canBeRemoved()).toBe(false);
2535 });2535 });
25362536
2537 it("true if node can be edited", function() {2537 it("true if node can be edited", function() {
2538 var controller = makeController();2538 var controller = makeController();
2539 $parentScope.isController = false;2539 $parentScope.isController = false;
2540 spyOn($scope, "isNodeEditingAllowed").and.returnValue(true);2540 spyOn($scope, "isAllNetworkingDisabled").and.returnValue(false);
2541 expect($scope.canBeRemoved()).toBe(true);2541 expect($scope.canBeRemoved()).toBe(true);
2542 });2542 });
2543 });2543 });
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 @@
656 <div class="p-notification" data-ng-if="!isController && isAllNetworkingDisabled() && isSuperUser()">656 <div class="p-notification" data-ng-if="!isController && isAllNetworkingDisabled() && isSuperUser()">
657 <p class="p-notification__response">Interface657 <p class="p-notification__response">Interface
658 configuration cannot be modified unless the658 configuration cannot be modified unless the
659 node is Ready or Broken.</p>659 node is Ready, Allocated, or Broken.</p>
660 </div>660 </div>
661 <div class="p-notification--warning" data-ng-if="!isController && isCustomOS()">661 <div class="p-notification--warning" data-ng-if="!isController && isCustomOS()">
662 <p class="p-notification__response">662 <p class="p-notification__response">
@@ -698,15 +698,15 @@
698 </header>698 </header>
699 <div class="table__body" data-selected-rows>699 <div class="table__body" data-selected-rows>
700 <div class="table__row"700 <div class="table__row"
701 data-ng-class="{ disabled: isDisabled(), 'is-active': isInterfaceSelected(interface) && (isNodeEditingAllowed() || isLimitedEditingAllowed(interface)), noEdit: cannotEditInterface(interface) }"701 data-ng-class="{ disabled: isDisabled(), 'is-active': isInterfaceSelected(interface) && (!isAllNetworkingDisabled() || isLimitedEditingAllowed(interface)), noEdit: cannotEditInterface(interface) }"
702 data-ng-repeat="interface in interfaces | removeInterfaceParents:newBondInterface:!isNodeEditingAllowed() | removeInterfaceParents:newBridgeInterface:!isNodeEditingAllowed()">702 data-ng-repeat="interface in interfaces | removeInterfaceParents:newBondInterface:isAllNetworkingDisabled() | removeInterfaceParents:newBridgeInterface:isAllNetworkingDisabled()">
703 <div class="table__data table-col--3" data-ng-if="!isDevice" aria-label="Select">703 <div class="table__data table-col--3" data-ng-if="!isDevice" aria-label="Select">
704 <input type="checkbox" class="checkbox" id="{$ getUniqueKey(interface) $}"704 <input type="checkbox" class="checkbox" id="{$ getUniqueKey(interface) $}"
705 data-ng-hide="isAllNetworkingDisabled()"705 data-ng-hide="isAllNetworkingDisabled()"
706 data-ng-checked="isInterfaceSelected(interface)"706 data-ng-checked="isInterfaceSelected(interface)"
707 data-ng-click="toggleInterfaceSelect(interface)"707 data-ng-click="toggleInterfaceSelect(interface)"
708 data-ng-disabled="isDisabled()"708 data-ng-disabled="isDisabled()"
709 data-ng-if="!isController && isNodeEditingAllowed()">709 data-ng-if="!isController && !isAllNetworkingDisabled()">
710 <label for="{$ getUniqueKey(interface) $}"></label>710 <label for="{$ getUniqueKey(interface) $}"></label>
711 </div>711 </div>
712 <div class="table__data table-col--12" aria-label="Name" data-ng-if="!isDevice" data-ng-show="tableInfo.column == 'name'">712 <div class="table__data table-col--12" aria-label="Name" data-ng-if="!isDevice" data-ng-show="tableInfo.column == 'name'">
@@ -755,7 +755,7 @@
755 </span>755 </span>
756 </div>756 </div>
757 <div class="table__data table--mobile-controls table-col--8">757 <div class="table__data table--mobile-controls table-col--8">
758 <div class="table__controls" toggle-ctrl data-ng-if="isNodeEditingAllowed() || isLimitedEditingAllowed(interface)">758 <div class="table__controls" toggle-ctrl data-ng-if="!isAllNetworkingDisabled() || isLimitedEditingAllowed(interface)">
759 <button class="table__controls-toggle" data-ng-click="toggleMenu()">View actions</button>759 <button class="table__controls-toggle" data-ng-click="toggleMenu()">View actions</button>
760 <div class="table__controls-menu" role="menu" data-ng-show="isToggled">760 <div class="table__controls-menu" role="menu" data-ng-show="isToggled">
761 <button class="table__controls-action"761 <button class="table__controls-action"
@@ -787,7 +787,7 @@
787 </div>787 </div>
788 </div>788 </div>
789 </div>789 </div>
790 <div data-ng-if="isNodeEditingAllowed() || isLimitedEditingAllowed(interface)">790 <div data-ng-if="!isAllNetworkingDisabled() || isLimitedEditingAllowed(interface)">
791 <div class="table__dropdown" tabindex="0" data-ng-if="isShowingAdd() || isEditing(interface) || isShowingDeleteConfirm() || isShowingAdd() && !newInterface.saving">791 <div class="table__dropdown" tabindex="0" data-ng-if="isShowingAdd() || isEditing(interface) || isShowingDeleteConfirm() || isShowingAdd() && !newInterface.saving">
792 <div class="table__dropdown-title">792 <div class="table__dropdown-title">
793 <h2 data-ng-click="cancel()" class="u-float--left">793 <h2 data-ng-click="cancel()" class="u-float--left">
@@ -888,10 +888,10 @@
888 input-class="table__input u-margin--none" placeholder="00:00:00:00:00:00"></maas-obj-field>888 input-class="table__input u-margin--none" placeholder="00:00:00:00:00:00"></maas-obj-field>
889 <maas-obj-field type="tags" key="tags" label="Tags" label-width="two" input-width="three"889 <maas-obj-field type="tags" key="tags" label="Tags" label-width="two" input-width="three"
890 placeholder="Add a tag"890 placeholder="Add a tag"
891 data-ng-if="!isLimitedEditingAllowed(interface) && interface.type !== 'alias'"891 data-ng-if="!isAllNetworkingDisabled(interface) && interface.type !== 'alias'"
892 disable-label="false" input-class="table__input u-margin--none"></maas-obj-field>892 disable-label="false" input-class="table__input u-margin--none"></maas-obj-field>
893 </fieldset>893 </fieldset>
894 <fieldset class="form__fieldset six-col last-col" data-ng-if="!isLimitedEditingAllowed(interface)">894 <fieldset class="form__fieldset six-col last-col" data-ng-if="!isAllNetworkingDisabled(interface)">
895 <maas-obj-field type="options" key="fabric" label="Fabric" label-width="two" input-width="three"895 <maas-obj-field type="options" key="fabric" label="Fabric" label-width="two" input-width="three"
896 disable-label="false" input-class="table__input u-margin--none"896 disable-label="false" input-class="table__input u-margin--none"
897 data-ng-if="!isDevice"897 data-ng-if="!isDevice"
@@ -976,7 +976,7 @@
976 </div>976 </div>
977 </div>977 </div>
978 </div>978 </div>
979 <div class="table__row is-active" tabindex="0" data-ng-if="isShowingCreateBond() && isNodeEditingAllowed()">979 <div class="table__row is-active" tabindex="0" data-ng-if="isShowingCreateBond() && !isAllNetworkingDisabled()">
980 <div class="table__data table-col--3">980 <div class="table__data table-col--3">
981 <input type="checkbox" class="checkbox" name="bond-create" disabled="disabled" checked />981 <input type="checkbox" class="checkbox" name="bond-create" disabled="disabled" checked />
982 <label for="bond-create"></label>982 <label for="bond-create"></label>
@@ -1077,7 +1077,7 @@
1077 </div>1077 </div>
1078 </div>1078 </div>
1079 </div>1079 </div>
1080 <div class="table__row is-active" tabindex="0" data-ng-if="isShowingCreateBridge() && isNodeEditingAllowed()">1080 <div class="table__row is-active" tabindex="0" data-ng-if="isShowingCreateBridge() && !isAllNetworkingDisabled()">
1081 <div class="table__data table-col--3">1081 <div class="table__data table-col--3">
1082 <input type="checkbox" class="checkbox" id="bond-create" disabled="disabled" checked />1082 <input type="checkbox" class="checkbox" id="bond-create" disabled="disabled" checked />
1083 <label for="bridge-create"></label>1083 <label for="bridge-create"></label>
@@ -1262,7 +1262,7 @@
1262 data-ng-class="{ 'has-error': isMACAddressInvalid(newInterface.mac_address, true) }">1262 data-ng-class="{ 'has-error': isMACAddressInvalid(newInterface.mac_address, true) }">
1263 </div>1263 </div>
1264 </fieldset>1264 </fieldset>
1265 <fieldset class="form__fieldset six-col last-col" data-ng-if="!isLimitedEditingAllowed(interface)">1265 <fieldset class="form__fieldset six-col last-col" data-ng-if="!isAllNetworkingDisabled(interface)">
1266 <div class="form__group">1266 <div class="form__group">
1267 <label for="ip-assignment" class="two-col">IP Assignment</label>1267 <label for="ip-assignment" class="two-col">IP Assignment</label>
1268 <select name="ip-assignment" class="three-col"1268 <select name="ip-assignment" class="three-col"

Subscribers

People subscribed via source and target branches