Merge lp:~blake-rouse/maas/node-networking-link into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4337
Proposed branch: lp:~blake-rouse/maas/node-networking-link
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 2142 lines (+1529/-130)
10 files modified
src/maasserver/models/interface.py (+134/-10)
src/maasserver/models/tests/test_interface.py (+503/-1)
src/maasserver/static/js/angular/controllers/node_details_networking.js (+175/-24)
src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js (+530/-58)
src/maasserver/static/js/angular/factories/nodes.js (+14/-2)
src/maasserver/static/js/angular/factories/tests/test_nodes.js (+45/-1)
src/maasserver/static/partials/node-details.html (+21/-22)
src/maasserver/websockets/handlers/node.py (+33/-3)
src/maasserver/websockets/handlers/tests/test_node.py (+54/-5)
src/maastesting/factory.py (+20/-4)
To merge this branch: bzr merge lp:~blake-rouse/maas/node-networking-link
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+273002@code.launchpad.net

Commit message

Add the ability to change the link mode of an interface or alias in the UI. Provide the backend work to allow making this changed without changing the link_id. Order the links for each interface by ID so the order never changes. Clean up the unmountFilesystem websocket handler name and fix the naming of subnets and vlans in the dropdowns.

Description of the change

This is a rather large change just because the backend could not handle the interaction that the UI wanted. Also includes some small fixes and HTML and JS.

I have fully tested this from packaging and it fully works.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

lgtm! I have not done an indepth review but looks good so far!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/models/interface.py'
--- src/maasserver/models/interface.py 2015-09-24 16:22:12 +0000
+++ src/maasserver/models/interface.py 2015-10-01 14:33:47 +0000
@@ -601,7 +601,8 @@
601 return ip_address601 return ip_address
602602
603 def _link_subnet_static(603 def _link_subnet_static(
604 self, subnet, ip_address=None, alloc_type=None, user=None):604 self, subnet, ip_address=None, alloc_type=None, user=None,
605 swap_static_ip=None):
605 """Link interface to subnet using STATIC."""606 """Link interface to subnet using STATIC."""
606 valid_alloc_types = [607 valid_alloc_types = [
607 IPADDRESS_TYPE.STICKY,608 IPADDRESS_TYPE.STICKY,
@@ -617,12 +618,8 @@
617 user = None618 user = None
618619
619 ngi = None620 ngi = None
620 has_allocations = False
621 if subnet is not None:621 if subnet is not None:
622 ngi = subnet.get_managed_cluster_interface()622 ngi = subnet.get_managed_cluster_interface()
623 if ngi is not None:
624 has_allocations = self._has_static_allocation_on_cluster(
625 ngi.nodegroup, get_subnet_family(subnet))
626623
627 if ip_address:624 if ip_address:
628 ip_address = IPAddress(ip_address)625 ip_address = IPAddress(ip_address)
@@ -664,10 +661,20 @@
664 None, None, alloc_type=alloc_type, subnet=subnet, user=user)661 None, None, alloc_type=alloc_type, subnet=subnet, user=user)
665 self.ip_addresses.add(static_ip)662 self.ip_addresses.add(static_ip)
666663
664 # Swap the ID's that way it keeps the same ID as the swap object.
665 if swap_static_ip is not None:
666 static_ip.id, swap_static_ip.id = swap_static_ip.id, static_ip.id
667 swap_static_ip.delete()
668 static_ip.save()
669
667 # Need to update the hostmaps on the cluster, if this subnet670 # Need to update the hostmaps on the cluster, if this subnet
668 # has a managed interface.671 # has a managed interface.
669 if ngi is not None and not has_allocations:672 if ngi is not None:
670 self._update_host_maps(ngi.nodegroup, static_ip)673 allocated_ip = (
674 self._get_first_static_allocation_for_cluster(
675 ngi.nodegroup, get_subnet_family(subnet)))
676 if allocated_ip is None or allocated_ip.id == static_ip.id:
677 self._update_host_maps(ngi.nodegroup, static_ip)
671678
672 # Was successful at creating the STATIC link. Remove the DHCP and679 # Was successful at creating the STATIC link. Remove the DHCP and
673 # LINK_UP link if it exists.680 # LINK_UP link if it exists.
@@ -756,7 +763,8 @@
756 subnet = None763 subnet = None
757 self.link_subnet(INTERFACE_LINK_TYPE.LINK_UP, subnet)764 self.link_subnet(INTERFACE_LINK_TYPE.LINK_UP, subnet)
758765
759 def _unlink_static_ip(self, static_ip, update_cluster=True):766 def _unlink_static_ip(
767 self, static_ip, update_cluster=True, swap_alloc_type=None):
760 """Unlink the STATIC IP address from the interface."""768 """Unlink the STATIC IP address from the interface."""
761 registered_on_cluster = False769 registered_on_cluster = False
762 ngi = None770 ngi = None
@@ -771,14 +779,23 @@
771 # Need to remove the hostmap on the cluster before it can779 # Need to remove the hostmap on the cluster before it can
772 # be deleted.780 # be deleted.
773 self._remove_host_maps(ngi.nodegroup, static_ip)781 self._remove_host_maps(ngi.nodegroup, static_ip)
774 static_ip.delete()782
783 # If the allocation type is only changing then we don't need to delete
784 # the IP address it needs to be updated.
785 ip_version = IPAddress(static_ip.ip).version
786 if swap_alloc_type is not None:
787 static_ip.alloc_type = swap_alloc_type
788 static_ip.ip = None
789 static_ip.save()
790 else:
791 static_ip.delete()
775792
776 # If this IP address was registered on the cluster and now has been793 # If this IP address was registered on the cluster and now has been
777 # deleted we need to register the next assigned IP address to the794 # deleted we need to register the next assigned IP address to the
778 # cluster hostmap.795 # cluster hostmap.
779 if registered_on_cluster and ngi is not None and update_cluster:796 if registered_on_cluster and ngi is not None and update_cluster:
780 new_hostmap_ip = self._get_first_static_allocation_for_cluster(797 new_hostmap_ip = self._get_first_static_allocation_for_cluster(
781 ngi.nodegroup, IPAddress(static_ip.ip).version)798 ngi.nodegroup, ip_version)
782 if new_hostmap_ip is not None:799 if new_hostmap_ip is not None:
783 self._update_host_maps(ngi.nodegroup, new_hostmap_ip)800 self._update_host_maps(ngi.nodegroup, new_hostmap_ip)
784801
@@ -787,6 +804,7 @@
787 self._update_dns_zones([ngi.nodegroup])804 self._update_dns_zones([ngi.nodegroup])
788 else:805 else:
789 self._update_dns_zones()806 self._update_dns_zones()
807 return static_ip
790808
791 def unlink_ip_address(809 def unlink_ip_address(
792 self, ip_address, update_cluster=True, clearing_config=False):810 self, ip_address, update_cluster=True, clearing_config=False):
@@ -815,6 +833,112 @@
815 ip_address = self.ip_addresses.get(id=link_id)833 ip_address = self.ip_addresses.get(id=link_id)
816 self.unlink_ip_address(ip_address)834 self.unlink_ip_address(ip_address)
817835
836 def _swap_subnet(self, static_ip, subnet, ip_address=None):
837 """Swap the subnet for the `static_ip`."""
838 # Check that requested `ip_address` is available.
839 if ip_address is not None:
840 already_used = get_one(
841 StaticIPAddress.objects.filter(ip=ip_address))
842 if already_used is not None:
843 raise StaticIPAddressUnavailable(
844 "IP address is already in use.")
845
846 # Remove the hostmap on the new subnet.
847 new_subnet_ngi = subnet.get_managed_cluster_interface()
848 if new_subnet_ngi is not None:
849 static_ip_on_new_subnet = (
850 self._get_first_static_allocation_for_cluster(
851 new_subnet_ngi.nodegroup, get_subnet_family(subnet)))
852 if (static_ip_on_new_subnet is not None and
853 static_ip_on_new_subnet.id > static_ip.id):
854 # The updated static_id should be registered over the other
855 # IP address registered on the new subnet.
856 self._remove_host_maps(
857 new_subnet_ngi.nodegroup, static_ip_on_new_subnet)
858
859 # If the subnets are different then remove the hostmap from the old
860 # subnet as well.
861 if static_ip.subnet is not None and static_ip.subnet != subnet:
862 old_subnet_ngi = static_ip.subnet.get_managed_cluster_interface()
863 registered_on_cluster = False
864 if old_subnet_ngi is not None:
865 registered_on_cluster = (
866 self._is_first_static_allocation_on_cluster(
867 static_ip, old_subnet_ngi.nodegroup))
868 if registered_on_cluster:
869 self._remove_host_maps(old_subnet_ngi.nodegroup, static_ip)
870
871 # Clear the subnet before checking which is the next hostmap.
872 static_ip.subnet = None
873 static_ip.save()
874
875 # Register the new STATIC IP address for the old subnet.
876 if registered_on_cluster and old_subnet_ngi is not None:
877 new_hostmap_ip = self._get_first_static_allocation_for_cluster(
878 old_subnet_ngi.nodegroup, IPAddress(static_ip.ip).version)
879 if new_hostmap_ip is not None:
880 self._update_host_maps(
881 old_subnet_ngi.nodegroup, new_hostmap_ip)
882
883 # Update the DNS configuration for the old subnet if needed.
884 if old_subnet_ngi is not None:
885 self._update_dns_zones([old_subnet_ngi.nodegroup])
886
887 # If the IP addresses are on the same subnet but the IP's are
888 # different then we need to remove the hostmap.
889 if (static_ip.subnet == subnet and
890 new_subnet_ngi is not None and
891 static_ip.ip != ip_address):
892 self._remove_host_maps(
893 new_subnet_ngi.nodegroup, static_ip)
894
895 # Link to the new subnet, which will also update the hostmap.
896 return self._link_subnet_static(
897 subnet, ip_address=ip_address, swap_static_ip=static_ip)
898
899 def update_ip_address(
900 self, static_ip, mode, subnet, ip_address=None):
901 """Update an already existing link on interface to be the new data."""
902 if mode == INTERFACE_LINK_TYPE.AUTO:
903 new_alloc_type = IPADDRESS_TYPE.AUTO
904 elif mode == INTERFACE_LINK_TYPE.DHCP:
905 new_alloc_type = IPADDRESS_TYPE.DHCP
906 elif mode in [INTERFACE_LINK_TYPE.LINK_UP, INTERFACE_LINK_TYPE.STATIC]:
907 new_alloc_type = IPADDRESS_TYPE.STICKY
908
909 current_mode = static_ip.get_interface_link_type()
910 if current_mode == INTERFACE_LINK_TYPE.STATIC:
911 if mode == INTERFACE_LINK_TYPE.STATIC:
912 if (static_ip.subnet == subnet and (
913 ip_address is None or static_ip.ip == ip_address)):
914 # Same subnet and IP address nothing to do.
915 return static_ip
916 # Update the subent and IP address for the static assignment.
917 return self._swap_subnet(
918 static_ip, subnet, ip_address=ip_address)
919 else:
920 # Not staying in the same mode so we can just remove the
921 # static IP and change its alloc_type from STICKY.
922 static_ip = self._unlink_static_ip(
923 static_ip, swap_alloc_type=new_alloc_type)
924 elif mode == INTERFACE_LINK_TYPE.STATIC:
925 # Linking to the subnet statically were the original was not a
926 # static link. Swap the objects so the object keeps the same ID.
927 return self._link_subnet_static(
928 subnet, ip_address=ip_address,
929 swap_static_ip=static_ip)
930 static_ip.alloc_type = new_alloc_type
931 static_ip.ip = None
932 static_ip.subnet = subnet
933 static_ip.save()
934 return static_ip
935
936 def update_link_by_id(self, link_id, mode, subnet, ip_address=None):
937 """Update the `IPAddress` link on interface by its ID."""
938 static_ip = self.ip_addresses.get(id=link_id)
939 return self.update_ip_address(
940 static_ip, mode, subnet, ip_address=ip_address)
941
818 def clear_all_links(self, clearing_config=False):942 def clear_all_links(self, clearing_config=False):
819 """Remove all the `IPAddress` link on the interface."""943 """Remove all the `IPAddress` link on the interface."""
820 for ip_address in self.ip_addresses.exclude(944 for ip_address in self.ip_addresses.exclude(
821945
=== modified file 'src/maasserver/models/tests/test_interface.py'
--- src/maasserver/models/tests/test_interface.py 2015-09-24 16:22:12 +0000
+++ src/maasserver/models/tests/test_interface.py 2015-10-01 14:33:47 +0000
@@ -29,7 +29,10 @@
29 NODEGROUP_STATUS,29 NODEGROUP_STATUS,
30 NODEGROUPINTERFACE_MANAGEMENT,30 NODEGROUPINTERFACE_MANAGEMENT,
31)31)
32from maasserver.exceptions import StaticIPAddressOutOfRange32from maasserver.exceptions import (
33 StaticIPAddressOutOfRange,
34 StaticIPAddressUnavailable,
35)
33from maasserver.models import (36from maasserver.models import (
34 Fabric,37 Fabric,
35 interface as interface_module,38 interface as interface_module,
@@ -67,6 +70,7 @@
67 IPNetwork,70 IPNetwork,
68 IPRange,71 IPRange,
69)72)
73from testtools import ExpectedException
70from testtools.matchers import (74from testtools.matchers import (
71 Equals,75 Equals,
72 MatchesDict,76 MatchesDict,
@@ -1318,6 +1322,504 @@
1318 alloc_type=IPADDRESS_TYPE.STICKY, ip=None).first())1322 alloc_type=IPADDRESS_TYPE.STICKY, ip=None).first())
13191323
13201324
1325class TestUpdateIPAddress(MAASServerTestCase):
1326 """Tests for `Interface.update_ip_address`."""
1327
1328 def test__switch_dhcp_to_auto(self):
1329 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1330 subnet = factory.make_Subnet(vlan=interface.vlan)
1331 static_ip = factory.make_StaticIPAddress(
1332 alloc_type=IPADDRESS_TYPE.DHCP, ip="",
1333 subnet=subnet, interface=interface)
1334 static_id = static_ip.id
1335 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1336 static_ip = interface.update_ip_address(
1337 static_ip, INTERFACE_LINK_TYPE.AUTO, new_subnet)
1338 self.assertEquals(static_id, static_ip.id)
1339 self.assertEquals(IPADDRESS_TYPE.AUTO, static_ip.alloc_type)
1340 self.assertEquals(new_subnet, static_ip.subnet)
1341 self.assertIsNone(static_ip.ip)
1342
1343 def test__switch_dhcp_to_link_up(self):
1344 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1345 subnet = factory.make_Subnet(vlan=interface.vlan)
1346 static_ip = factory.make_StaticIPAddress(
1347 alloc_type=IPADDRESS_TYPE.DHCP, ip="",
1348 subnet=subnet, interface=interface)
1349 static_id = static_ip.id
1350 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1351 static_ip = interface.update_ip_address(
1352 static_ip, INTERFACE_LINK_TYPE.LINK_UP, new_subnet)
1353 self.assertEquals(static_id, static_ip.id)
1354 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
1355 self.assertEquals(new_subnet, static_ip.subnet)
1356 self.assertIsNone(static_ip.ip)
1357
1358 def test__switch_dhcp_to_static(self):
1359 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1360 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1361 network_v4 = factory.make_ipv4_network(slash=24)
1362 subnet = factory.make_Subnet(
1363 vlan=interface.vlan, cidr=unicode(network_v4.cidr))
1364 factory.make_NodeGroupInterface(
1365 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1366 subnet=subnet)
1367 static_ip = factory.make_StaticIPAddress(
1368 alloc_type=IPADDRESS_TYPE.DHCP, ip="",
1369 subnet=subnet, interface=interface)
1370 static_id = static_ip.id
1371 network_v6 = factory.make_ipv6_network(slash=24)
1372 new_subnet = factory.make_Subnet(
1373 vlan=interface.vlan, cidr=unicode(network_v6.cidr))
1374 factory.make_NodeGroupInterface(
1375 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1376 subnet=new_subnet)
1377 mock_update_host_maps = self.patch_autospec(
1378 interface, "_update_host_maps")
1379 mock_update_dns_zones = self.patch_autospec(
1380 interface, "_update_dns_zones")
1381 static_ip = interface.update_ip_address(
1382 static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet)
1383 self.assertEquals(static_id, static_ip.id)
1384 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
1385 self.assertEquals(new_subnet, static_ip.subnet)
1386 self.assertIsNotNone(static_ip.ip)
1387 self.assertThat(
1388 mock_update_host_maps, MockCalledOnceWith(nodegroup, static_ip))
1389 self.assertThat(
1390 mock_update_dns_zones, MockCalledOnceWith([nodegroup]))
1391
1392 def test__switch_auto_to_dhcp(self):
1393 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1394 subnet = factory.make_Subnet(vlan=interface.vlan)
1395 static_ip = factory.make_StaticIPAddress(
1396 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
1397 subnet=subnet, interface=interface)
1398 static_id = static_ip.id
1399 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1400 static_ip = interface.update_ip_address(
1401 static_ip, INTERFACE_LINK_TYPE.DHCP, new_subnet)
1402 self.assertEquals(static_id, static_ip.id)
1403 self.assertEquals(IPADDRESS_TYPE.DHCP, static_ip.alloc_type)
1404 self.assertEquals(new_subnet, static_ip.subnet)
1405 self.assertIsNone(static_ip.ip)
1406
1407 def test__switch_auto_to_link_up(self):
1408 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1409 subnet = factory.make_Subnet(vlan=interface.vlan)
1410 static_ip = factory.make_StaticIPAddress(
1411 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
1412 subnet=subnet, interface=interface)
1413 static_id = static_ip.id
1414 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1415 static_ip = interface.update_ip_address(
1416 static_ip, INTERFACE_LINK_TYPE.LINK_UP, new_subnet)
1417 self.assertEquals(static_id, static_ip.id)
1418 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
1419 self.assertEquals(new_subnet, static_ip.subnet)
1420 self.assertIsNone(static_ip.ip)
1421
1422 def test__switch_auto_to_static(self):
1423 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1424 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1425 network_v4 = factory.make_ipv4_network(slash=24)
1426 subnet = factory.make_Subnet(
1427 vlan=interface.vlan, cidr=unicode(network_v4.cidr))
1428 factory.make_NodeGroupInterface(
1429 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1430 subnet=subnet)
1431 static_ip = factory.make_StaticIPAddress(
1432 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
1433 subnet=subnet, interface=interface)
1434 static_id = static_ip.id
1435 network_v6 = factory.make_ipv6_network(slash=24)
1436 new_subnet = factory.make_Subnet(
1437 vlan=interface.vlan, cidr=unicode(network_v6.cidr))
1438 factory.make_NodeGroupInterface(
1439 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1440 subnet=new_subnet)
1441 mock_update_host_maps = self.patch_autospec(
1442 interface, "_update_host_maps")
1443 mock_update_dns_zones = self.patch_autospec(
1444 interface, "_update_dns_zones")
1445 static_ip = interface.update_ip_address(
1446 static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet)
1447 self.assertEquals(static_id, static_ip.id)
1448 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
1449 self.assertEquals(new_subnet, static_ip.subnet)
1450 self.assertIsNotNone(static_ip.ip)
1451 self.assertThat(
1452 mock_update_host_maps, MockCalledOnceWith(nodegroup, static_ip))
1453 self.assertThat(
1454 mock_update_dns_zones, MockCalledOnceWith([nodegroup]))
1455
1456 def test__switch_link_up_to_auto(self):
1457 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1458 subnet = factory.make_Subnet(vlan=interface.vlan)
1459 static_ip = factory.make_StaticIPAddress(
1460 alloc_type=IPADDRESS_TYPE.STICKY, ip="",
1461 subnet=subnet, interface=interface)
1462 static_id = static_ip.id
1463 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1464 static_ip = interface.update_ip_address(
1465 static_ip, INTERFACE_LINK_TYPE.AUTO, new_subnet)
1466 self.assertEquals(static_id, static_ip.id)
1467 self.assertEquals(IPADDRESS_TYPE.AUTO, static_ip.alloc_type)
1468 self.assertEquals(new_subnet, static_ip.subnet)
1469 self.assertIsNone(static_ip.ip)
1470
1471 def test__switch_link_up_to_dhcp(self):
1472 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1473 subnet = factory.make_Subnet(vlan=interface.vlan)
1474 static_ip = factory.make_StaticIPAddress(
1475 alloc_type=IPADDRESS_TYPE.STICKY, ip="",
1476 subnet=subnet, interface=interface)
1477 static_id = static_ip.id
1478 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1479 static_ip = interface.update_ip_address(
1480 static_ip, INTERFACE_LINK_TYPE.DHCP, new_subnet)
1481 self.assertEquals(static_id, static_ip.id)
1482 self.assertEquals(IPADDRESS_TYPE.DHCP, static_ip.alloc_type)
1483 self.assertEquals(new_subnet, static_ip.subnet)
1484 self.assertIsNone(static_ip.ip)
1485
1486 def test__switch_link_up_to_static(self):
1487 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1488 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1489 network_v4 = factory.make_ipv4_network(slash=24)
1490 subnet = factory.make_Subnet(
1491 vlan=interface.vlan, cidr=unicode(network_v4.cidr))
1492 factory.make_NodeGroupInterface(
1493 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1494 subnet=subnet)
1495 static_ip = factory.make_StaticIPAddress(
1496 alloc_type=IPADDRESS_TYPE.STICKY, ip="",
1497 subnet=subnet, interface=interface)
1498 static_id = static_ip.id
1499 network_v6 = factory.make_ipv6_network(slash=24)
1500 new_subnet = factory.make_Subnet(
1501 vlan=interface.vlan, cidr=unicode(network_v6.cidr))
1502 factory.make_NodeGroupInterface(
1503 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1504 subnet=new_subnet)
1505 mock_update_host_maps = self.patch_autospec(
1506 interface, "_update_host_maps")
1507 mock_update_dns_zones = self.patch_autospec(
1508 interface, "_update_dns_zones")
1509 static_ip = interface.update_ip_address(
1510 static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet)
1511 self.assertEquals(static_id, static_ip.id)
1512 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
1513 self.assertEquals(new_subnet, static_ip.subnet)
1514 self.assertIsNotNone(static_ip.ip)
1515 self.assertThat(
1516 mock_update_host_maps, MockCalledOnceWith(nodegroup, static_ip))
1517 self.assertThat(
1518 mock_update_dns_zones, MockCalledOnceWith([nodegroup]))
1519
1520 def test__switch_static_to_dhcp(self):
1521 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1522 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1523 subnet = factory.make_Subnet(vlan=interface.vlan)
1524 ngi = factory.make_NodeGroupInterface(
1525 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1526 subnet=subnet)
1527 static_ip = factory.make_StaticIPAddress(
1528 alloc_type=IPADDRESS_TYPE.STICKY,
1529 ip=factory.pick_ip_in_static_range(ngi),
1530 subnet=subnet, interface=interface)
1531 static_id = static_ip.id
1532 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1533 mock_remove_host_maps = self.patch_autospec(
1534 interface, "_remove_host_maps")
1535 mock_update_dns_zones = self.patch_autospec(
1536 interface, "_update_dns_zones")
1537 static_ip = interface.update_ip_address(
1538 static_ip, INTERFACE_LINK_TYPE.DHCP, new_subnet)
1539 self.assertEquals(static_id, static_ip.id)
1540 self.assertEquals(IPADDRESS_TYPE.DHCP, static_ip.alloc_type)
1541 self.assertEquals(new_subnet, static_ip.subnet)
1542 self.assertIsNone(static_ip.ip)
1543 self.assertThat(
1544 mock_remove_host_maps, MockCalledOnceWith(nodegroup, static_ip))
1545 self.assertThat(
1546 mock_update_dns_zones, MockCalledOnceWith([nodegroup]))
1547
1548 def test__switch_static_to_auto(self):
1549 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1550 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1551 subnet = factory.make_Subnet(vlan=interface.vlan)
1552 ngi = factory.make_NodeGroupInterface(
1553 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1554 subnet=subnet)
1555 static_ip = factory.make_StaticIPAddress(
1556 alloc_type=IPADDRESS_TYPE.STICKY,
1557 ip=factory.pick_ip_in_static_range(ngi),
1558 subnet=subnet, interface=interface)
1559 static_id = static_ip.id
1560 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1561 mock_remove_host_maps = self.patch_autospec(
1562 interface, "_remove_host_maps")
1563 mock_update_dns_zones = self.patch_autospec(
1564 interface, "_update_dns_zones")
1565 static_ip = interface.update_ip_address(
1566 static_ip, INTERFACE_LINK_TYPE.AUTO, new_subnet)
1567 self.assertEquals(static_id, static_ip.id)
1568 self.assertEquals(IPADDRESS_TYPE.AUTO, static_ip.alloc_type)
1569 self.assertEquals(new_subnet, static_ip.subnet)
1570 self.assertIsNone(static_ip.ip)
1571 self.assertThat(
1572 mock_remove_host_maps, MockCalledOnceWith(nodegroup, static_ip))
1573 self.assertThat(
1574 mock_update_dns_zones, MockCalledOnceWith([nodegroup]))
1575
1576 def test__switch_static_to_link_up(self):
1577 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1578 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1579 subnet = factory.make_Subnet(vlan=interface.vlan)
1580 ngi = factory.make_NodeGroupInterface(
1581 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1582 subnet=subnet)
1583 static_ip = factory.make_StaticIPAddress(
1584 alloc_type=IPADDRESS_TYPE.STICKY,
1585 ip=factory.pick_ip_in_static_range(ngi),
1586 subnet=subnet, interface=interface)
1587 static_id = static_ip.id
1588 new_subnet = factory.make_Subnet(vlan=interface.vlan)
1589 mock_remove_host_maps = self.patch_autospec(
1590 interface, "_remove_host_maps")
1591 mock_update_dns_zones = self.patch_autospec(
1592 interface, "_update_dns_zones")
1593 static_ip = interface.update_ip_address(
1594 static_ip, INTERFACE_LINK_TYPE.LINK_UP, new_subnet)
1595 self.assertEquals(static_id, static_ip.id)
1596 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
1597 self.assertEquals(new_subnet, static_ip.subnet)
1598 self.assertIsNone(static_ip.ip)
1599 self.assertThat(
1600 mock_remove_host_maps, MockCalledOnceWith(nodegroup, static_ip))
1601 self.assertThat(
1602 mock_update_dns_zones, MockCalledOnceWith([nodegroup]))
1603
1604 def test__switch_static_to_same_subnet_does_nothing(self):
1605 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1606 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1607 subnet = factory.make_Subnet(vlan=interface.vlan)
1608 ngi = factory.make_NodeGroupInterface(
1609 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1610 subnet=subnet)
1611 static_ip = factory.make_StaticIPAddress(
1612 alloc_type=IPADDRESS_TYPE.STICKY,
1613 ip=factory.pick_ip_in_static_range(ngi),
1614 subnet=subnet, interface=interface)
1615 static_id = static_ip.id
1616 static_ip_address = static_ip.ip
1617 mock_remove_host_maps = self.patch_autospec(
1618 interface, "_remove_host_maps")
1619 mock_update_host_maps = self.patch_autospec(
1620 interface, "_update_host_maps")
1621 mock_update_dns_zones = self.patch_autospec(
1622 interface, "_update_dns_zones")
1623 static_ip = interface.update_ip_address(
1624 static_ip, INTERFACE_LINK_TYPE.STATIC, subnet)
1625 self.assertEquals(static_id, static_ip.id)
1626 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
1627 self.assertEquals(subnet, static_ip.subnet)
1628 self.assertEquals(static_ip_address, static_ip.ip)
1629 self.assertThat(mock_remove_host_maps, MockNotCalled())
1630 self.assertThat(mock_update_host_maps, MockNotCalled())
1631 self.assertThat(mock_update_dns_zones, MockNotCalled())
1632
1633 def test__switch_static_to_already_used_ip_address(self):
1634 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1635 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1636 subnet = factory.make_Subnet(vlan=interface.vlan)
1637 ngi = factory.make_NodeGroupInterface(
1638 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1639 subnet=subnet)
1640 static_ip = factory.make_StaticIPAddress(
1641 alloc_type=IPADDRESS_TYPE.STICKY,
1642 ip=factory.pick_ip_in_static_range(ngi),
1643 subnet=subnet, interface=interface)
1644 other_interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1645 used_ip_address = factory.pick_ip_in_static_range(
1646 ngi, but_not=[static_ip.ip])
1647 factory.make_StaticIPAddress(
1648 alloc_type=IPADDRESS_TYPE.STICKY,
1649 ip=used_ip_address,
1650 subnet=subnet, interface=other_interface)
1651 with ExpectedException(StaticIPAddressUnavailable):
1652 interface.update_ip_address(
1653 static_ip, INTERFACE_LINK_TYPE.STATIC, subnet,
1654 ip_address=used_ip_address)
1655
1656 def test__switch_static_to_same_subnet_with_different_ip(self):
1657 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1658 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1659 network = factory.make_ipv4_network(slash=24)
1660 subnet = factory.make_Subnet(
1661 vlan=interface.vlan, cidr=unicode(network.cidr))
1662 ngi = factory.make_NodeGroupInterface(
1663 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1664 subnet=subnet)
1665 static_ip = factory.make_StaticIPAddress(
1666 alloc_type=IPADDRESS_TYPE.STICKY,
1667 ip=factory.pick_ip_in_static_range(ngi),
1668 subnet=subnet, interface=interface)
1669 static_id = static_ip.id
1670 static_ip_address = static_ip.ip
1671 new_ip_address = factory.pick_ip_in_static_range(
1672 ngi, but_not=[static_ip_address])
1673 mock_remove_host_maps = self.patch_autospec(
1674 interface, "_remove_host_maps")
1675 mock_update_host_maps = self.patch_autospec(
1676 interface, "_update_host_maps")
1677 mock_update_dns_zones = self.patch_autospec(
1678 interface, "_update_dns_zones")
1679 new_static_ip = interface.update_ip_address(
1680 static_ip, INTERFACE_LINK_TYPE.STATIC, subnet,
1681 ip_address=new_ip_address)
1682 self.assertEquals(static_id, new_static_ip.id)
1683 self.assertEquals(IPADDRESS_TYPE.STICKY, new_static_ip.alloc_type)
1684 self.assertEquals(subnet, new_static_ip.subnet)
1685 self.assertEquals(new_ip_address, new_static_ip.ip)
1686 # The remove actual loads the IP address from the database so it
1687 # is not the same object. We just need to check that the IP's match.
1688 self.assertEquals(nodegroup, mock_remove_host_maps.call_args[0][0])
1689 self.assertEquals(
1690 static_ip_address, mock_remove_host_maps.call_args[0][1].ip)
1691 self.assertThat(
1692 mock_update_host_maps,
1693 MockCalledOnceWith(nodegroup, new_static_ip))
1694 self.assertThat(
1695 mock_update_dns_zones, MockCalledOnceWith([nodegroup]))
1696
1697 def test__switch_static_to_another_subnet(self):
1698 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1699 nodegroup_v4 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1700 network_v4 = factory.make_ipv4_network(slash=24)
1701 subnet = factory.make_Subnet(
1702 vlan=interface.vlan, cidr=unicode(network_v4.cidr))
1703 ngi = factory.make_NodeGroupInterface(
1704 nodegroup_v4, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1705 subnet=subnet)
1706 static_ip = factory.make_StaticIPAddress(
1707 alloc_type=IPADDRESS_TYPE.STICKY,
1708 ip=factory.pick_ip_in_static_range(ngi),
1709 subnet=subnet, interface=interface)
1710 other_static_ip = factory.make_StaticIPAddress(
1711 alloc_type=IPADDRESS_TYPE.STICKY,
1712 ip=factory.pick_ip_in_static_range(ngi, but_not=[static_ip.id]),
1713 subnet=subnet, interface=interface)
1714 static_id = static_ip.id
1715 nodegroup_v6 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1716 network_v6 = factory.make_ipv6_network(slash=24)
1717 new_subnet = factory.make_Subnet(
1718 vlan=interface.vlan, cidr=unicode(network_v6.cidr))
1719 new_ngi = factory.make_NodeGroupInterface(
1720 nodegroup_v6, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1721 subnet=new_subnet)
1722 new_subnet_static_ip = factory.make_StaticIPAddress(
1723 alloc_type=IPADDRESS_TYPE.STICKY,
1724 ip=factory.pick_ip_in_static_range(new_ngi),
1725 subnet=new_subnet, interface=interface)
1726 mock_remove_host_maps = self.patch_autospec(
1727 interface, "_remove_host_maps")
1728 mock_update_host_maps = self.patch_autospec(
1729 interface, "_update_host_maps")
1730 mock_update_dns_zones = self.patch_autospec(
1731 interface, "_update_dns_zones")
1732 new_static_ip = interface.update_ip_address(
1733 static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet)
1734 self.assertEquals(static_id, new_static_ip.id)
1735 self.assertEquals(IPADDRESS_TYPE.STICKY, new_static_ip.alloc_type)
1736 self.assertEquals(new_subnet, new_static_ip.subnet)
1737 self.assertIsNotNone(new_static_ip.ip)
1738 self.assertThat(
1739 mock_remove_host_maps,
1740 MockCallsMatch(
1741 call(nodegroup_v6, new_subnet_static_ip),
1742 call(nodegroup_v4, static_ip),
1743 ))
1744 self.assertThat(
1745 mock_update_host_maps,
1746 MockCallsMatch(
1747 call(nodegroup_v4, other_static_ip),
1748 call(nodegroup_v6, new_static_ip),
1749 ))
1750 self.assertThat(
1751 mock_update_dns_zones,
1752 MockCallsMatch(
1753 call([nodegroup_v4]),
1754 call([nodegroup_v6]),
1755 ))
1756
1757 def test__switch_static_to_another_subnet_with_ip_address(self):
1758 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1759 nodegroup_v4 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1760 network_v4 = factory.make_ipv4_network(slash=24)
1761 subnet = factory.make_Subnet(
1762 vlan=interface.vlan, cidr=unicode(network_v4.cidr))
1763 ngi = factory.make_NodeGroupInterface(
1764 nodegroup_v4, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1765 subnet=subnet)
1766 static_ip = factory.make_StaticIPAddress(
1767 alloc_type=IPADDRESS_TYPE.STICKY,
1768 ip=factory.pick_ip_in_static_range(ngi),
1769 subnet=subnet, interface=interface)
1770 static_id = static_ip.id
1771 nodegroup_v6 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
1772 network_v6 = factory.make_ipv6_network(slash=24)
1773 new_subnet = factory.make_Subnet(
1774 vlan=interface.vlan, cidr=unicode(network_v6.cidr))
1775 new_ngi = factory.make_NodeGroupInterface(
1776 nodegroup_v6, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
1777 subnet=new_subnet)
1778 new_ip_address = factory.pick_ip_in_static_range(new_ngi)
1779 mock_remove_host_maps = self.patch_autospec(
1780 interface, "_remove_host_maps")
1781 mock_update_host_maps = self.patch_autospec(
1782 interface, "_update_host_maps")
1783 mock_update_dns_zones = self.patch_autospec(
1784 interface, "_update_dns_zones")
1785 new_static_ip = interface.update_ip_address(
1786 static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet,
1787 ip_address=new_ip_address)
1788 self.assertEquals(static_id, new_static_ip.id)
1789 self.assertEquals(IPADDRESS_TYPE.STICKY, new_static_ip.alloc_type)
1790 self.assertEquals(new_subnet, new_static_ip.subnet)
1791 self.assertEquals(new_ip_address, new_static_ip.ip)
1792 self.assertThat(
1793 mock_remove_host_maps,
1794 MockCalledOnceWith(nodegroup_v4, static_ip))
1795 self.assertThat(
1796 mock_update_host_maps,
1797 MockCalledOnceWith(nodegroup_v6, new_static_ip))
1798 self.assertThat(
1799 mock_update_dns_zones,
1800 MockCallsMatch(
1801 call([nodegroup_v4]),
1802 call([nodegroup_v6]),
1803 ))
1804
1805
1806class TestUpdateLinkById(MAASServerTestCase):
1807 """Tests for `Interface.update_link_by_id`."""
1808
1809 def test__calls_update_ip_address_with_ip_address(self):
1810 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1811 subnet = factory.make_Subnet(vlan=interface.vlan)
1812 static_ip = factory.make_StaticIPAddress(
1813 alloc_type=IPADDRESS_TYPE.DHCP, ip="",
1814 subnet=subnet, interface=interface)
1815 mock_update_ip_address = self.patch_autospec(
1816 interface, "update_ip_address")
1817 interface.update_link_by_id(
1818 static_ip.id, INTERFACE_LINK_TYPE.AUTO, subnet)
1819 self.expectThat(mock_update_ip_address, MockCalledOnceWith(
1820 static_ip, INTERFACE_LINK_TYPE.AUTO, subnet, ip_address=None))
1821
1822
1321class TestClaimAutoIPs(MAASServerTestCase):1823class TestClaimAutoIPs(MAASServerTestCase):
1322 """Tests for `Interface.claim_auto_ips`."""1824 """Tests for `Interface.claim_auto_ips`."""
13231825
13241826
=== modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js'
--- src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-09-29 15:19:25 +0000
+++ src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-10-01 14:33:47 +0000
@@ -6,10 +6,10 @@
66
7angular.module('MAAS').controller('NodeNetworkingController', [7angular.module('MAAS').controller('NodeNetworkingController', [
8 '$scope', 'FabricsManager', 'VLANsManager', 'SubnetsManager',8 '$scope', 'FabricsManager', 'VLANsManager', 'SubnetsManager',
9 'NodesManager', 'ManagerHelperService',9 'NodesManager', 'ManagerHelperService', 'ValidationService',
10 function(10 function(
11 $scope, FabricsManager, VLANsManager, SubnetsManager, NodesManager,11 $scope, FabricsManager, VLANsManager, SubnetsManager, NodesManager,
12 ManagerHelperService) {12 ManagerHelperService, ValidationService) {
1313
14 // Different interface types.14 // Different interface types.
15 var INTERFACE_TYPE = {15 var INTERFACE_TYPE = {
@@ -36,7 +36,7 @@
36 "auto": "Auto assign",36 "auto": "Auto assign",
37 "static": "Static assign",37 "static": "Static assign",
38 "dhcp": "DHCP",38 "dhcp": "DHCP",
39 "link_up": "Unconfigured"39 "link_up": "No IP"
40 };40 };
4141
42 // Set the initial values for this scope.42 // Set the initial values for this scope.
@@ -46,6 +46,7 @@
46 $scope.column = 'name';46 $scope.column = 'name';
47 $scope.fabrics = FabricsManager.getItems();47 $scope.fabrics = FabricsManager.getItems();
48 $scope.vlans = VLANsManager.getItems();48 $scope.vlans = VLANsManager.getItems();
49 $scope.subnets = SubnetsManager.getItems();
49 $scope.interfaces = [];50 $scope.interfaces = [];
50 $scope.interfaceLinksMap = {};51 $scope.interfaceLinksMap = {};
51 $scope.originalInterfaces = {};52 $scope.originalInterfaces = {};
@@ -119,7 +120,7 @@
119 // disabled or has no links (which means the interface120 // disabled or has no links (which means the interface
120 // is in LINK_UP mode).121 // is in LINK_UP mode).
121 nic.link_id = -1;122 nic.link_id = -1;
122 nic.subnet_id = null;123 nic.subnet = null;
123 nic.mode = LINK_MODE.LINK_UP;124 nic.mode = LINK_MODE.LINK_UP;
124 nic.ip_address = "";125 nic.ip_address = "";
125 interfaces.push(nic);126 interfaces.push(nic);
@@ -128,9 +129,13 @@
128 angular.forEach(nic.links, function(link) {129 angular.forEach(nic.links, function(link) {
129 var nic_copy = angular.copy(nic);130 var nic_copy = angular.copy(nic);
130 nic_copy.link_id = link.id;131 nic_copy.link_id = link.id;
131 nic_copy.subnet_id = link.subnet_id;132 nic_copy.subnet = SubnetsManager.getItemFromList(
133 link.subnet_id);
132 nic_copy.mode = link.mode;134 nic_copy.mode = link.mode;
133 nic_copy.ip_address = link.ip_address;135 nic_copy.ip_address = link.ip_address;
136 if(angular.isUndefined(nic_copy.ip_address)) {
137 nic_copy.ip_address = "";
138 }
134 // We don't want to deep copy the VLAN and fabric139 // We don't want to deep copy the VLAN and fabric
135 // object so we set those back to the original.140 // object so we set those back to the original.
136 nic_copy.vlan = nic.vlan;141 nic_copy.vlan = nic.vlan;
@@ -176,6 +181,23 @@
176 }181 }
177 }182 }
178183
184 // Return the original link object for the given interface.
185 function mapNICToOriginalLink(nic) {
186 var originalInteface = $scope.originalInterfaces[nic.id];
187 if(angular.isObject(originalInteface)) {
188 var i, link = null;
189 for(i = 0; i < originalInteface.links.length; i++) {
190 link = originalInteface.links[i];
191 if(link.id === nic.link_id) {
192 break;
193 }
194 }
195 return link;
196 } else {
197 return null;
198 }
199 }
200
179 // Called by $parent when the node has been loaded.201 // Called by $parent when the node has been loaded.
180 $scope.nodeLoaded = function() {202 $scope.nodeLoaded = function() {
181 $scope.$watch("node.interfaces", updateInterfaces);203 $scope.$watch("node.interfaces", updateInterfaces);
@@ -203,22 +225,25 @@
203 }225 }
204 };226 };
205227
206 // Get the subnet for the interface.228 // Get the text to display in the VLAN dropdown.
207 $scope.getSubnet = function(nic) {229 $scope.getVLANText = function(vlan) {
208 return SubnetsManager.getItemFromList(nic.subnet_id);230 if(angular.isString(vlan.name) && vlan.name.length > 0) {
209 };231 return vlan.vid + " (" + vlan.name + ")";
210
211 // Get the name of the subnet for this interface.
212 $scope.getSubnetName = function(nic) {
213 if(angular.isNumber(nic.subnet_id)) {
214 var subnet = $scope.getSubnet(nic);
215 if(angular.isObject(subnet)) {
216 return subnet.name;
217 } else {
218 return "Unknown";
219 }
220 } else {232 } else {
233 return vlan.vid;
234 }
235 };
236
237 // Get the text to display in the subnet dropdown.
238 $scope.getSubnetText = function(subnet) {
239 if(!angular.isObject(subnet)) {
221 return "Unconfigured";240 return "Unconfigured";
241 } else if(angular.isString(subnet.name) &&
242 subnet.name.length > 0 &&
243 subnet.cidr !== subnet.name) {
244 return subnet.cidr + " (" + subnet.name + ")";
245 } else {
246 return subnet.cidr;
222 }247 }
223 };248 };
224249
@@ -240,10 +265,10 @@
240 // Save the following interface on the node. This will only save if265 // Save the following interface on the node. This will only save if
241 // the interface has changed.266 // the interface has changed.
242 $scope.saveInterface = function(nic) {267 $scope.saveInterface = function(nic) {
243 // If the name or vlan has changed then we need to update
244 // the interface.
245 var originalInteface = $scope.originalInterfaces[nic.id];268 var originalInteface = $scope.originalInterfaces[nic.id];
246 if(originalInteface.name !== nic.name ||269 if($scope.isInterfaceNameInvalid(nic)) {
270 nic.name = originalInteface.name;
271 } else if(originalInteface.name !== nic.name ||
247 originalInteface.vlan_id !== nic.vlan.id) {272 originalInteface.vlan_id !== nic.vlan.id) {
248 var params = {273 var params = {
249 "name": nic.name,274 "name": nic.name,
@@ -255,6 +280,10 @@
255 // we need to expose this as a better message to the280 // we need to expose this as a better message to the
256 // user.281 // user.
257 console.log(error);282 console.log(error);
283
284 // Update the interfaces so it is back to the way it
285 // was before the user changed it.
286 updateInterfaces();
258 });287 });
259 }288 }
260 };289 };
@@ -268,10 +297,16 @@
268 // if it has changed.297 // if it has changed.
269 $scope.clearFocusInterface = function(nic) {298 $scope.clearFocusInterface = function(nic) {
270 if(angular.isUndefined(nic)) {299 if(angular.isUndefined(nic)) {
271 $scope.saveInterface($scope.focusInterface);300 if($scope.focusInterface.type !== INTERFACE_TYPE.ALIAS) {
301 $scope.saveInterface($scope.focusInterface);
302 }
303 $scope.saveInterfaceIPAddress($scope.focusInterface);
272 $scope.focusInterface = null;304 $scope.focusInterface = null;
273 } else if($scope.focusInterface === nic) {305 } else if($scope.focusInterface === nic) {
274 $scope.saveInterface($scope.focusInterface);306 if($scope.focusInterface.type !== INTERFACE_TYPE.ALIAS) {
307 $scope.saveInterface($scope.focusInterface);
308 }
309 $scope.saveInterfaceIPAddress($scope.focusInterface);
275 $scope.focusInterface = null;310 $scope.focusInterface = null;
276 }311 }
277 };312 };
@@ -300,6 +335,122 @@
300 $scope.saveInterface(nic);335 $scope.saveInterface(nic);
301 };336 };
302337
338 // Return True if the link mode select should be disabled.
339 $scope.isLinkModeDisabled = function(nic) {
340 // This is only disabled when a subnet has not been selected.
341 return !angular.isObject(nic.subnet);
342 };
343
344 // Get the available link modes for an interface.
345 $scope.getLinkModes = function(nic) {
346 modes = [];
347 if(!angular.isObject(nic.subnet)) {
348 // No subnet is configure so the only allowed mode
349 // is 'link_up'.
350 modes.push({
351 "mode": LINK_MODE.LINK_UP,
352 "text": LINK_MODE_TEXTS[LINK_MODE.LINK_UP]
353 });
354 } else {
355 angular.forEach(LINK_MODE_TEXTS, function(text, mode) {
356 // Don't add LINK_UP or DHCP if more than one link exists.
357 if(nic.links.length > 1 && (
358 mode === LINK_MODE.LINK_UP ||
359 mode === LINK_MODE.DHCP)) {
360 return;
361 }
362 modes.push({
363 "mode": mode,
364 "text": text
365 });
366 });
367 }
368 return modes;
369 };
370
371 // Called when the link mode for this interface and link has been
372 // changed.
373 $scope.saveInterfaceLink = function(nic) {
374 var params = {
375 "mode": nic.mode
376 };
377 if(angular.isObject(nic.subnet)) {
378 params.subnet = nic.subnet.id;
379 }
380 if(nic.link_id >= 0) {
381 params.link_id = nic.link_id;
382 }
383 if(nic.mode === LINK_MODE.STATIC && nic.ip_address.length > 0) {
384 params.ip_address = nic.ip_address;
385 }
386 NodesManager.linkSubnet($scope.node, nic.id, params).then(
387 null, function(error) {
388 // XXX blake_r: Just log the error in the console, but
389 // we need to expose this as a better message to the
390 // user.
391 console.log(error);
392
393 // Update the interfaces so it is back to the way it
394 // was before the user changed it.
395 updateInterfaces();
396 });
397 };
398
399 // Called when the user changes the subnet.
400 $scope.subnetChanged = function(nic) {
401 if(!angular.isObject(nic.subnet)) {
402 // Set to 'Unconfigured' so the link mode should be set to
403 // 'link_up'.
404 nic.mode = LINK_MODE.LINK_UP;
405 }
406 // Clear the IP address so a new one on the subnet is assigned.
407 nic.ip_address = "";
408 $scope.saveInterfaceLink(nic);
409 };
410
411 // Return True when the IP address input field should be shown.
412 $scope.shouldShowIPAddress = function(nic) {
413 if(nic.mode === LINK_MODE.STATIC) {
414 // Check that the original has an IP address if it doesn't then
415 // it should not be shown as the IP address still has not been
416 // loaded over the websocket. If the subnets have been switched
417 // then the IP address has been clear, don't show the IP
418 // address until the original subnet and nic subnet match.
419 var originalLink = mapNICToOriginalLink(nic);
420 return (
421 angular.isObject(originalLink) &&
422 angular.isString(originalLink.ip_address) &&
423 originalLink.ip_address.length > 0 &&
424 angular.isObject(nic.subnet) &&
425 nic.subnet.id === originalLink.subnet_id);
426 } else if(angular.isString(nic.ip_address) &&
427 nic.ip_address.length > 0) {
428 return true;
429 } else {
430 return false;
431 }
432 };
433
434 // Return True if the interface IP address that the user typed is
435 // invalid.
436 $scope.isIPAddressInvalid = function(nic) {
437 return (nic.ip_address.length === 0 ||
438 !ValidationService.validateIP(nic.ip_address) ||
439 !ValidationService.validateIPInNetwork(
440 nic.ip_address, nic.subnet.cidr));
441 };
442
443 // Save the interface IP address.
444 $scope.saveInterfaceIPAddress = function(nic) {
445 var originalLink = mapNICToOriginalLink(nic);
446 var prevIPAddress = originalLink.ip_address;
447 if($scope.isIPAddressInvalid(nic)) {
448 nic.ip_address = prevIPAddress;
449 } else if(nic.ip_address !== prevIPAddress) {
450 $scope.saveInterfaceLink(nic);
451 }
452 };
453
303 // Load all the required managers.454 // Load all the required managers.
304 ManagerHelperService.loadManagers([455 ManagerHelperService.loadManagers([
305 FabricsManager,456 FabricsManager,
306457
=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js'
--- src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-09-29 15:19:25 +0000
+++ src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-01 14:33:47 +0000
@@ -65,6 +65,9 @@
65 expect($scope.nodeHasLoaded).toBe(false);65 expect($scope.nodeHasLoaded).toBe(false);
66 expect($scope.managersHaveLoaded).toBe(false);66 expect($scope.managersHaveLoaded).toBe(false);
67 expect($scope.column).toBe('name');67 expect($scope.column).toBe('name');
68 expect($scope.fabrics).toBe(FabricsManager.getItems());
69 expect($scope.vlans).toBe(VLANsManager.getItems());
70 expect($scope.subnets).toBe(SubnetsManager.getItems());
68 expect($scope.interfaces).toEqual([]);71 expect($scope.interfaces).toEqual([]);
69 expect($scope.interfaceLinksMap).toEqual({});72 expect($scope.interfaceLinksMap).toEqual({});
70 expect($scope.originalInterfaces).toEqual({});73 expect($scope.originalInterfaces).toEqual({});
@@ -212,7 +215,7 @@
212 members: [parent1, parent2],215 members: [parent1, parent2],
213 vlan: null,216 vlan: null,
214 link_id: -1,217 link_id: -1,
215 subnet_id: null,218 subnet: null,
216 mode: "link_up",219 mode: "link_up",
217 ip_address: ""220 ip_address: ""
218 }]);221 }]);
@@ -297,13 +300,15 @@
297 links: [],300 links: [],
298 vlan: null,301 vlan: null,
299 link_id: -1,302 link_id: -1,
300 subnet_id: null,303 subnet: null,
301 mode: "link_up",304 mode: "link_up",
302 ip_address: ""305 ip_address: ""
303 }]);306 }]);
304 });307 });
305308
306 it("duplicates links as alias interfaces", function() {309 it("duplicates links as alias interfaces", function() {
310 var subnet0 = { id: 0 }, subnet1 = { id: 1 }, subnet2 = { id: 2 };
311 SubnetsManager._items = [subnet0, subnet1, subnet2];
307 var links = [312 var links = [
308 {313 {
309 id: 0,314 id: 0,
@@ -345,7 +350,7 @@
345 vlan: null,350 vlan: null,
346 fabric: undefined,351 fabric: undefined,
347 link_id: 0,352 link_id: 0,
348 subnet_id: 0,353 subnet: subnet0,
349 mode: "dhcp",354 mode: "dhcp",
350 ip_address: ""355 ip_address: ""
351 },356 },
@@ -359,7 +364,7 @@
359 vlan: null,364 vlan: null,
360 fabric: undefined,365 fabric: undefined,
361 link_id: 1,366 link_id: 1,
362 subnet_id: 1,367 subnet: subnet1,
363 mode: "auto",368 mode: "auto",
364 ip_address: ""369 ip_address: ""
365 },370 },
@@ -373,7 +378,7 @@
373 vlan: null,378 vlan: null,
374 fabric: undefined,379 fabric: undefined,
375 link_id: 2,380 link_id: 2,
376 subnet_id: 2,381 subnet: subnet2,
377 mode: "static",382 mode: "static",
378 ip_address: "192.168.122.10"383 ip_address: "192.168.122.10"
379 }384 }
@@ -470,7 +475,7 @@
470 "auto": "Auto assign",475 "auto": "Auto assign",
471 "static": "Static assign",476 "static": "Static assign",
472 "dhcp": "DHCP",477 "dhcp": "DHCP",
473 "link_up": "Unconfigured",478 "link_up": "No IP",
474 "missing_type": "missing_type"479 "missing_type": "missing_type"
475 };480 };
476481
@@ -485,64 +490,63 @@
485 });490 });
486 });491 });
487492
488 describe("getSubnet", function() {493 describe("getVLANText", function() {
489494
490 it("returns item from SubnetsManager", function() {495 it("returns just vid", function() {
491 var controller = makeController();496 var controller = makeController();
492 var subnet_id = makeInteger(0, 100);497 var vlan = {
493 var subnet = {498 vid: 5
494 id: subnet_id499 };
495 };500 expect($scope.getVLANText(vlan)).toBe(5);
496 SubnetsManager._items = [subnet];
497
498 var nic = {
499 subnet_id: subnet_id
500 };
501 expect($scope.getSubnet(nic)).toBe(subnet);
502 });501 });
503502
504 it("returns null for missing subnet", function() {503 it("returns vid + name", function() {
505 var controller = makeController();504 var controller = makeController();
506 var subnet_id = makeInteger(0, 100);505 var name = makeName("vlan");
507 var nic = {506 var vlan = {
508 subnet_id: subnet_id507 vid: 5,
508 name: name
509 };509 };
510 expect($scope.getSubnet(nic)).toBeNull();510 expect($scope.getVLANText(vlan)).toBe("5 (" + name + ")");
511 });511 });
512 });512 });
513513
514 describe("getSubnetName", function() {514 describe("getSubnetText", function() {
515515
516 it("returns name from item in SubnetsManager", function() {516 it("returns 'Unconfigured' for null", function() {
517 var controller = makeController();517 var controller = makeController();
518 var subnet_id = makeInteger(0, 100);518 expect($scope.getSubnetText(null)).toBe("Unconfigured");
519 var subnet_name = makeName("subnet");519 });
520 var subnet = {520
521 id: subnet_id,521 it("returns just cidr if no name", function() {
522 name: subnet_name522 var controller = makeController();
523 };523 var cidr = makeName("cidr");
524 SubnetsManager._items = [subnet];524 var subnet = {
525525 cidr: cidr
526 var nic = {526 };
527 subnet_id: subnet_id527 expect($scope.getSubnetText(subnet)).toBe(cidr);
528 };528 });
529 expect($scope.getSubnetName(nic)).toBe(subnet_name);529
530 });530 it("returns just cidr if name same as cidr", function() {
531531 var controller = makeController();
532 it("returns 'Unknown' if item not in SubnetsManager", function() {532 var cidr = makeName("cidr");
533 var controller = makeController();533 var subnet = {
534 var nic = {534 cidr: cidr,
535 subnet_id: makeInteger(0, 100)535 name: cidr
536 };536 };
537 expect($scope.getSubnetName(nic)).toBe("Unknown");537 expect($scope.getSubnetText(subnet)).toBe(cidr);
538 });538 });
539539
540 it("returns 'Unconfigured' if no subnet_id", function() {540 it("returns cidr + name", function() {
541 var controller = makeController();541 var controller = makeController();
542 var nic = {542 var cidr = makeName("cidr");
543 subnet_id: null543 var name = makeName("name");
544 };544 var subnet = {
545 expect($scope.getSubnetName(nic)).toBe("Unconfigured");545 cidr: cidr,
546 name: name
547 };
548 expect($scope.getSubnetText(subnet)).toBe(
549 cidr + " (" + name + ")");
546 });550 });
547 });551 });
548552
@@ -615,6 +619,31 @@
615 expect(NodesManager.updateInterface).not.toHaveBeenCalled();619 expect(NodesManager.updateInterface).not.toHaveBeenCalled();
616 });620 });
617621
622 it("resets name if its invalid and doesn't call update", function() {
623 var controller = makeController();
624 var id = makeInteger(0, 100);
625 var name = makeName("nic");
626 var vlan = { id: makeInteger(0, 100) };
627 var original_nic = {
628 id: id,
629 name: name,
630 vlan_id: vlan.id
631 };
632 var nic = {
633 id: id,
634 name: "",
635 vlan: vlan
636 };
637 $scope.originalInterfaces[id] = original_nic;
638 $scope.interfaces = [nic];
639
640 spyOn(NodesManager, "updateInterface").and.returnValue(
641 $q.defer().promise);
642 $scope.saveInterface(nic);
643 expect(nic.name).toBe(name);
644 expect(NodesManager.updateInterface).not.toHaveBeenCalled();
645 });
646
618 it("calls NodesManager.updateInterface if name changed", function() {647 it("calls NodesManager.updateInterface if name changed", function() {
619 var controller = makeController();648 var controller = makeController();
620 var id = makeInteger(0, 100);649 var id = makeInteger(0, 100);
@@ -686,32 +715,72 @@
686715
687 it("clears focusInterface no arguments", function() {716 it("clears focusInterface no arguments", function() {
688 var controller = makeController();717 var controller = makeController();
689 var nic = {};718 var nic = {
719 type: "physical"
720 };
690 $scope.focusInterface = nic;721 $scope.focusInterface = nic;
691 spyOn($scope, "saveInterface");722 spyOn($scope, "saveInterface");
723 spyOn($scope, "saveInterfaceIPAddress");
692 $scope.clearFocusInterface();724 $scope.clearFocusInterface();
693 expect($scope.focusInterface).toBeNull();725 expect($scope.focusInterface).toBeNull();
694 expect($scope.saveInterface).toHaveBeenCalledWith(nic);726 expect($scope.saveInterface).toHaveBeenCalledWith(nic);
727 expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic);
695 });728 });
696729
697 it("clears focusInterface if same interface", function() {730 it("clears focusInterface if same interface", function() {
698 var controller = makeController();731 var controller = makeController();
699 var nic = {};732 var nic = {
733 type: "physical"
734 };
700 $scope.focusInterface = nic;735 $scope.focusInterface = nic;
701 spyOn($scope, "saveInterface");736 spyOn($scope, "saveInterface");
737 spyOn($scope, "saveInterfaceIPAddress");
702 $scope.clearFocusInterface(nic);738 $scope.clearFocusInterface(nic);
703 expect($scope.focusInterface).toBeNull();739 expect($scope.focusInterface).toBeNull();
704 expect($scope.saveInterface).toHaveBeenCalledWith(nic);740 expect($scope.saveInterface).toHaveBeenCalledWith(nic);
741 expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic);
705 });742 });
706743
707 it("doesnt clear focusInterface if different interface", function() {744 it("doesnt clear focusInterface if different interface", function() {
708 var controller = makeController();745 var controller = makeController();
709 var nic = {};746 var nic = {
747 type: "physical"
748 };
710 $scope.focusInterface = nic;749 $scope.focusInterface = nic;
711 spyOn($scope, "saveInterface");750 spyOn($scope, "saveInterface");
751 spyOn($scope, "saveInterfaceIPAddress");
712 $scope.clearFocusInterface({});752 $scope.clearFocusInterface({});
713 expect($scope.focusInterface).toBe(nic);753 expect($scope.focusInterface).toBe(nic);
714 expect($scope.saveInterface).not.toHaveBeenCalled();754 expect($scope.saveInterface).not.toHaveBeenCalled();
755 expect($scope.saveInterfaceIPAddress).not.toHaveBeenCalled();
756 });
757
758 it("doesnt call save with focusInterface no arguments", function() {
759 var controller = makeController();
760 var nic = {
761 type: "alias"
762 };
763 $scope.focusInterface = nic;
764 spyOn($scope, "saveInterface");
765 spyOn($scope, "saveInterfaceIPAddress");
766 $scope.clearFocusInterface();
767 expect($scope.focusInterface).toBeNull();
768 expect($scope.saveInterface).not.toHaveBeenCalled();
769 expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic);
770 });
771
772 it("doesnt call save with focusInterface if same nic", function() {
773 var controller = makeController();
774 var nic = {
775 type: "alias"
776 };
777 $scope.focusInterface = nic;
778 spyOn($scope, "saveInterface");
779 spyOn($scope, "saveInterfaceIPAddress");
780 $scope.clearFocusInterface(nic);
781 expect($scope.focusInterface).toBeNull();
782 expect($scope.saveInterface).not.toHaveBeenCalled();
783 expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic);
715 });784 });
716 });785 });
717786
@@ -812,4 +881,407 @@
812 expect($scope.saveInterface).toHaveBeenCalledWith(nic);881 expect($scope.saveInterface).toHaveBeenCalledWith(nic);
813 });882 });
814 });883 });
884
885 describe("isLinkModeDisabled", function() {
886
887 it("enabled when subnet", function() {
888 var controller = makeController();
889 var nic = {
890 subnet : {}
891 };
892 expect($scope.isLinkModeDisabled(nic)).toBe(false);
893 });
894
895 it("disabled when not subnet", function() {
896 var controller = makeController();
897 var nic = {
898 subnet : null
899 };
900 expect($scope.isLinkModeDisabled(nic)).toBe(true);
901 });
902 });
903
904 describe("getLinkModes", function() {
905
906 it("only link_up when no subnet", function() {
907 var controller = makeController();
908 var nic = {
909 subnet : null
910 };
911 expect($scope.getLinkModes(nic)).toEqual([
912 {
913 "mode": "link_up",
914 "text": "No IP"
915 }
916 ]);
917 });
918
919 it("all modes if only one link", function() {
920 var controller = makeController();
921 var nic = {
922 subnet : {},
923 links: [{}]
924 };
925 expect($scope.getLinkModes(nic)).toEqual([
926 {
927 "mode": "auto",
928 "text": "Auto assign"
929 },
930 {
931 "mode": "static",
932 "text": "Static assign"
933 },
934 {
935 "mode": "dhcp",
936 "text": "DHCP"
937 },
938 {
939 "mode": "link_up",
940 "text": "No IP"
941 }
942 ]);
943 });
944
945 it("auto and static modes if more than one link", function() {
946 var controller = makeController();
947 var nic = {
948 subnet : {},
949 links: [{}, {}]
950 };
951 expect($scope.getLinkModes(nic)).toEqual([
952 {
953 "mode": "auto",
954 "text": "Auto assign"
955 },
956 {
957 "mode": "static",
958 "text": "Static assign"
959 }
960 ]);
961 });
962 });
963
964 describe("saveInterfaceLink", function() {
965
966 it("calls NodesManager.linkSubnet with params", function() {
967 var controller = makeController();
968 var nic = {
969 id: makeInteger(0, 100),
970 mode: "static",
971 subnet: { id: makeInteger(0, 100) },
972 link_id: makeInteger(0, 100),
973 ip_address: "192.168.122.1"
974 };
975 spyOn(NodesManager, "linkSubnet").and.returnValue(
976 $q.defer().promise);
977 $scope.saveInterfaceLink(nic);
978 expect(NodesManager.linkSubnet).toHaveBeenCalledWith(
979 node, nic.id, {
980 "mode": "static",
981 "subnet": nic.subnet.id,
982 "link_id": nic.link_id,
983 "ip_address": nic.ip_address
984 });
985 });
986 });
987
988 describe("subnetChanged", function() {
989
990 it("sets mode to link_up if set to no subnet", function() {
991 var controller = makeController();
992 var nic = {
993 subnet: null
994 };
995 spyOn($scope, "saveInterfaceLink");
996 $scope.subnetChanged(nic);
997 expect(nic.mode).toBe("link_up");
998 expect($scope.saveInterfaceLink).toHaveBeenCalledWith(nic);
999 });
1000
1001 it("doesnt set mode to link_up if set if subnet", function() {
1002 var controller = makeController();
1003 var nic = {
1004 mode: "static",
1005 subnet: {}
1006 };
1007 spyOn($scope, "saveInterfaceLink");
1008 $scope.subnetChanged(nic);
1009 expect(nic.mode).toBe("static");
1010 expect($scope.saveInterfaceLink).toHaveBeenCalledWith(nic);
1011 });
1012
1013 it("clears ip_address", function() {
1014 var controller = makeController();
1015 var nic = {
1016 subnet: null,
1017 ip_address: makeName("ip")
1018 };
1019 spyOn($scope, "saveInterfaceLink");
1020 $scope.subnetChanged(nic);
1021 expect(nic.ip_address).toBe("");
1022 });
1023 });
1024
1025 describe("shouldShowIPAddress", function() {
1026
1027 it("true if not static and has ip address", function() {
1028 var controller = makeController();
1029 var nic = {
1030 mode: "auto",
1031 ip_address: "192.168.122.1"
1032 };
1033 expect($scope.shouldShowIPAddress(nic)).toBe(true);
1034 });
1035
1036 it("false if not static and doesn't have ip address", function() {
1037 var controller = makeController();
1038 var nic = {
1039 mode: "dhcp",
1040 ip_address: ""
1041 };
1042 expect($scope.shouldShowIPAddress(nic)).toBe(false);
1043 });
1044
1045 describe("static", function() {
1046
1047 it("false if no orginial link", function() {
1048 var controller = makeController();
1049 var nic = {
1050 id: 0,
1051 mode: "static",
1052 link_id: -1,
1053 ip_address: ""
1054 };
1055 expect($scope.shouldShowIPAddress(nic)).toBe(false);
1056 });
1057
1058 it("false if orginial link has no IP address", function() {
1059 var controller = makeController();
1060 var originalInterface = {
1061 id: 0,
1062 links: [
1063 {
1064 id: 0,
1065 mode: "static"
1066 }
1067 ]
1068 };
1069 $scope.originalInterfaces = [originalInterface];
1070
1071 var nic = {
1072 id: 0,
1073 mode: "static",
1074 link_id: 0,
1075 ip_address: ""
1076 };
1077 expect($scope.shouldShowIPAddress(nic)).toBe(false);
1078 });
1079
1080 it("false if orginial link has empty IP address", function() {
1081 var controller = makeController();
1082 var originalInterface = {
1083 id: 0,
1084 links: [
1085 {
1086 id: 0,
1087 mode: "static",
1088 ip_address: ""
1089 }
1090 ]
1091 };
1092 $scope.originalInterfaces = [originalInterface];
1093
1094 var nic = {
1095 id: 0,
1096 mode: "static",
1097 link_id: 0,
1098 ip_address: ""
1099 };
1100 expect($scope.shouldShowIPAddress(nic)).toBe(false);
1101 });
1102
1103 it("false if no subnet on nic", function() {
1104 var controller = makeController();
1105 var originalInterface = {
1106 id: 0,
1107 links: [
1108 {
1109 id: 0,
1110 mode: "static",
1111 ip_address: "192.168.122.2"
1112 }
1113 ]
1114 };
1115 $scope.originalInterfaces = [originalInterface];
1116
1117 var nic = {
1118 id: 0,
1119 mode: "static",
1120 link_id: 0,
1121 ip_address: ""
1122 };
1123 expect($scope.shouldShowIPAddress(nic)).toBe(false);
1124 });
1125
1126 it("false if the subnets don't match", function() {
1127 var controller = makeController();
1128 var originalInterface = {
1129 id: 0,
1130 links: [
1131 {
1132 id: 0,
1133 mode: "static",
1134 ip_address: "192.168.122.2",
1135 subnet_id: 0
1136 }
1137 ]
1138 };
1139 $scope.originalInterfaces = [originalInterface];
1140
1141 var nic = {
1142 id: 0,
1143 mode: "static",
1144 link_id: 0,
1145 ip_address: "",
1146 subnet: {
1147 id: 1
1148 }
1149 };
1150 expect($scope.shouldShowIPAddress(nic)).toBe(false);
1151 });
1152
1153 it("true if all condititions match", function() {
1154 var controller = makeController();
1155 var originalInterface = {
1156 id: 0,
1157 links: [
1158 {
1159 id: 0,
1160 mode: "static",
1161 ip_address: "192.168.122.2",
1162 subnet_id: 0
1163 }
1164 ]
1165 };
1166 $scope.originalInterfaces = [originalInterface];
1167
1168 var nic = {
1169 id: 0,
1170 mode: "static",
1171 link_id: 0,
1172 ip_address: "",
1173 subnet: {
1174 id: 0
1175 }
1176 };
1177 expect($scope.shouldShowIPAddress(nic)).toBe(true);
1178 });
1179 });
1180 });
1181
1182 describe("isIPAddressInvalid", function() {
1183
1184 it("true if empty IP address", function() {
1185 var controller = makeController();
1186 var nic = {
1187 ip_address: ""
1188 };
1189 expect($scope.isIPAddressInvalid(nic)).toBe(true);
1190 });
1191
1192 it("true if not valid IP address", function() {
1193 var controller = makeController();
1194 var nic = {
1195 ip_address: "192.168.260.5"
1196 };
1197 expect($scope.isIPAddressInvalid(nic)).toBe(true);
1198 });
1199
1200 it("true if IP address not in subnet", function() {
1201 var controller = makeController();
1202 var nic = {
1203 ip_address: "192.168.123.10",
1204 subnet: {
1205 cidr: "192.168.122.0/24"
1206 }
1207 };
1208 expect($scope.isIPAddressInvalid(nic)).toBe(true);
1209 });
1210
1211 it("false if IP address in subnet", function() {
1212 var controller = makeController();
1213 var nic = {
1214 ip_address: "192.168.122.10",
1215 subnet: {
1216 cidr: "192.168.122.0/24"
1217 }
1218 };
1219 expect($scope.isIPAddressInvalid(nic)).toBe(false);
1220 });
1221 });
1222
1223 describe("saveInterfaceIPAddress", function() {
1224
1225 it("resets IP address if invalid doesn't save", function() {
1226 var controller = makeController();
1227 var originalInterface = {
1228 id: 0,
1229 links: [
1230 {
1231 id: 0,
1232 mode: "static",
1233 ip_address: "192.168.122.10",
1234 subnet_id: 0
1235 }
1236 ]
1237 };
1238 $scope.originalInterfaces = [originalInterface];
1239
1240 var nic = {
1241 id: 0,
1242 mode: "static",
1243 link_id: 0,
1244 ip_address: "192.168.123.10",
1245 subnet: {
1246 id: 0,
1247 cidr: "192.168.122.0/24"
1248 }
1249 };
1250 spyOn($scope, "saveInterfaceLink");
1251 $scope.saveInterfaceIPAddress(nic);
1252 expect(nic.ip_address).toBe("192.168.122.10");
1253 expect($scope.saveInterfaceLink).not.toHaveBeenCalled();
1254 });
1255
1256 it("saves the link if valid", function() {
1257 var controller = makeController();
1258 var originalInterface = {
1259 id: 0,
1260 links: [
1261 {
1262 id: 0,
1263 mode: "static",
1264 ip_address: "192.168.122.10",
1265 subnet_id: 0
1266 }
1267 ]
1268 };
1269 $scope.originalInterfaces = [originalInterface];
1270
1271 var nic = {
1272 id: 0,
1273 mode: "static",
1274 link_id: 0,
1275 ip_address: "192.168.122.11",
1276 subnet: {
1277 id: 0,
1278 cidr: "192.168.122.0/24"
1279 }
1280 };
1281 spyOn($scope, "saveInterfaceLink");
1282 $scope.saveInterfaceIPAddress(nic);
1283 expect(nic.ip_address).toBe("192.168.122.11");
1284 expect($scope.saveInterfaceLink).toHaveBeenCalledWith(nic);
1285 });
1286 });
815});1287});
8161288
=== modified file 'src/maasserver/static/js/angular/factories/nodes.js'
--- src/maasserver/static/js/angular/factories/nodes.js 2015-09-30 22:40:08 +0000
+++ src/maasserver/static/js/angular/factories/nodes.js 2015-10-01 14:33:47 +0000
@@ -90,11 +90,23 @@
90 "node.update_interface", params);90 "node.update_interface", params);
91 };91 };
9292
93 // Send the update information to the region.93 // Create or update the link to the subnet for the interface.
94 NodesManager.prototype.linkSubnet = function(
95 node, interface_id, params) {
96 if(!angular.isObject(params)) {
97 params = {};
98 }
99 params.system_id = node.system_id;
100 params.interface_id = interface_id;
101 return RegionConnection.callMethod(
102 "node.link_subnet", params);
103 };
104
105 // Unmount the filesystem on the block device or partition.
94 NodesManager.prototype.unmountFilesystem = function(106 NodesManager.prototype.unmountFilesystem = function(
95 system_id, block_id, partition_id) {107 system_id, block_id, partition_id) {
96 var self = this;108 var self = this;
97 var method = this._handler + ".unmountFilesystem";109 var method = this._handler + ".unmount_filesystem";
98 var params = {110 var params = {
99 system_id: system_id,111 system_id: system_id,
100 block_id: block_id,112 block_id: block_id,
101113
=== modified file 'src/maasserver/static/js/angular/factories/tests/test_nodes.js'
--- src/maasserver/static/js/angular/factories/tests/test_nodes.js 2015-09-30 22:40:08 +0000
+++ src/maasserver/static/js/angular/factories/tests/test_nodes.js 2015-10-01 14:33:47 +0000
@@ -178,6 +178,49 @@
178 });178 });
179 });179 });
180180
181 describe("linkSubnet", function() {
182
183 it("calls node.link_subnet with system_id and interface_id",
184 function(done) {
185 var node = makeNode(), interface_id = makeInteger(0, 100);
186 webSocket.returnData.push(makeFakeResponse("updated"));
187 NodesManager.linkSubnet(node, interface_id).then(
188 function() {
189 var sentObject = angular.fromJson(
190 webSocket.sentData[0]);
191 expect(sentObject.method).toBe(
192 "node.link_subnet");
193 expect(sentObject.params.system_id).toBe(
194 node.system_id);
195 expect(sentObject.params.interface_id).toBe(
196 interface_id);
197 done();
198 });
199 });
200
201 it("calls node.link_subnet with params",
202 function(done) {
203 var node = makeNode(), interface_id = makeInteger(0, 100);
204 var params = {
205 name: makeName("eth0")
206 };
207 webSocket.returnData.push(makeFakeResponse("updated"));
208 NodesManager.linkSubnet(node, interface_id, params).then(
209 function() {
210 var sentObject = angular.fromJson(
211 webSocket.sentData[0]);
212 expect(sentObject.method).toBe(
213 "node.link_subnet");
214 expect(sentObject.params.system_id).toBe(
215 node.system_id);
216 expect(sentObject.params.interface_id).toBe(
217 interface_id);
218 expect(sentObject.params.name).toBe(params.name);
219 done();
220 });
221 });
222 });
223
181 describe("unmountFilesystem", function() {224 describe("unmountFilesystem", function() {
182225
183 it("calls node.unmountFilesystem", function(done) {226 it("calls node.unmountFilesystem", function(done) {
@@ -186,7 +229,7 @@
186 NodesManager.unmountFilesystem(229 NodesManager.unmountFilesystem(
187 makeName("block_id"), null).then(function() {230 makeName("block_id"), null).then(function() {
188 var sentObject = angular.fromJson(webSocket.sentData[0]);231 var sentObject = angular.fromJson(webSocket.sentData[0]);
189 expect(sentObject.method).toBe("node.unmountFilesystem");232 expect(sentObject.method).toBe("node.unmount_filesystem");
190 done();233 done();
191 });234 });
192 });235 });
@@ -200,6 +243,7 @@
200 fakeNode.system_id, block_id, partition_id).then(243 fakeNode.system_id, block_id, partition_id).then(
201 function() {244 function() {
202 var sentObject = angular.fromJson(webSocket.sentData[0]);245 var sentObject = angular.fromJson(webSocket.sentData[0]);
246 expect(sentObject.method).toBe("node.unmount_filesystem");
203 expect(sentObject.params.system_id).toBe(fakeNode.system_id);247 expect(sentObject.params.system_id).toBe(fakeNode.system_id);
204 expect(sentObject.params.block_id).toBe(block_id);248 expect(sentObject.params.block_id).toBe(block_id);
205 expect(sentObject.params.partition_id).toBe(partition_id);249 expect(sentObject.params.partition_id).toBe(partition_id);
206250
=== modified file 'src/maasserver/static/partials/node-details.html'
--- src/maasserver/static/partials/node-details.html 2015-10-01 11:15:36 +0000
+++ src/maasserver/static/partials/node-details.html 2015-10-01 14:33:47 +0000
@@ -358,6 +358,7 @@
358 <div class="table__data table__column--14">358 <div class="table__data table__column--14">
359 <select class="table__input" name="fabric" id="fabric"359 <select class="table__input" name="fabric" id="fabric"
360 data-ng-model="interface.fabric"360 data-ng-model="interface.fabric"
361 data-ng-disabled="interface.type == 'alias'"
361 data-ng-change="fabricChanged(interface)"362 data-ng-change="fabricChanged(interface)"
362 data-ng-options="fabric as fabric.name for fabric in fabrics">363 data-ng-options="fabric as fabric.name for fabric in fabrics">
363 </select>364 </select>
@@ -365,39 +366,37 @@
365 <div class="table__data table__column--14">366 <div class="table__data table__column--14">
366 <select class="table__input" name="vlan" id="vlan"367 <select class="table__input" name="vlan" id="vlan"
367 data-ng-model="interface.vlan"368 data-ng-model="interface.vlan"
369 data-ng-disabled="interface.type == 'alias'"
368 data-ng-change="saveInterface(interface)"370 data-ng-change="saveInterface(interface)"
369 data-ng-options="vlan as vlan.name for vlan in vlans | filterByFabric:interface.fabric">371 data-ng-options="vlan as getVLANText(vlan) for vlan in vlans | filterByFabric:interface.fabric">
370 </select>372 </select>
371 </div>373 </div>
372 <div class="table__data table__column--18">374 <div class="table__data table__column--18">
373 {$ getSubnetName(interface) $}375 <select class="table__input" name="subnet" id="subnet"
374 <!--376 data-ng-model="interface.subnet"
375 TODO: Use when editing is done.377 data-ng-change="subnetChanged(interface)"
376 <select class="table__input" name="range" id="range">378 data-ng-options="subnet as getSubnetText(subnet) for subnet in subnets | filterByVLAN:interface.vlan">
377 <option value="auto">10.10.10.1/6 (managed)</option>379 <option value="" data-ng-hide="interface.links.length > 1">Unconfigured</option>
378 </select>380 </select>
379 -->
380 </div>381 </div>
381 <div class="table__data table__column--14">382 <div class="table__data table__column--14">
382 <ul class="no-bullets">383 <ul class="no-bullets">
383 <li>384 <li>
384 {$ getLinkModeText(interface) $}385 <select class="table__input" name="link-mode" id="link-mode"
385 <!--386 data-ng-model="interface.mode"
386 TODO: Use when editing is done.387 data-ng-change="saveInterfaceLink(interface)"
387 <select class="table__input" name="ip-address" id="ip-address">388 data-ng-disabled="isLinkModeDisabled(interface)"
388 <option value="auto">Auto assign</option>389 data-ng-options="mode.mode as mode.text for mode in getLinkModes(interface)">
389 <option value="manual" selected>Manual</option>
390 <option value="DHCP">DHCP</option>
391 <option value="unconfigured">Unconfigured</option>
392 </select>390 </select>
393 -->
394 </li>391 </li>
395 <li class="margin-top--ten" data-ng-show="interface.ip_address">392 <li class="margin-top--ten" data-ng-show="shouldShowIPAddress(interface)">
396 {$ interface.ip_address $}393 <input type="text" class="table__input"
397 <!--394 data-ng-model="interface.ip_address"
398 TODO: Use when editing is done.395 data-ng-class="{ invalid: isIPAddressInvalid(interface) }"
399 <input type="text" class="table__input" value="127.0.0.1">396 data-maas-enter-blur
400 -->397 data-ng-focus="setFocusInterface(interface)"
398 data-ng-blur="clearFocusInterface(interface)"
399 data-ng-disabled="interface.mode != 'static'">
401 </li>400 </li>
402 </ul>401 </ul>
403 </div>402 </div>
404403
=== modified file 'src/maasserver/websockets/handlers/node.py'
--- src/maasserver/websockets/handlers/node.py 2015-09-30 22:40:08 +0000
+++ src/maasserver/websockets/handlers/node.py 2015-10-01 14:33:47 +0000
@@ -37,6 +37,7 @@
37from maasserver.models.nodeprobeddetails import get_single_probed_details37from maasserver.models.nodeprobeddetails import get_single_probed_details
38from maasserver.models.partition import Partition38from maasserver.models.partition import Partition
39from maasserver.models.physicalblockdevice import PhysicalBlockDevice39from maasserver.models.physicalblockdevice import PhysicalBlockDevice
40from maasserver.models.subnet import Subnet
40from maasserver.models.tag import Tag41from maasserver.models.tag import Tag
41from maasserver.node_action import compile_node_actions42from maasserver.node_action import compile_node_actions
42from maasserver.rpc import getClientFor43from maasserver.rpc import getClientFor
@@ -105,7 +106,8 @@
105 'set_active',106 'set_active',
106 'check_power',107 'check_power',
107 'update_interface',108 'update_interface',
108 'unmountFilesystem',109 'link_subnet',
110 'unmount_filesystem',
109 ]111 ]
110 form = AdminNodeWithMACAddressesForm112 form = AdminNodeWithMACAddressesForm
111 exclude = [113 exclude = [
@@ -348,7 +350,9 @@
348350
349 def dehydrate_interface(self, interface, obj):351 def dehydrate_interface(self, interface, obj):
350 """Dehydrate a `interface` into a interface definition."""352 """Dehydrate a `interface` into a interface definition."""
351 links = interface.get_links()353 # Sort the links by ID that way they show up in the same order in
354 # the UI.
355 links = sorted(interface.get_links(), key=itemgetter("id"))
352 for link in links:356 for link in links:
353 # Replace the subnet object with the subnet_id. The client will357 # Replace the subnet object with the subnet_id. The client will
354 # use this information to pull the subnet information from the358 # use this information to pull the subnet information from the
@@ -545,7 +549,7 @@
545 node_obj.save()549 node_obj.save()
546 return self.full_dehydrate(node_obj)550 return self.full_dehydrate(node_obj)
547551
548 def unmountFilesystem(self, params):552 def unmount_filesystem(self, params):
549 node = self.get_object(params)553 node = self.get_object(params)
550 if params.get('partition_id') is not None:554 if params.get('partition_id') is not None:
551 obj = Partition.objects.get(555 obj = Partition.objects.get(
@@ -601,6 +605,10 @@
601605
602 def update_interface(self, params):606 def update_interface(self, params):
603 """Update the interface."""607 """Update the interface."""
608 # Only admin users can perform update.
609 if not self.user.is_superuser:
610 raise HandlerPermissionError()
611
604 node = self.get_object(params)612 node = self.get_object(params)
605 interface = Interface.objects.get(node=node, id=params["interface_id"])613 interface = Interface.objects.get(node=node, id=params["interface_id"])
606 interface_form = InterfaceForm.get_interface_form(interface.type)614 interface_form = InterfaceForm.get_interface_form(interface.type)
@@ -610,6 +618,28 @@
610 else:618 else:
611 raise ValidationError(form.errors)619 raise ValidationError(form.errors)
612620
621 def link_subnet(self, params):
622 """Create or update the link."""
623 # Only admin users can perform update.
624 if not self.user.is_superuser:
625 raise HandlerPermissionError()
626
627 node = self.get_object(params)
628 interface = Interface.objects.get(node=node, id=params["interface_id"])
629 subnet = None
630 if "subnet" in params:
631 subnet = Subnet.objects.get(id=params["subnet"])
632 if "link_id" in params:
633 # We are updating an already existing link.
634 interface.update_link_by_id(
635 params["link_id"], params["mode"], subnet,
636 ip_address=params.get("ip_address", None))
637 else:
638 # We are creating a new link.
639 interface.link_subnet(
640 params["mode"], subnet,
641 ip_address=params.get("ip_address", None))
642
613 @asynchronous643 @asynchronous
614 @inlineCallbacks644 @inlineCallbacks
615 def check_power(self, params):645 def check_power(self, params):
616646
=== modified file 'src/maasserver/websockets/handlers/tests/test_node.py'
--- src/maasserver/websockets/handlers/tests/test_node.py 2015-10-01 13:29:51 +0000
+++ src/maasserver/websockets/handlers/tests/test_node.py 2015-10-01 14:33:47 +0000
@@ -25,6 +25,7 @@
25from lxml import etree25from lxml import etree
26from maasserver.enum import (26from maasserver.enum import (
27 FILESYSTEM_FORMAT_TYPE_CHOICES_DICT,27 FILESYSTEM_FORMAT_TYPE_CHOICES_DICT,
28 INTERFACE_LINK_TYPE,
28 INTERFACE_TYPE,29 INTERFACE_TYPE,
29 IPADDRESS_TYPE,30 IPADDRESS_TYPE,
30 NODE_STATUS,31 NODE_STATUS,
@@ -33,6 +34,7 @@
33from maasserver.forms import AdminNodeWithMACAddressesForm34from maasserver.forms import AdminNodeWithMACAddressesForm
34from maasserver.models import interface as interface_module35from maasserver.models import interface as interface_module
35from maasserver.models.config import Config36from maasserver.models.config import Config
37from maasserver.models.interface import Interface
36from maasserver.models.nodeprobeddetails import get_single_probed_details38from maasserver.models.nodeprobeddetails import get_single_probed_details
37from maasserver.node_action import compile_node_actions39from maasserver.node_action import compile_node_actions
38from maasserver.rpc.testing.fixtures import MockLiveRegionToClusterRPCFixture40from maasserver.rpc.testing.fixtures import MockLiveRegionToClusterRPCFixture
@@ -79,7 +81,10 @@
79 LIST_MODALIASES_OUTPUT_NAME,81 LIST_MODALIASES_OUTPUT_NAME,
80 LLDP_OUTPUT_NAME,82 LLDP_OUTPUT_NAME,
81)83)
82from mock import sentinel84from mock import (
85 ANY,
86 sentinel,
87)
83from netaddr import IPAddress88from netaddr import IPAddress
84from provisioningserver.power.poweraction import PowerActionFail89from provisioningserver.power.poweraction import PowerActionFail
85from provisioningserver.rpc.cluster import PowerQuery90from provisioningserver.rpc.cluster import PowerQuery
@@ -915,14 +920,14 @@
915 updated_node = handler.update(node_data)920 updated_node = handler.update(node_data)
916 self.assertItemsEqual([tag_name], updated_node["tags"])921 self.assertItemsEqual([tag_name], updated_node["tags"])
917922
918 def test_unmountFilesystem(self):923 def test_unmount_filesystem(self):
919 user = factory.make_admin()924 user = factory.make_admin()
920 handler = NodeHandler(user, {})925 handler = NodeHandler(user, {})
921 architecture = make_usable_architecture(self)926 architecture = make_usable_architecture(self)
922 node = factory.make_Node(interface=True, architecture=architecture)927 node = factory.make_Node(interface=True, architecture=architecture)
923 block_device = factory.make_PhysicalBlockDevice(node=node)928 block_device = factory.make_PhysicalBlockDevice(node=node)
924 factory.make_Filesystem(block_device=block_device)929 factory.make_Filesystem(block_device=block_device)
925 handler.unmountFilesystem({930 handler.unmount_filesystem({
926 'system_id': node.system_id,931 'system_id': node.system_id,
927 'block_id': block_device.id932 'block_id': block_device.id
928 })933 })
@@ -1006,7 +1011,7 @@
1006 node.distro_series, Equals(osystem["releases"][0]["name"]))1011 node.distro_series, Equals(osystem["releases"][0]["name"]))
10071012
1008 def test_update_interface(self):1013 def test_update_interface(self):
1009 user = factory.make_User()1014 user = factory.make_admin()
1010 node = factory.make_Node()1015 node = factory.make_Node()
1011 handler = NodeHandler(user, {})1016 handler = NodeHandler(user, {})
1012 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)1017 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
@@ -1023,7 +1028,7 @@
1023 self.assertEquals(new_vlan, interface.vlan)1028 self.assertEquals(new_vlan, interface.vlan)
10241029
1025 def test_update_interface_raises_ValidationError(self):1030 def test_update_interface_raises_ValidationError(self):
1026 user = factory.make_User()1031 user = factory.make_admin()
1027 node = factory.make_Node()1032 node = factory.make_Node()
1028 handler = NodeHandler(user, {})1033 handler = NodeHandler(user, {})
1029 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)1034 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
@@ -1036,6 +1041,50 @@
1036 "vlan": random.randint(1000, 5000),1041 "vlan": random.randint(1000, 5000),
1037 })1042 })
10381043
1044 def test_link_subnet_calls_update_link_by_id_if_link_id(self):
1045 user = factory.make_admin()
1046 node = factory.make_Node()
1047 handler = NodeHandler(user, {})
1048 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
1049 subnet = factory.make_Subnet()
1050 link_id = random.randint(0, 100)
1051 mode = factory.pick_enum(INTERFACE_LINK_TYPE)
1052 ip_address = factory.make_ip_address()
1053 self.patch_autospec(Interface, "update_link_by_id")
1054 handler.link_subnet({
1055 "system_id": node.system_id,
1056 "interface_id": interface.id,
1057 "link_id": link_id,
1058 "subnet": subnet.id,
1059 "mode": mode,
1060 "ip_address": ip_address,
1061 })
1062 self.assertThat(
1063 Interface.update_link_by_id,
1064 MockCalledOnceWith(
1065 ANY, link_id, mode, subnet, ip_address=ip_address))
1066
1067 def test_link_subnet_calls_link_subnet_if_not_link_id(self):
1068 user = factory.make_admin()
1069 node = factory.make_Node()
1070 handler = NodeHandler(user, {})
1071 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
1072 subnet = factory.make_Subnet()
1073 mode = factory.pick_enum(INTERFACE_LINK_TYPE)
1074 ip_address = factory.make_ip_address()
1075 self.patch_autospec(Interface, "link_subnet")
1076 handler.link_subnet({
1077 "system_id": node.system_id,
1078 "interface_id": interface.id,
1079 "subnet": subnet.id,
1080 "mode": mode,
1081 "ip_address": ip_address,
1082 })
1083 self.assertThat(
1084 Interface.link_subnet,
1085 MockCalledOnceWith(
1086 ANY, mode, subnet, ip_address=ip_address))
1087
10391088
1040class TestNodeHandlerCheckPower(MAASTransactionServerTestCase):1089class TestNodeHandlerCheckPower(MAASTransactionServerTestCase):
10411090
10421091
=== modified file 'src/maastesting/factory.py'
--- src/maastesting/factory.py 2015-09-24 16:22:12 +0000
+++ src/maastesting/factory.py 2015-10-01 14:33:47 +0000
@@ -276,15 +276,31 @@
276 slash = 128 - host_bits276 slash = 128 - host_bits
277 return self.make_ipv6_network(slash=slash)277 return self.make_ipv6_network(slash=slash)
278278
279 def pick_ip_in_dynamic_range(self, ngi):279 def pick_ip_in_dynamic_range(self, ngi, but_not=None):
280 if but_not is None:
281 but_not = []
280 first = ngi.get_dynamic_ip_range().first282 first = ngi.get_dynamic_ip_range().first
281 last = ngi.get_dynamic_ip_range().last283 last = ngi.get_dynamic_ip_range().last
282 return unicode(IPAddress(random.randrange(first, last)))284 but_not = [IPAddress(but) for but in but_not if but is not None]
285 for _ in range(100):
286 address = IPAddress(random.randint(first, last))
287 if address not in but_not:
288 return bytes(address)
289 raise TooManyRandomRetries(
290 "Could not find available IP in static range")
283291
284 def pick_ip_in_static_range(self, ngi):292 def pick_ip_in_static_range(self, ngi, but_not=None):
293 if but_not is None:
294 but_not = []
285 first = ngi.get_static_ip_range().first295 first = ngi.get_static_ip_range().first
286 last = ngi.get_static_ip_range().last296 last = ngi.get_static_ip_range().last
287 return unicode(IPAddress(random.randrange(first, last)))297 but_not = [IPAddress(but) for but in but_not if but is not None]
298 for _ in range(100):
299 address = IPAddress(random.randint(first, last))
300 if address not in but_not:
301 return bytes(address)
302 raise TooManyRandomRetries(
303 "Could not find available IP in static range")
288304
289 def pick_ip_in_network(self, network, but_not=None):305 def pick_ip_in_network(self, network, but_not=None):
290 if but_not is None:306 if but_not is None: