summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrey Morris <trey.morris@rackspace.com>2011-12-01 16:54:40 -0600
committerTrey Morris <treyemorris@gmail.com>2011-12-08 17:33:04 -0600
commitd8e84937c19169e3de73e7ad6b7382d954d753ac (patch)
treeab50a17c95db9378666350c0657bff1e8d2b9490
parent10c829ff39500d68f1e3493f5fa1a4e2f5bc26d6 (diff)
Adds network model and network info cache.
The next merge will prepopulate the cache, and use the model to keep the cache up to date. I realize "cache" is a bit of a stretch for what this is doing. blueprint network-info-model blueprint compute-network-info Change-Id: I0f0f4ba3de1310e1ff89239dab6ea8e24c85f2c8
-rw-r--r--nova/db/api.py42
-rw-r--r--nova/db/sqlalchemy/api.py71
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/062_add_instance_info_cache_table.py62
-rw-r--r--nova/db/sqlalchemy/models.py21
-rw-r--r--nova/exception.py4
-rw-r--r--nova/network/model.py255
-rw-r--r--nova/tests/fake_network_cache_model.py72
-rw-r--r--nova/tests/test_network_info.py328
8 files changed, 855 insertions, 0 deletions
diff --git a/nova/db/api.py b/nova/db/api.py
index 4af79b13c..bc413319e 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -649,6 +649,48 @@ def instance_get_id_to_uuid_mapping(context, ids):
###################
+def instance_info_cache_create(context, values):
+ """Create a new instance cache record in the table.
+
+ :param context: = request context object
+ :param values: = dict containing column values
+ """
+ return IMPL.instance_info_cache_create(context, values)
+
+
+def instance_info_cache_get(context, instance_id, session=None):
+ """Gets an instance info cache from the table.
+
+ :param instance_id: = id of the info cache's instance
+ :param session: = optional session object
+ """
+ return IMPL.instance_info_cache_get(context, instance_id, session=None)
+
+
+def instance_info_cache_update(context, instance_id, values,
+ session=None):
+ """Update an instance info cache record in the table.
+
+ :param instance_id: = id of info cache's instance
+ :param values: = dict containing column values to update
+ """
+ return IMPL.instance_info_cache_update(context, instance_id, values,
+ session)
+
+
+def instance_info_cache_delete_by_instance_id(context, instance_id,
+ session=None):
+ """Deletes an existing instance_info_cache record
+
+ :param instance_id: = id of the instance tied to the cache record
+ """
+ return IMPL.instance_info_cache_delete_by_instance_id(context, instance_id,
+ session)
+
+
+###################
+
+
def key_pair_create(context, values):
"""Create a key_pair from the values dictionary."""
return IMPL.key_pair_create(context, values)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index a8424686f..2619c247f 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -1132,6 +1132,8 @@ def instance_destroy(context, instance_id):
update({'deleted': True,
'deleted_at': utils.utcnow(),
'updated_at': literal_column('updated_at')})
+ instance_info_cache_delete_by_instance_id(context, instance_id,
+ session=session)
@require_context
@@ -1558,6 +1560,75 @@ def instance_get_id_to_uuid_mapping(context, ids):
@require_context
+def instance_info_cache_create(context, values):
+ """Create a new instance cache record in the table.
+
+ :param context: = request context object
+ :param values: = dict containing column values
+ """
+ info_cache = models.InstanceInfoCache()
+ info_cache['id'] = str(utils.gen_uuid())
+ info_cache.update(values)
+
+ session = get_session()
+ with session.begin():
+ info_cache.save(session=session)
+ return info_cache
+
+
+@require_context
+def instance_info_cache_get(context, instance_id, session=None):
+ """Gets an instance info cache from the table.
+
+ :param instance_id: = id of the info cache's instance
+ :param session: = optional session object
+ """
+ session = session or get_session()
+
+ info_cache = session.query(models.InstanceInfoCache).\
+ filter_by(instance_id=instance_id).\
+ first()
+ return info_cache
+
+
+@require_context
+def instance_info_cache_update(context, instance_id, values,
+ session=None):
+ """Update an instance info cache record in the table.
+
+ :param instance_id: = id of info cache's instance
+ :param values: = dict containing column values to update
+ :param session: = optional session object
+ """
+ session = session or get_session()
+ info_cache = instance_info_cache_get(context, instance_id,
+ session=session)
+
+ values['updated_at'] = literal_column('updated_at')
+
+ if info_cache:
+ info_cache.update(values)
+ info_cache.save(session=session)
+ return info_cache
+
+
+@require_context
+def instance_info_cache_delete_by_instance_id(context, instance_id,
+ session=None):
+ """Deletes an existing instance_info_cache record
+
+ :param instance_id: = id of the instance tied to the cache record
+ :param session: = optional session object
+ """
+ values = {'deleted': True,
+ 'deleted_at': utils.utcnow()}
+ instance_info_cache_update(context, instance_id, values, session)
+
+
+###################
+
+
+@require_context
def key_pair_create(context, values):
key_pair_ref = models.KeyPair()
key_pair_ref.update(values)
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/062_add_instance_info_cache_table.py b/nova/db/sqlalchemy/migrate_repo/versions/062_add_instance_info_cache_table.py
new file mode 100644
index 000000000..99479bbf9
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/062_add_instance_info_cache_table.py
@@ -0,0 +1,62 @@
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from sqlalchemy import *
+from migrate import *
+
+from nova import log as logging
+from nova import utils
+
+meta = MetaData()
+
+# instance info cache table to add to DB
+instance_info_caches = Table('instance_info_caches', meta,
+ Column('created_at', DateTime(timezone=False),
+ default=utils.utcnow()),
+ Column('updated_at', DateTime(timezone=False),
+ onupdate=utils.utcnow()),
+ Column('deleted_at', DateTime(timezone=False)),
+ Column('deleted', Boolean(create_constraint=True, name=None)),
+ Column('id', Integer(), primary_key=True),
+ Column('network_info', Text()),
+ Column('instance_id', String(36),
+ ForeignKey('instances.uuid'),
+ nullable=False,
+ unique=True),
+ mysql_engine='InnoDB')
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ # load instances for fk
+ instances = Table('instances', meta, autoload=True)
+
+ # create instance_info_caches table
+ try:
+ instance_info_caches.create()
+ except Exception:
+ logging.error(_("Table |%s| not created!"), repr(instance_info_caches))
+ raise
+
+
+def downgrade(migrate_engine):
+ try:
+ instance_info_caches.drop()
+ except Exception:
+ logging.error(_("instance_info_caches tables not dropped"))
+ raise
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 407c9e2e7..1774e9ea8 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -257,6 +257,27 @@ class Instance(BASE, NovaBase):
progress = Column(Integer)
+class InstanceInfoCache(BASE, NovaBase):
+ """
+ Represents a cache of information about an instance
+ """
+ __tablename__ = 'instance_info_caches'
+ id = Column(String(36), primary_key=True)
+
+ # text column used for storing a json object of network data for api
+ network_info = Column(Text)
+
+ # this is all uuid based, we have them might as well start using them
+ instance_id = Column(String(36), ForeignKey('instances.uuid'),
+ nullable=False, unique=True)
+ instance = relationship(Instance,
+ backref=backref('info_cache', uselist=False),
+ foreign_keys=instance_id,
+ primaryjoin='and_('
+ 'InstanceInfoCache.instance_id == Instance.uuid,'
+ 'InstanceInfoCache.deleted == False)')
+
+
class VirtualStorageArray(BASE, NovaBase):
"""
Represents a virtual storage array supplying block storage to instances.
diff --git a/nova/exception.py b/nova/exception.py
index 8da1d752c..565e55387 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -317,6 +317,10 @@ class InvalidCPUInfo(Invalid):
message = _("Unacceptable CPU info") + ": %(reason)s"
+class InvalidIpAddressError(Invalid):
+ message = _("%(address)s is not a valid IP v4/6 address.")
+
+
class InvalidVLANTag(Invalid):
message = _("VLAN tag is not appropriate for the port group "
"%(bridge)s. Expected VLAN tag is %(tag)s, "
diff --git a/nova/network/model.py b/nova/network/model.py
new file mode 100644
index 000000000..92094f23b
--- /dev/null
+++ b/nova/network/model.py
@@ -0,0 +1,255 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import netaddr
+import types
+
+from nova import exception
+
+
+class Model(dict):
+ """Defines some necessary structures for most of the network models"""
+ def __repr__(self):
+ return self.__class__.__name__ + '(' + dict.__repr__(self) + ')'
+
+ def set_meta(self, kwargs):
+ # pull meta out of kwargs if it's there
+ self['meta'] = kwargs.pop('meta', {})
+ # update meta with any additional kwargs that may exist
+ self['meta'].update(kwargs)
+
+
+class IP(Model):
+ """Represents an IP address in Nova"""
+ def __init__(self, address=None, type=None, **kwargs):
+ super(IP, self).__init__()
+
+ self['address'] = address
+ self['type'] = type
+ self['version'] = kwargs.pop('version', None)
+
+ self.set_meta(kwargs)
+
+ # determine version from address if not passed in
+ if self['address'] and not self['version']:
+ try:
+ self['version'] = netaddr.IPAddress(self['address']).version
+ except netaddr.AddrFormatError, e:
+ raise exception.InvalidIpAddressError(self['address'])
+
+ def __eq__(self, other):
+ return self['address'] == other['address']
+
+ @classmethod
+ def hydrate(cls, ip):
+ if ip:
+ return IP(**ip)
+ return None
+
+
+class FixedIP(IP):
+ """Represents a Fixed IP address in Nova"""
+ def __init__(self, floating_ips=None, **kwargs):
+ super(FixedIP, self).__init__(**kwargs)
+ self['floating_ips'] = floating_ips or []
+
+ if not self['type']:
+ self['type'] = 'fixed'
+
+ def add_floating_ip(self, floating_ip):
+ if floating_ip not in self['floating_ips']:
+ self['floating_ips'].append(floating_ip)
+
+ def floating_ip_addresses(self):
+ return [ip['address'] for ip in self['floating_ips']]
+
+ @classmethod
+ def hydrate(cls, fixed_ip):
+ fixed_ip = FixedIP(**fixed_ip)
+ fixed_ip['floating_ips'] = [IP.hydrate(floating_ip)
+ for floating_ip in fixed_ip['floating_ips']]
+ return fixed_ip
+
+
+class Route(Model):
+ """Represents an IP Route in Nova"""
+ def __init__(self, cidr=None, gateway=None, interface=None, **kwargs):
+ super(Route, self).__init__()
+
+ self['cidr'] = cidr
+ self['gateway'] = gateway
+ self['interface'] = interface
+
+ self.set_meta(kwargs)
+
+ @classmethod
+ def hydrate(cls, route):
+ route = Route(**route)
+ route['gateway'] = IP.hydrate(route['gateway'])
+ return route
+
+
+class Subnet(Model):
+ """Represents a Subnet in Nova"""
+ def __init__(self, cidr=None, dns=None, gateway=None, ips=None,
+ routes=None, **kwargs):
+ super(Subnet, self).__init__()
+
+ self['cidr'] = cidr
+ self['dns'] = dns or []
+ self['gateway'] = gateway
+ self['ips'] = ips or []
+ self['routes'] = routes or []
+ self['version'] = kwargs.pop('version', None)
+
+ self.set_meta(kwargs)
+
+ if self['cidr'] and not self['version']:
+ self['version'] = netaddr.IPNetwork(self['cidr']).version
+
+ def __eq__(self, other):
+ return self['cidr'] == other['cidr']
+
+ def add_route(self, new_route):
+ if new_route not in self['routes']:
+ self['routes'].append(new_route)
+
+ def add_dns(self, dns):
+ if dns not in self['dns']:
+ self['dns'].append(dns)
+
+ def add_ip(self, ip):
+ if ip not in self['ips']:
+ self['ips'].append(ip)
+
+ @classmethod
+ def hydrate(cls, subnet):
+ subnet = Subnet(**subnet)
+ subnet['dns'] = [IP.hydrate(dns) for dns in subnet['dns']]
+ subnet['ips'] = [FixedIP.hydrate(ip) for ip in subnet['ips']]
+ subnet['routes'] = [Route.hydrate(route) for route in subnet['routes']]
+ subnet['gateway'] = IP.hydrate(subnet['gateway'])
+ return subnet
+
+
+class Network(Model):
+ """Represents a Network in Nova"""
+ def __init__(self, id=None, bridge=None, label=None,
+ subnets=None, **kwargs):
+ super(Network, self).__init__()
+
+ self['id'] = id
+ self['bridge'] = bridge
+ self['label'] = label
+ self['subnets'] = subnets or []
+
+ self.set_meta(kwargs)
+
+ def add_subnet(self, subnet):
+ if subnet not in self['subnets']:
+ self['subnets'].append(subnet)
+
+ @classmethod
+ def hydrate(cls, network):
+ if network:
+ network = Network(**network)
+ network['subnets'] = [Subnet.hydrate(subnet)
+ for subnet in network['subnets']]
+ return network
+
+
+class VIF(Model):
+ """Represents a Virtual Interface in Nova"""
+ def __init__(self, id=None, address=None, network=None, **kwargs):
+ super(VIF, self).__init__()
+
+ self['id'] = id
+ self['address'] = address
+ self['network'] = network or None
+
+ self.set_meta(kwargs)
+
+ def __eq__(self, other):
+ return self['id'] == other['id']
+
+ def fixed_ips(self):
+ return [fixed_ip for subnet in self['network']['subnets']
+ for fixed_ip in subnet['ips']]
+
+ def floating_ips(self):
+ return [floating_ip for fixed_ip in self.fixed_ips()
+ for floating_ip in fixed_ip['floating_ips']]
+
+ def labeled_ips(self):
+ """ returns the list of all IPs in this flat structure:
+ {'network_label': 'my_network',
+ 'network_id': 'n8v29837fn234782f08fjxk3ofhb84',
+ 'ips': [{'address': '123.123.123.123',
+ 'version': 4,
+ 'type: 'fixed',
+ 'meta': {...}},
+ {'address': '124.124.124.124',
+ 'version': 4,
+ 'type': 'floating',
+ 'meta': {...}},
+ {'address': 'fe80::4',
+ 'version': 6,
+ 'type': 'fixed',
+ 'meta': {...}}]"""
+ if self['network']:
+ # remove unecessary fields on fixed_ips
+ ips = [IP(**ip) for ip in self.fixed_ips()]
+ for ip in ips:
+ # remove floating ips from IP, since this is a flat structure
+ # of all IPs
+ del ip['meta']['floating_ips']
+ # add floating ips to list (if any)
+ ips.extend(self.floating_ips())
+ return {'network_label': self['network']['label'],
+ 'network_id': self['network']['id'],
+ 'ips': ips}
+ return []
+
+ @classmethod
+ def hydrate(cls, vif):
+ vif = VIF(**vif)
+ vif['network'] = Network.hydrate(vif['network'])
+ return vif
+
+
+class NetworkInfo(list):
+ """Stores and manipulates network information for a Nova instance"""
+
+ # NetworkInfo is a list of VIFs
+
+ def fixed_ips(self):
+ """Returns all fixed_ips without floating_ips attached"""
+ return [ip for vif in self for ip in vif.fixed_ips()]
+
+ def floating_ips(self):
+ """Returns all floating_ips"""
+ return [ip for vif in self for ip in vif.floating_ips()]
+
+ @classmethod
+ def hydrate(cls, network_info):
+ if isinstance(network_info, types.StringTypes):
+ network_info = json.loads(network_info)
+ return NetworkInfo([VIF.hydrate(vif) for vif in network_info])
+
+ def as_cache(self):
+ return json.dumps(self)
diff --git a/nova/tests/fake_network_cache_model.py b/nova/tests/fake_network_cache_model.py
new file mode 100644
index 000000000..c85b1b025
--- /dev/null
+++ b/nova/tests/fake_network_cache_model.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from nova.network import model
+
+
+def new_ip(ip_dict=None):
+ new_ip = dict(address='192.168.1.100')
+ ip_dict = ip_dict or {}
+ new_ip.update(ip_dict)
+ return model.FixedIP(**new_ip)
+
+
+def new_route(route_dict=None):
+ new_route = dict(
+ cidr='0.0.0.0/24',
+ gateway=new_ip(dict(address='192.168.1.1')),
+ interface='eth0')
+
+ route_dict = route_dict or {}
+ new_route.update(route_dict)
+ return model.Route(**new_route)
+
+
+def new_subnet(subnet_dict=None):
+ new_subnet = dict(
+ cidr='255.255.255.0',
+ dns=[new_ip(dict(address='1.2.3.4')),
+ new_ip(dict(address='2.3.4.5'))],
+ gateway=new_ip(dict(address='192.168.1.1')),
+ ips=[new_ip(dict(address='192.168.1.100')),
+ new_ip(dict(address='192.168.1.101'))],
+ routes=[new_route()],
+ version=4)
+ subnet_dict = subnet_dict or {}
+ new_subnet.update(subnet_dict)
+ return model.Subnet(**new_subnet)
+
+
+def new_network(network_dict=None):
+ new_net = dict(
+ id=1,
+ bridge='br0',
+ label='public',
+ subnets=[new_subnet(), new_subnet(dict(cidr='255.255.255.255'))])
+ network_dict = network_dict or {}
+ new_net.update(network_dict)
+ return model.Network(**new_net)
+
+
+def new_vif(vif_dict=None):
+ vif = dict(
+ id=1,
+ address='aa:aa:aa:aa:aa:aa',
+ network=new_network())
+ vif_dict = vif_dict or {}
+ vif.update(vif_dict)
+ return model.VIF(**vif)
diff --git a/nova/tests/test_network_info.py b/nova/tests/test_network_info.py
new file mode 100644
index 000000000..1fd30dc9a
--- /dev/null
+++ b/nova/tests/test_network_info.py
@@ -0,0 +1,328 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from nova import exception
+from nova import log as logging
+from nova.network import model
+from nova import test
+from nova.tests import fake_network_cache_model
+
+LOG = logging.getLogger('nova.tests.network')
+
+
+class RouteTests(test.TestCase):
+ def test_create_route_with_attrs(self):
+ route = fake_network_cache_model.new_route()
+ ip = fake_network_cache_model.new_ip(dict(address='192.168.1.1'))
+ self.assertEqual(route['cidr'], '0.0.0.0/24')
+ self.assertEqual(route['gateway']['address'], '192.168.1.1')
+ self.assertEqual(route['interface'], 'eth0')
+
+ def test_routes_equal(self):
+ route1 = fake_network_cache_model.new_route()
+ route2 = fake_network_cache_model.new_route()
+ self.assertEqual(route1, route2)
+
+ def test_routes_not_equal(self):
+ route1 = fake_network_cache_model.new_route()
+ route2 = fake_network_cache_model.new_route(dict(cidr='1.1.1.1/24'))
+ self.assertNotEqual(route1, route2)
+
+ def test_hydrate(self):
+ route = model.Route.hydrate(
+ {'gateway': fake_network_cache_model.new_ip(
+ dict(address='192.168.1.1'))})
+ self.assertEqual(route['cidr'], None)
+ self.assertEqual(route['gateway']['address'], '192.168.1.1')
+ self.assertEqual(route['interface'], None)
+
+
+class FixedIPTests(test.TestCase):
+ def test_createnew_fixed_ip_with_attrs(self):
+ fixed_ip = model.FixedIP(address='192.168.1.100')
+ self.assertEqual(fixed_ip['address'], '192.168.1.100')
+ self.assertEqual(fixed_ip['floating_ips'], [])
+ self.assertEqual(fixed_ip['type'], 'fixed')
+ self.assertEqual(fixed_ip['version'], 4)
+
+ def test_create_fixed_ipv6(self):
+ fixed_ip = model.FixedIP(address='::1')
+ self.assertEqual(fixed_ip['address'], '::1')
+ self.assertEqual(fixed_ip['floating_ips'], [])
+ self.assertEqual(fixed_ip['type'], 'fixed')
+ self.assertEqual(fixed_ip['version'], 6)
+
+ def test_create_fixed_bad_ip_fails(self):
+ self.assertRaises(exception.InvalidIpAddressError,
+ model.FixedIP,
+ address='picklespicklespickles')
+
+ def test_equate_two_fixed_ips(self):
+ fixed_ip = model.FixedIP(address='::1')
+ fixed_ip2 = model.FixedIP(address='::1')
+ self.assertEqual(fixed_ip, fixed_ip2)
+
+ def test_equate_two_dissimilar_fixed_ips_fails(self):
+ fixed_ip = model.FixedIP(address='::1')
+ fixed_ip2 = model.FixedIP(address='::2')
+ self.assertNotEqual(fixed_ip, fixed_ip2)
+
+ def test_hydrate(self):
+ fixed_ip = model.FixedIP.hydrate({})
+ self.assertEqual(fixed_ip['floating_ips'], [])
+ self.assertEqual(fixed_ip['address'], None)
+ self.assertEqual(fixed_ip['type'], 'fixed')
+ self.assertEqual(fixed_ip['version'], None)
+
+ def test_add_floating_ip(self):
+ fixed_ip = model.FixedIP(address='192.168.1.100')
+ fixed_ip.add_floating_ip('192.168.1.101')
+ self.assertEqual(fixed_ip['floating_ips'], ['192.168.1.101'])
+
+ def test_add_floating_ip_repeatedly_only_one_instance(self):
+ fixed_ip = model.FixedIP(address='192.168.1.100')
+ for i in xrange(10):
+ fixed_ip.add_floating_ip('192.168.1.101')
+ self.assertEqual(fixed_ip['floating_ips'], ['192.168.1.101'])
+
+
+class SubnetTests(test.TestCase):
+ def test_create_subnet_with_attrs(self):
+ subnet = fake_network_cache_model.new_subnet()
+
+ route1 = fake_network_cache_model.new_route()
+
+ self.assertEqual(subnet['cidr'], '255.255.255.0')
+ self.assertEqual(subnet['dns'],
+ [fake_network_cache_model.new_ip(dict(address='1.2.3.4')),
+ fake_network_cache_model.new_ip(dict(address='2.3.4.5'))])
+ self.assertEqual(subnet['gateway']['address'], '192.168.1.1')
+ self.assertEqual(subnet['ips'],
+ [fake_network_cache_model.new_ip(
+ dict(address='192.168.1.100')),
+ fake_network_cache_model.new_ip(
+ dict(address='192.168.1.101'))])
+ self.assertEqual(subnet['routes'], [route1])
+ self.assertEqual(subnet['version'], 4)
+
+ def test_add_route(self):
+ subnet = fake_network_cache_model.new_subnet()
+ route1 = fake_network_cache_model.new_route()
+ route2 = fake_network_cache_model.new_route({'cidr': '1.1.1.1/24'})
+ subnet.add_route(route2)
+ self.assertEqual(subnet['routes'], [route1, route2])
+
+ def test_add_route_a_lot(self):
+ subnet = fake_network_cache_model.new_subnet()
+ route1 = fake_network_cache_model.new_route()
+ route2 = fake_network_cache_model.new_route({'cidr': '1.1.1.1/24'})
+ for i in xrange(10):
+ subnet.add_route(route2)
+ self.assertEqual(subnet['routes'], [route1, route2])
+
+ def test_add_dns(self):
+ subnet = fake_network_cache_model.new_subnet()
+ dns = fake_network_cache_model.new_ip(dict(address='9.9.9.9'))
+ subnet.add_dns(dns)
+ self.assertEqual(subnet['dns'],
+ [fake_network_cache_model.new_ip(dict(address='1.2.3.4')),
+ fake_network_cache_model.new_ip(dict(address='2.3.4.5')),
+ fake_network_cache_model.new_ip(dict(address='9.9.9.9'))])
+
+ def test_add_dns_a_lot(self):
+ subnet = fake_network_cache_model.new_subnet()
+ for i in xrange(10):
+ subnet.add_dns(fake_network_cache_model.new_ip(
+ dict(address='9.9.9.9')))
+ self.assertEqual(subnet['dns'],
+ [fake_network_cache_model.new_ip(dict(address='1.2.3.4')),
+ fake_network_cache_model.new_ip(dict(address='2.3.4.5')),
+ fake_network_cache_model.new_ip(dict(address='9.9.9.9'))])
+
+ def test_add_ip(self):
+ subnet = fake_network_cache_model.new_subnet()
+ subnet.add_ip(fake_network_cache_model.new_ip(
+ dict(address='192.168.1.102')))
+ self.assertEqual(subnet['ips'],
+ [fake_network_cache_model.new_ip(
+ dict(address='192.168.1.100')),
+ fake_network_cache_model.new_ip(
+ dict(address='192.168.1.101')),
+ fake_network_cache_model.new_ip(
+ dict(address='192.168.1.102'))])
+
+ def test_add_ip_a_lot(self):
+ subnet = fake_network_cache_model.new_subnet()
+ for i in xrange(10):
+ subnet.add_ip(fake_network_cache_model.new_ip(
+ dict(address='192.168.1.102')))
+ self.assertEqual(subnet['ips'],
+ [fake_network_cache_model.new_ip(
+ dict(address='192.168.1.100')),
+ fake_network_cache_model.new_ip(
+ dict(address='192.168.1.101')),
+ fake_network_cache_model.new_ip(
+ dict(address='192.168.1.102'))])
+
+ def test_hydrate(self):
+ subnet_dict = {
+ 'cidr': '255.255.255.0',
+ 'dns': [fake_network_cache_model.new_ip(dict(address='1.1.1.1'))],
+ 'ips': [fake_network_cache_model.new_ip(dict(address='2.2.2.2'))],
+ 'routes': [fake_network_cache_model.new_route()],
+ 'version': 4,
+ 'gateway': fake_network_cache_model.new_ip(
+ dict(address='3.3.3.3'))}
+ subnet = model.Subnet.hydrate(subnet_dict)
+
+ self.assertEqual(subnet['cidr'], '255.255.255.0')
+ self.assertEqual(subnet['dns'], [fake_network_cache_model.new_ip(
+ dict(address='1.1.1.1'))])
+ self.assertEqual(subnet['gateway']['address'], '3.3.3.3')
+ self.assertEqual(subnet['ips'], [fake_network_cache_model.new_ip(
+ dict(address='2.2.2.2'))])
+ self.assertEqual(subnet['routes'], [
+ fake_network_cache_model.new_route()])
+ self.assertEqual(subnet['version'], 4)
+
+
+class NetworkTests(test.TestCase):
+ def test_create_network(self):
+ network = fake_network_cache_model.new_network()
+ self.assertEqual(network['id'], 1)
+ self.assertEqual(network['bridge'], 'br0')
+ self.assertEqual(network['label'], 'public')
+ self.assertEqual(network['subnets'],
+ [fake_network_cache_model.new_subnet(),
+ fake_network_cache_model.new_subnet(
+ dict(cidr='255.255.255.255'))])
+
+ def test_add_subnet(self):
+ network = fake_network_cache_model.new_network()
+ network.add_subnet(fake_network_cache_model.new_subnet(
+ dict(cidr='0.0.0.0')))
+ self.assertEqual(network['subnets'],
+ [fake_network_cache_model.new_subnet(),
+ fake_network_cache_model.new_subnet(
+ dict(cidr='255.255.255.255')),
+ fake_network_cache_model.new_subnet(dict(cidr='0.0.0.0'))])
+
+ def test_add_subnet_a_lot(self):
+ network = fake_network_cache_model.new_network()
+ for i in xrange(10):
+ network.add_subnet(fake_network_cache_model.new_subnet(
+ dict(cidr='0.0.0.0')))
+ self.assertEqual(network['subnets'],
+ [fake_network_cache_model.new_subnet(),
+ fake_network_cache_model.new_subnet(
+ dict(cidr='255.255.255.255')),
+ fake_network_cache_model.new_subnet(dict(cidr='0.0.0.0'))])
+
+ def test_hydrate(self):
+ new_network = dict(
+ id=1,
+ bridge='br0',
+ label='public',
+ subnets=[fake_network_cache_model.new_subnet(),
+ fake_network_cache_model.new_subnet(
+ dict(cidr='255.255.255.255'))])
+ network = model.Network.hydrate(fake_network_cache_model.new_network())
+
+ self.assertEqual(network['id'], 1)
+ self.assertEqual(network['bridge'], 'br0')
+ self.assertEqual(network['label'], 'public')
+ self.assertEqual(network['subnets'],
+ [fake_network_cache_model.new_subnet(),
+ fake_network_cache_model.new_subnet(
+ dict(cidr='255.255.255.255'))])
+
+
+class VIFTests(test.TestCase):
+ def test_create_vif(self):
+ vif = fake_network_cache_model.new_vif()
+ self.assertEqual(vif['id'], 1)
+ self.assertEqual(vif['address'], 'aa:aa:aa:aa:aa:aa')
+ self.assertEqual(vif['network'],
+ fake_network_cache_model.new_network())
+
+ def test_vif_get_fixed_ips(self):
+ vif = fake_network_cache_model.new_vif()
+ fixed_ips = vif.fixed_ips()
+ ips = [fake_network_cache_model.new_ip(dict(address='192.168.1.100')),
+ fake_network_cache_model.new_ip(
+ dict(address='192.168.1.101'))] * 2
+ self.assertEqual(fixed_ips, ips)
+
+ def test_vif_get_floating_ips(self):
+ vif = fake_network_cache_model.new_vif()
+ vif['network']['subnets'][0]['ips'][0].add_floating_ip('192.168.1.1')
+ floating_ips = vif.floating_ips()
+ self.assertEqual(floating_ips, ['192.168.1.1'])
+
+ def test_vif_get_labeled_ips(self):
+ vif = fake_network_cache_model.new_vif()
+ labeled_ips = vif.labeled_ips()
+ ip_dict = {
+ 'network_id': 1,
+ 'ips': [fake_network_cache_model.new_ip(
+ {'address': '192.168.1.100'}),
+ fake_network_cache_model.new_ip(
+ {'address': '192.168.1.101'})] * 2,
+ 'network_label': 'public'}
+ self.assertEqual(labeled_ips, ip_dict)
+
+ def test_hydrate(self):
+ new_vif = dict(
+ id=1,
+ address='127.0.0.1',
+ network=fake_network_cache_model.new_network())
+ vif = model.VIF.hydrate(fake_network_cache_model.new_vif())
+ self.assertEqual(vif['id'], 1)
+ self.assertEqual(vif['address'], 'aa:aa:aa:aa:aa:aa')
+ self.assertEqual(vif['network'],
+ fake_network_cache_model.new_network())
+
+
+class NetworkInfoTests(test.TestCase):
+ def test_create_model(self):
+ ninfo = model.NetworkInfo([fake_network_cache_model.new_vif(),
+ fake_network_cache_model.new_vif(
+ {'address':'bb:bb:bb:bb:bb:bb'})])
+ self.assertEqual(ninfo.fixed_ips(),
+ [fake_network_cache_model.new_ip({'address': '192.168.1.100'}),
+ fake_network_cache_model.new_ip(
+ {'address': '192.168.1.101'})] * 4)
+
+ def test_get_floating_ips(self):
+ vif = fake_network_cache_model.new_vif()
+ vif['network']['subnets'][0]['ips'][0].add_floating_ip('192.168.1.1')
+ ninfo = model.NetworkInfo([vif,
+ fake_network_cache_model.new_vif(
+ {'address':'bb:bb:bb:bb:bb:bb'})])
+ self.assertEqual(ninfo.floating_ips(), ['192.168.1.1'])
+
+ def test_hydrate(self):
+ ninfo = model.NetworkInfo([fake_network_cache_model.new_vif(),
+ fake_network_cache_model.new_vif(
+ {'address':'bb:bb:bb:bb:bb:bb'})])
+ deserialized = model.NetworkInfo.hydrate(ninfo)
+ self.assertEqual(ninfo.fixed_ips(),
+ [fake_network_cache_model.new_ip({'address': '192.168.1.100'}),
+ fake_network_cache_model.new_ip(
+ {'address': '192.168.1.101'})] * 4)