diff --git a/app/models.py b/app/models.py index d711617017f6d340cce83131ea406ee5d601308c..7e3732ca24408dfc1767714542f278e8082e20df 100644 --- a/app/models.py +++ b/app/models.py @@ -299,6 +299,38 @@ class Network(db.Model): def __str__(self): return str(self.prefix) + @property + def network_ip(self): + return ipaddress.ip_network(self.prefix) + + @property + def first_ip(self): + return ipaddress.ip_address(self.first) + + @property + def last_ip(self): + return ipaddress.ip_address(self.last) + + def ip_range(self): + """Return the list of IP addresses that can be assigned for this network + + The range is defined by the first and last IP + """ + return [addr for addr in self.network_ip.hosts() + if self.first_ip <= addr <= self.last_ip] + + def used_ips(self): + """Return the list of IP addresses in use + + The list is sorted + """ + return sorted(host.address for host in self.hosts) + + def available_ips(self): + """Return the list of IP addresses available""" + return [addr for addr in self.ip_range() + if addr not in self.used_ips()] + @staticmethod def ip_in_network(ip, prefix): """Ensure the IP is in the network @@ -356,6 +388,10 @@ class Host(db.Model): mac = db.relationship('Mac', backref='host') + @property + def address(self): + return ipaddress.ip_address(self.ip) + def __str__(self): return str(self.ip) diff --git a/tests/functional/test_models.py b/tests/functional/test_models.py new file mode 100644 index 0000000000000000000000000000000000000000..673c239bda95697de9e41656e16d48fe9bef2584 --- /dev/null +++ b/tests/functional/test_models.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +""" +tests.functional.test_models +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module defines models tests. + +:copyright: (c) 2017 European Spallation Source ERIC +:license: BSD 2-Clause, see LICENSE for more details. + +""" +import ipaddress +from app import models + + +def test_network_ip_properties(session): + # Create some networks + network1 = models.Network(prefix='172.16.1.0/24', first='172.16.1.10', last='172.16.1.250') + network2 = models.Network(prefix='172.16.20.0/26', first='172.16.20.11', last='172.16.20.14') + for network in (network1, network2): + session.add(network) + session.commit() + + assert network1.network_ip == ipaddress.ip_network('172.16.1.0/24') + assert network1.first_ip == ipaddress.ip_address('172.16.1.10') + assert network1.last_ip == ipaddress.ip_address('172.16.1.250') + assert len(network1.ip_range()) == 241 + assert network1.ip_range() == [ipaddress.ip_address(f'172.16.1.{i}') for i in range(10, 251)] + assert network1.ip_range() == network1.available_ips() + assert network1.used_ips() == [] + + assert network2.network_ip == ipaddress.ip_network('172.16.20.0/26') + assert network2.first_ip == ipaddress.ip_address('172.16.20.11') + assert network2.last_ip == ipaddress.ip_address('172.16.20.14') + assert len(network2.ip_range()) == 4 + assert network2.ip_range() == [ipaddress.ip_address(f'172.16.20.{i}') for i in range(11, 15)] + assert network2.ip_range() == network2.available_ips() + assert network2.used_ips() == [] + + +def test_network_available_and_used_ips(session): + # Create some networks and hosts + network1 = models.Network(prefix='172.16.1.0/24', first='172.16.1.10', last='172.16.1.250') + network2 = models.Network(prefix='172.16.20.0/26', first='172.16.20.11', last='172.16.20.14') + for network in (network1, network2): + session.add(network) + session.flush() + for i in range(10, 20): + session.add(models.Host(network_id=network1.id, ip=f'172.16.1.{i}')) + session.add(models.Host(network_id=network2.id, ip='172.16.20.13')) + session.commit() + # Check available and used IPs + assert network1.used_ips() == [ipaddress.ip_address(f'172.16.1.{i}') for i in range(10, 20)] + assert network1.available_ips() == [ipaddress.ip_address(f'172.16.1.{i}') for i in range(20, 251)] + assert network2.used_ips() == [ipaddress.ip_address('172.16.20.13')] + assert network2.available_ips() == [ipaddress.ip_address('172.16.20.11'), + ipaddress.ip_address('172.16.20.12'), + ipaddress.ip_address('172.16.20.14')] + + # Add more hosts + session.add(models.Host(network_id=network2.id, ip='172.16.20.11')) + session.add(models.Host(network_id=network2.id, ip='172.16.20.14')) + session.commit() + assert len(network2.used_ips()) == 3 + assert network2.used_ips() == [ipaddress.ip_address('172.16.20.11'), + ipaddress.ip_address('172.16.20.13'), + ipaddress.ip_address('172.16.20.14')] + assert network2.available_ips() == [ipaddress.ip_address('172.16.20.12')] + + # Add last available IP + session.add(models.Host(network_id=network2.id, ip='172.16.20.12')) + session.commit() + assert network2.used_ips() == [ipaddress.ip_address(f'172.16.20.{i}') for i in range(11, 15)] + assert list(network2.available_ips()) == []