From e7e568d7b6579382e2da9d213f727aa73ca97c2e Mon Sep 17 00:00:00 2001 From: Rajaram Mallya Date: Thu, 10 Nov 2011 15:41:15 +0530 Subject: [PATCH] interface has a list of allowed ips --- etc/melange/melange.conf.sample | 4 ++-- melange/db/sqlalchemy/api.py | 20 ++++++++++++++-- melange/db/sqlalchemy/mappers.py | 24 +++++++++++++++++++ .../migrate_repo/versions/001_base_schema.py | 16 +++++++++---- melange/ipam/models.py | 15 ++++++++---- melange/tests/factories/models.py | 8 +++---- melange/tests/unit/test_ipam_models.py | 23 ++++++++++++++++++ 7 files changed, 94 insertions(+), 16 deletions(-) diff --git a/etc/melange/melange.conf.sample b/etc/melange/melange.conf.sample index 05c77d1f..b0b8e9e6 100644 --- a/etc/melange/melange.conf.sample +++ b/etc/melange/melange.conf.sample @@ -38,8 +38,8 @@ default_cidr = 10.0.0.0/24 #ipv6_generator=melange.ipv6.tenant_based_generator.TenantBasedIpV6Generator #DNS info for a data_center -dns1 = "ns1.example.com" -dns2 = "ns2.example.com" +dns1 = 8.8.8.8 +dns2 = 8.8.4.4 #Number of days before deallocated IPs are deleted keep_deallocated_ips_for_days = 2 diff --git a/melange/db/sqlalchemy/api.py b/melange/db/sqlalchemy/api.py index c4ed0ec4..631e4289 100644 --- a/melange/db/sqlalchemy/api.py +++ b/melange/db/sqlalchemy/api.py @@ -19,6 +19,7 @@ import sqlalchemy.exc from sqlalchemy import and_ from sqlalchemy import or_ from sqlalchemy.orm import aliased +from sqlalchemy.orm import joinedload from melange import ipam from melange.common import exception @@ -71,7 +72,7 @@ def delete_all(query_func, model, **conditions): query_func(model, **conditions).delete() -def update(model, values): +def update(model, **values): for k, v in values.iteritems(): model[k] = v @@ -106,7 +107,7 @@ def save_nat_relationships(nat_relationships): for relationship in nat_relationships: ip_nat = mappers.IpNat() relationship['id'] = utils.generate_uuid() - update(ip_nat, relationship) + update(ip_nat, **relationship) save(ip_nat) @@ -203,6 +204,21 @@ def pop_allocatable_address(**conditions): return ip.address +def save_allowed_ip(interface_id, ip_address_id): + allowed_ip = mappers.AllowedIp() + update(allowed_ip, + id=utils.generate_uuid(), + interface_id=interface_id, + ip_address_id=ip_address_id) + save(allowed_ip) + + +def find_allowed_ips(**conditions): + query = _query_by(mappers.AllowedIp, **conditions).\ + options(joinedload('ip_address')) + return [allowed_ip.ip_address for allowed_ip in query] + + def configure_db(options): session.configure_db(options) diff --git a/melange/db/sqlalchemy/mappers.py b/melange/db/sqlalchemy/mappers.py index 5902977f..fce8bbf4 100644 --- a/melange/db/sqlalchemy/mappers.py +++ b/melange/db/sqlalchemy/mappers.py @@ -33,6 +33,7 @@ def map(engine, models): mac_address_ranges_table = Table('mac_address_ranges', meta, autoload=True) mac_addresses_table = Table('mac_addresses', meta, autoload=True) interfaces_table = Table('interfaces', meta, autoload=True) + allowed_ips_table = Table('allowed_ips', meta, autoload=True) orm.mapper(models["IpBlock"], Table('ip_blocks', meta, autoload=True)) orm.mapper(models["IpAddress"], ip_addresses_table) @@ -60,6 +61,13 @@ def map(engine, models): } ) + orm.mapper(AllowedIp, allowed_ips_table, + properties={ + 'interface': orm.relation(models["Interface"]), + 'ip_address': orm.relation(models["IpAddress"]) + } + ) + class IpNat(object): """Many to Many table for natting inside globals and locals. @@ -75,3 +83,19 @@ class IpNat(object): def __getitem__(self, key): return getattr(self, key) + + +class AllowedIp(object): + """Many to Many table for natting inside globals and locals. + + This resides in sqlalchemy mappers as its not a true model + and non-relational dbs may not expose many-to-many relationships as + another table + + """ + + def __setitem__(self, key, value): + setattr(self, key, value) + + def __getitem__(self, key): + return getattr(self, key) diff --git a/melange/db/sqlalchemy/migrate_repo/versions/001_base_schema.py b/melange/db/sqlalchemy/migrate_repo/versions/001_base_schema.py index 3791ae71..ce85bf48 100644 --- a/melange/db/sqlalchemy/migrate_repo/versions/001_base_schema.py +++ b/melange/db/sqlalchemy/migrate_repo/versions/001_base_schema.py @@ -142,16 +142,24 @@ interfaces = Table('interfaces', meta, Column('updated_at', DateTime()), UniqueConstraint('virtual_interface_id')) +allowed_ips = Table('allowed_ips', meta, + Column('id', String(36), primary_key=True, nullable=False), + Column('ip_address_id', String(36), ForeignKey('ip_addresses.id'), + nullable=False), + Column('interface_id', String(36), ForeignKey('interfaces.id'), + nullable=False), + UniqueConstraint('ip_address_id', 'interface_id')) + def upgrade(migrate_engine): meta.bind = migrate_engine create_tables([policies, ip_ranges, ip_octets, ip_blocks, ip_routes, mac_address_ranges, mac_addresses, interfaces, ip_addresses, - ip_nats, allocatable_ips]) + ip_nats, allocatable_ips, allowed_ips]) def downgrade(migrate_engine): meta.bind = migrate_engine - drop_tables([allocatable_ips, ip_nats, ip_addresses, interfaces, - mac_addresses, mac_address_ranges, ip_routes, ip_blocks, - ip_ranges, ip_octets, policies]) + drop_tables([allowed_ips, allocatable_ips, ip_nats, ip_addresses, + interfaces, mac_addresses, mac_address_ranges, ip_routes, + ip_blocks, ip_ranges, ip_octets, policies]) diff --git a/melange/ipam/models.py b/melange/ipam/models.py index 88de5dd8..d4597f6f 100644 --- a/melange/ipam/models.py +++ b/melange/ipam/models.py @@ -338,10 +338,11 @@ class IpBlock(ModelBase): mac_address=interface.mac_address_eui_format, **kwargs) try: - return IpAddress.create(address=address, - ip_block_id=self.id, - interface_id=interface.id) - + ip = IpAddress.create(address=address, + ip_block_id=self.id, + interface_id=interface.id) + interface.allow_ip(ip) + return ip except exception.DBConstraintError as error: LOG.debug("IP allocation retry count :{0}".format(retries + 1)) LOG.exception(error) @@ -796,6 +797,12 @@ class Interface(ModelBase): data['id'] = self.virtual_interface_id return data + def allow_ip(self, ip): + db_api.save_allowed_ip(self.id, ip.id) + + def ips_allowed(self): + return db_api.find_allowed_ips(interface_id=self.id) + @utils.cached_property def mac_address(self): return MacAddress.get_by(interface_id=self.id) diff --git a/melange/tests/factories/models.py b/melange/tests/factories/models.py index 517e2fa1..bffb7bb7 100644 --- a/melange/tests/factories/models.py +++ b/melange/tests/factories/models.py @@ -26,8 +26,8 @@ class IpBlockFactory(factory.Factory): FACTORY_FOR = models.IpBlock cidr = factory.Sequence(lambda n: "192.168.{0}.0/24".format(int(n) % 255)) type = "private" - dns1 = "ns1.example.com" - dns2 = "ns2.example.com" + dns1 = "8.8.8.8" + dns2 = "8.8.4.4" tenant_id = "tenant_id" @@ -40,8 +40,8 @@ class PrivateIpBlockFactory(IpBlockFactory): class IpV6IpBlockFactory(IpBlockFactory): - cidr = factory.Sequence(lambda n: "fe::{0}00/120".format(hex(int(n) % 16))) - type = "public" + cidr = factory.Sequence(lambda n: "fe::{0}/120".format(hex(int(n))[2:])) + type = "private" class IpAddressFactory(factory.Factory): diff --git a/melange/tests/unit/test_ipam_models.py b/melange/tests/unit/test_ipam_models.py index 8c79fff4..061d0aee 100644 --- a/melange/tests/unit/test_ipam_models.py +++ b/melange/tests/unit/test_ipam_models.py @@ -2124,6 +2124,29 @@ class TestInterface(tests.BaseTest): self.assertModelsEqual(interface.ip_addresses, [ip1, ip2]) +class TestAllowedIps(tests.BaseTest): + + def test_allow_an_ip_on_an_interface(self): + interface = factory_models.InterfaceFactory() + ip1 = factory_models.IpAddressFactory(interface_id=interface.id) + ip2 = factory_models.IpAddressFactory(interface_id=interface.id) + noise_ip = factory_models.IpAddressFactory(interface_id=interface.id) + + interface.allow_ip(ip1) + interface.allow_ip(ip2) + + actual_allowed_ips = interface.ips_allowed() + self.assertModelsEqual(actual_allowed_ips, [ip1, ip2]) + + def test_allocating_ips_allows_the_ip_on_the_interface(self): + interface = factory_models.InterfaceFactory() + block = factory_models.IpBlockFactory() + + ip = block.allocate_ip(interface=interface) + + self.assertEqual(interface.ips_allowed(), [ip]) + + def _allocate_ip(block, interface=None, **kwargs): if interface is None: interface = factory_models.InterfaceFactory()