create new cidr type for data storage

it turns out that the 149 migration was overly agressive in its
IPAddress conversion, as we actually have a few columns that are
really CIDR values. This means that 39 chars isn't enough space to
store even a normalized IPv6 cidr in the worst case (you need 4
more to support /127).

We must also normalize IPv6 address cidrs otherwise 43 chars isn't
long enough. This was more of a problem in theory, than in practice,
as real IPv6 addresses rarely are non compressible.

This adds a migration to bump up the CIDR columns to 43 characters.
There is an infinitessimal chance that someone using mysql and long
IPv6 values might loose data in 149 because of truncation. This
doesn't address that, which would require modifying 149.

The native pg CIDR column type is not used because it would require
additional scrubbing of the data as CIDR is invalid if any host bits
are set (and it will fail on type conversion).

Fixes bug #1127696

Change-Id: I54539ac9c257d726bc4db5943169b5284cc847d3
This commit is contained in:
Sean Dague
2013-02-18 15:50:36 -05:00
committed by Morgan Fainberg
parent 50ff5b823b
commit ac08ce5344
2 changed files with 78 additions and 12 deletions

View File

@@ -507,28 +507,52 @@ class TestMigrations(BaseMigrationTestCase):
# migration 149, changes IPAddr storage format
def _prerun_149(self, engine):
provider_fw_rules = get_table(engine, 'provider_fw_rules')
data = [
console_pools = get_table(engine, 'console_pools')
data = {
'provider_fw_rules':
[
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "127.0.0.1"},
'to_port': 1234, 'cidr': "127.0.0.1/30"},
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "255.255.255.255"},
'to_port': 1234, 'cidr': "128.128.128.128/16"},
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "2001:db8::1:2"},
'to_port': 1234, 'cidr': "2001:db8::1:2/48"},
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "::1"}
'to_port': 1234, 'cidr': "::1/64"}
],
'console_pools':
[
{'address': '10.10.10.10'},
{'address': '128.100.100.100'},
{'address': '2002:2002:2002:2002:2002:2002:2002:2002'},
{'address': '::1'},
]
engine.execute(provider_fw_rules.insert(), data)
}
engine.execute(provider_fw_rules.insert(), data['provider_fw_rules'])
for pool in data['console_pools']:
engine.execute(console_pools.insert(), pool)
return data
def _check_149(self, engine, data):
provider_fw_rules = get_table(engine, 'provider_fw_rules')
result = provider_fw_rules.select().execute()
iplist = map(lambda x: x['cidr'], data)
iplist = map(lambda x: x['cidr'], data['provider_fw_rules'])
for row in result:
self.assertIn(row['cidr'], iplist)
console_pools = get_table(engine, 'console_pools')
result = console_pools.select().execute()
iplist = map(lambda x: x['address'], data['console_pools'])
for row in result:
self.assertIn(row['address'], iplist)
# migration 151 - changes period_beginning and period_ending to DateTime
def _prerun_151(self, engine):
task_log = get_table(engine, 'task_log')
@@ -703,3 +727,32 @@ class TestMigrations(BaseMigrationTestCase):
# override __eq__, but if we stringify them then they do.
self.assertEqual(str(base_column.type),
str(shadow_column.type))
# migration 156 - introduce CIDR type
def _prerun_156(self, engine):
# assume the same data as from 149
data = {
'provider_fw_rules':
[
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "127.0.0.1/30"},
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "128.128.128.128/16"},
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "2001:db8::1:2/48"},
{'protocol': 'tcp', 'from_port': 1234,
'to_port': 1234, 'cidr': "::1/64"}
],
'console_pools':
[
{'address': '10.10.10.10'},
{'address': '128.100.100.100'},
{'address': '2002:2002:2002:2002:2002:2002:2002:2002'},
{'address': '::1'},
]
}
return data
def _check_156(self, engine, data):
# recheck the 149 data
self._check_149(engine, data)

View File

@@ -896,11 +896,24 @@ def is_valid_ipv6(address):
return netaddr.valid_ipv6(address)
def is_valid_ipv6_cidr(address):
try:
str(netaddr.IPNetwork(address, version=6).cidr)
return True
except Exception:
return False
def get_shortened_ipv6(address):
addr = netaddr.IPAddress(address, version=6)
return str(addr.ipv6())
def get_shortened_ipv6_cidr(address):
net = netaddr.IPNetwork(address, version=6)
return str(net.cidr)
def is_valid_cidr(address):
"""Check if the provided ipv4 or ipv6 address is a valid
CIDR address or not"""