diff --git a/etc/melange/melange.conf.sample b/etc/melange/melange.conf.sample index 90741c21..132c2451 100644 --- a/etc/melange/melange.conf.sample +++ b/etc/melange/melange.conf.sample @@ -15,7 +15,7 @@ bind_port = 9898 # registry server. Any valid SQLAlchemy connection string is fine. # See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine sql_connection = sqlite:///melange_test.sqlite -#sql_connection = mysql://root:root@localhost/melange +# sql_connection = mysql://root:root@localhost/melange #sql_connection = postgresql://melange:melange@localhost/melange # Period in seconds after which SQLAlchemy should reestablish its connection diff --git a/melange/ipam/models.py b/melange/ipam/models.py index 8f2fd9d8..298bd877 100644 --- a/melange/ipam/models.py +++ b/melange/ipam/models.py @@ -20,6 +20,7 @@ import datetime import logging import netaddr +import operator from melange import db from melange import ipv6 @@ -688,7 +689,8 @@ class MacAddressRange(ModelBase): @classmethod def allocate_next_free_mac(cls, **kwargs): - ranges = sorted(cls.find_all(), key=lambda model: model.created_at) + ranges = sorted(cls.find_all(), + key=operator.attrgetter('created_at', 'id')) for range in ranges: try: return range.allocate_mac(**kwargs) @@ -956,9 +958,12 @@ class Network(ModelBase): @classmethod def find_by(cls, id, **conditions): ip_blocks = IpBlock.find_all(network_id=id, **conditions).all() - if len(ip_blocks) == 0: + sorted_blocks = sorted(ip_blocks, + key=operator.attrgetter('created_at', 'id')) + + if len(sorted_blocks) == 0: raise ModelNotFoundError(_("Network %s not found") % id) - return cls(id=id, ip_blocks=ip_blocks) + return cls(id=id, ip_blocks=sorted_blocks) @classmethod def find_or_create_by(cls, id, tenant_id): diff --git a/melange/tests/__init__.py b/melange/tests/__init__.py index 3256727b..9f9a7d67 100644 --- a/melange/tests/__init__.py +++ b/melange/tests/__init__.py @@ -20,6 +20,7 @@ import unittest import urlparse from melange.db import db_api +from melange.common import utils class BaseTest(unittest.TestCase): @@ -82,3 +83,7 @@ class BaseTest(unittest.TestCase): def assertErrorResponse(self, response, error_type, expected_error): self.assertEqual(response.status_int, error_type().code) self.assertIn(expected_error, response.body) + + def setup_uuid_with(self, fake_uuid): + self.mock.StubOutWithMock(utils, "generate_uuid") + utils.generate_uuid().MultipleTimes().AndReturn(fake_uuid) diff --git a/melange/tests/unit/test_ipam_models.py b/melange/tests/unit/test_ipam_models.py index fef315fd..92fc83f2 100644 --- a/melange/tests/unit/test_ipam_models.py +++ b/melange/tests/unit/test_ipam_models.py @@ -1025,7 +1025,7 @@ class TestIpBlock(tests.BaseTest): self.assertModelsEqual(block1.ip_routes(), ip_routes) def test_ip_block_creation_is_notified(self): - _setup_uuid(self.mock, "ip_block_uuid") + self.setup_uuid_with("ip_block_uuid") creation_time = datetime.datetime(2050, 1, 1) mock_notifier = _setup_notifier(self.mock) mock_notifier.info("create IpBlock", dict(tenant_id="tnt_id", @@ -1348,7 +1348,7 @@ class TestIpAddress(tests.BaseTest): def test_ip_addresss_creation_is_notified(self): block = factory_models.IpBlockFactory(cidr="10.1.1.1/24") - _setup_uuid(self.mock, "ip_address_uuid") + self.setup_uuid_with("ip_address_uuid") creation_time = datetime.datetime(2050, 1, 1) mock_notifier = _setup_notifier(self.mock) mock_notifier.info("create IpAddress", dict(used_by_tenant_id="tnt_id", @@ -1486,6 +1486,7 @@ class TestMacAddressRange(tests.BaseTest): two_days_before = datetime.datetime.now() - datetime.timedelta(days=2) with unit.StubTime(time=two_days_before): factory_models.MacAddressRangeFactory(cidr="BC:76:4E:20:00:00/47") + factory_models.MacAddressRangeFactory(cidr="BC:76:4E:30:00:00/40") mac1 = models.MacAddressRange.allocate_next_free_mac() @@ -1502,6 +1503,36 @@ class TestMacAddressRange(tests.BaseTest): self.assertEqual(netaddr.EUI(mac4.address), netaddr.EUI("BC:76:4E:30:00:01")) + def test_allocate_next_free_mac_chooses_range_by_created_date_and_id(self): + self.mock.StubOutWithMock(utils, "generate_uuid") + + def setup_range(cidr, uuid): + utils.generate_uuid().MultipleTimes().AndReturn(uuid) + self.mock.ReplayAll() + rng = factory_models.MacAddressRangeFactory(cidr=cidr) + self.mock.ResetAll() + return rng + today = datetime.datetime.now() + two_days_before = today - datetime.timedelta(days=2) + with unit.StubTime(time=two_days_before): + setup_range("BC:00:4E:20:00:00/47", 0) + + three_days_before = today - datetime.timedelta(days=3) + with unit.StubTime(time=three_days_before): + rng_of_uuid_5 = setup_range("AC:76:4E:20:00:00/48", 5) + rng_of_uuid_2 = setup_range("BC:76:4E:20:00:00/48", 2) + rng_of_uuid_3 = setup_range("CC:76:4E:20:00:00/48", 3) + rng_of_uuid_1 = setup_range("DC:76:4E:20:00:00/48", 1) + rng_of_uuid_4 = setup_range("EC:76:4E:20:00:00/48", 4) + self.mock.UnsetStubs() + + allocate_mac = models.MacAddressRange.allocate_next_free_mac + self.assertTrue(rng_of_uuid_1.contains(allocate_mac().address)) + self.assertTrue(rng_of_uuid_2.contains(allocate_mac().address)) + self.assertTrue(rng_of_uuid_3.contains(allocate_mac().address)) + self.assertTrue(rng_of_uuid_4.contains(allocate_mac().address)) + self.assertTrue(rng_of_uuid_5.contains(allocate_mac().address)) + def test_allocate_next_free_mac_raises_error_when_no_more_free_macs(self): factory_models.MacAddressRangeFactory(cidr="BC:76:4E:20:0:0/48") factory_models.MacAddressRangeFactory(cidr="BC:76:4E:30:0:0/48") @@ -1980,6 +2011,41 @@ class TestNetwork(tests.BaseTest): ip_address = models.IpAddress.find_by(ip_block_id=free_ip_block.id) self.assertEqual(allocated_ipv4, ip_address) + def test_picks_block_to_allocate_sorted_by_created_date_and_id(self): + interface = factory_models.InterfaceFactory(tenant_id="tenant_id") + self.mock.StubOutWithMock(utils, "generate_uuid") + + def setup_block(cidr, uuid): + utils.generate_uuid().MultipleTimes().AndReturn(uuid) + self.mock.ReplayAll() + block = factory_models.IpBlockFactory(cidr=cidr, + network_id="net", + tenant_id="tenant_id") + self.mock.ResetAll() + return block + today = datetime.datetime.now() + two_days_before = today - datetime.timedelta(days=2) + with unit.StubTime(time=two_days_before): + setup_block("10.1.1.1/31", 0) + + three_days_before = today - datetime.timedelta(days=3) + with unit.StubTime(time=three_days_before): + block_of_uuid_5 = setup_block("20.1.1.1/31", 5) + block_of_uuid_2 = setup_block("30.1.1.1/31", 2) + block_of_uuid_3 = setup_block("40.1.1.1/31", 3) + block_of_uuid_1 = setup_block("50.1.1.1/31", 1) + block_of_uuid_4 = setup_block("60.1.1.1/31", 4) + self.mock.UnsetStubs() + + network = models.Network.find_by("net", tenant_id="tenant_id") + allocate_ip = lambda interface: network.allocate_ips( + interface=interface)[0].address + self.assertTrue(block_of_uuid_1.contains(allocate_ip(interface))) + self.assertTrue(block_of_uuid_2.contains(allocate_ip(interface))) + self.assertTrue(block_of_uuid_3.contains(allocate_ip(interface))) + self.assertTrue(block_of_uuid_4.contains(allocate_ip(interface))) + self.assertTrue(block_of_uuid_5.contains(allocate_ip(interface))) + def test_allocate_ip_raises_error_when_all_ip_blocks_are_full(self): interface = factory_models.InterfaceFactory() full_ip_block = factory_models.PublicIpBlockFactory(network_id="1", @@ -2053,7 +2119,7 @@ class TestNetwork(tests.BaseTest): self.assertTrue(models.IpAddress.get(ip1.id).marked_for_deallocation) self.assertTrue(models.IpAddress.get(ip2.id).marked_for_deallocation) - def test_retrives_allocated_ips(self): + def test_retrieves_allocated_ips(self): ip_block1 = factory_models.IpBlockFactory(network_id="1", cidr="10.0.0.0/24") ip_block2 = factory_models.IpBlockFactory(network_id="1", @@ -2458,8 +2524,3 @@ def _allocate_ip(block, interface=None, **kwargs): def _setup_notifier(mock): mock.StubOutClassWithMocks(notifier, "NoopNotifier") return notifier.NoopNotifier() - - -def _setup_uuid(mock, uuid): - mock.StubOutWithMock(utils, "generate_uuid") - utils.generate_uuid().MultipleTimes().AndReturn(uuid) diff --git a/melange/tests/unit/test_notifier.py b/melange/tests/unit/test_notifier.py index 3cf81966..0e20d94d 100644 --- a/melange/tests/unit/test_notifier.py +++ b/melange/tests/unit/test_notifier.py @@ -34,7 +34,7 @@ class NotifierTestBase(): def _setup_expected_message(self, priority, event, message): - self._setup_uuid_with("test_uuid") + self.setup_uuid_with("test_uuid") return { 'event_type': event, 'timestamp': str(utils.utcnow()), @@ -44,10 +44,6 @@ class NotifierTestBase(): 'publisher_id': socket.gethostname(), } - def _setup_uuid_with(self, fake_uuid): - self.mock.StubOutWithMock(utils, "generate_uuid") - utils.generate_uuid().AndReturn(fake_uuid) - class TestLoggingNotifier(tests.BaseTest, NotifierTestBase):