Use database joins for fixed ips to other objects

Many of the fixed ip related calls are extremely inefficient. This
joins a couple of calls so that we can optimize queries that use
them.

Change-Id: Ifa2e4c6ac517286f7ae952052d29801b45a6b6b2
Partial-bug: #1365606
This commit is contained in:
Vishvananda Ishaya 2014-09-04 23:25:32 -07:00
parent 279b6e98bc
commit 31b0961c85
7 changed files with 81 additions and 24 deletions

@ -1357,8 +1357,17 @@ def fixed_ip_get_by_instance(context, instance_uuid):
if not uuidutils.is_uuid_like(instance_uuid): if not uuidutils.is_uuid_like(instance_uuid):
raise exception.InvalidUUID(uuid=instance_uuid) raise exception.InvalidUUID(uuid=instance_uuid)
vif_and = and_(models.VirtualInterface.id ==
models.FixedIp.virtual_interface_id,
models.VirtualInterface.deleted == 0)
result = model_query(context, models.FixedIp, read_deleted="no").\ result = model_query(context, models.FixedIp, read_deleted="no").\
filter_by(instance_uuid=instance_uuid).\ filter_by(instance_uuid=instance_uuid).\
outerjoin(models.VirtualInterface, vif_and).\
options(contains_eager("virtual_interface")).\
options(joinedload('network')).\
options(joinedload('floating_ips')).\
order_by(asc(models.VirtualInterface.created_at),
asc(models.VirtualInterface.id)).\
all() all()
if not result: if not result:
@ -1398,6 +1407,8 @@ def fixed_ip_get_by_network_host(context, network_id, host):
def fixed_ips_by_virtual_interface(context, vif_id): def fixed_ips_by_virtual_interface(context, vif_id):
result = model_query(context, models.FixedIp, read_deleted="no").\ result = model_query(context, models.FixedIp, read_deleted="no").\
filter_by(virtual_interface_id=vif_id).\ filter_by(virtual_interface_id=vif_id).\
options(joinedload('network')).\
options(joinedload('floating_ips')).\
all() all()
return result return result

@ -891,6 +891,14 @@ class FixedIp(BASE, NovaBase):
'FixedIp.instance_uuid == Instance.uuid,' 'FixedIp.instance_uuid == Instance.uuid,'
'FixedIp.deleted == 0,' 'FixedIp.deleted == 0,'
'Instance.deleted == 0)') 'Instance.deleted == 0)')
virtual_interface = orm.relationship(VirtualInterface,
backref=orm.backref('fixed_ips'),
foreign_keys=virtual_interface_id,
primaryjoin='and_('
'FixedIp.virtual_interface_id == '
'VirtualInterface.id,'
'FixedIp.deleted == 0,'
'VirtualInterface.deleted == 0)')
class FloatingIp(BASE, NovaBase): class FloatingIp(BASE, NovaBase):

@ -21,7 +21,8 @@ from nova.openstack.common import timeutils
from nova import utils from nova import utils
FIXED_IP_OPTIONAL_ATTRS = ['instance', 'network'] FIXED_IP_OPTIONAL_ATTRS = ['instance', 'network', 'virtual_interface',
'floating_ips']
class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject): class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
@ -30,7 +31,8 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
# Version 1.2: Instance version 1.14 # Version 1.2: Instance version 1.14
# Version 1.3: Instance 1.15 # Version 1.3: Instance 1.15
# Version 1.4: Added default_route field # Version 1.4: Added default_route field
VERSION = '1.4' # Version 1.5: Added floating_ips field
VERSION = '1.5'
fields = { fields = {
'id': fields.IntegerField(), 'id': fields.IntegerField(),
@ -47,10 +49,16 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
'network': fields.ObjectField('Network', nullable=True), 'network': fields.ObjectField('Network', nullable=True),
'virtual_interface': fields.ObjectField('VirtualInterface', 'virtual_interface': fields.ObjectField('VirtualInterface',
nullable=True), nullable=True),
# NOTE(danms): This should not ever be made lazy-loadable
# because it would create a bit of a loop between FixedIP
# and FloatingIP
'floating_ips': fields.ObjectField('FloatingIPList'),
} }
def obj_make_compatible(self, primitive, target_version): def obj_make_compatible(self, primitive, target_version):
target_version = utils.convert_version_to_tuple(target_version) target_version = utils.convert_version_to_tuple(target_version)
if target_version < (1, 5) and 'floating_ips' in primitive:
del primitive['floating_ips']
if target_version < (1, 4) and 'default_route' in primitive: if target_version < (1, 4) and 'default_route' in primitive:
del primitive['default_route'] del primitive['default_route']
if target_version < (1, 3) and 'instance' in primitive: if target_version < (1, 3) and 'instance' in primitive:
@ -62,20 +70,15 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
primitive['instance']['nova_object.data'], '1.13') primitive['instance']['nova_object.data'], '1.13')
primitive['instance']['nova_object.version'] = '1.13' primitive['instance']['nova_object.version'] = '1.13'
@property
def floating_ips(self):
return objects.FloatingIPList.get_by_fixed_ip_id(self._context,
self.id)
@staticmethod @staticmethod
def _from_db_object(context, fixedip, db_fixedip, expected_attrs=None): def _from_db_object(context, fixedip, db_fixedip, expected_attrs=None):
if expected_attrs is None: if expected_attrs is None:
expected_attrs = [] expected_attrs = []
for field in fixedip.fields: for field in fixedip.fields:
if field in ('virtual_interface', 'default_route'): if field == 'default_route':
# NOTE(danms): These fields are only set when doing a # NOTE(danms): This field is only set when doing a
# FixedIPList.get_by_network() because it's a relatively # FixedIPList.get_by_network() because it's a relatively
# special-case thing, so skip them here # special-case thing, so skip it here
continue continue
if field not in FIXED_IP_OPTIONAL_ATTRS: if field not in FIXED_IP_OPTIONAL_ATTRS:
fixedip[field] = db_fixedip[field] fixedip[field] = db_fixedip[field]
@ -87,7 +90,20 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
db_fixedip['instance']) if db_fixedip['instance'] else None db_fixedip['instance']) if db_fixedip['instance'] else None
if 'network' in expected_attrs: if 'network' in expected_attrs:
fixedip.network = objects.Network._from_db_object( fixedip.network = objects.Network._from_db_object(
context, objects.Network(context), db_fixedip['network']) context,
objects.Network(context),
db_fixedip['network']) if db_fixedip['network'] else None
if 'virtual_interface' in expected_attrs:
db_vif = db_fixedip['virtual_interface']
vif = objects.VirtualInterface._from_db_object(
context,
objects.VirtualInterface(context),
db_fixedip['virtual_interface']) if db_vif else None
fixedip.virtual_interface = vif
if 'floating_ips' in expected_attrs:
fixedip.floating_ips = obj_base.obj_make_list(
context, objects.FloatingIPList(context),
objects.FloatingIP, db_fixedip['floating_ips'])
fixedip._context = context fixedip._context = context
fixedip.obj_reset_changes() fixedip.obj_reset_changes()
return fixedip return fixedip
@ -185,7 +201,8 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
# Version 1.2: FixedIP <= version 1.2 # Version 1.2: FixedIP <= version 1.2
# Version 1.3: FixedIP <= version 1.3 # Version 1.3: FixedIP <= version 1.3
# Version 1.4: FixedIP <= version 1.4 # Version 1.4: FixedIP <= version 1.4
VERSION = '1.4' # Version 1.5: FixedIP <= version 1.5, added expected attrs to gets
VERSION = '1.5'
fields = { fields = {
'objects': fields.ListOfObjectsField('FixedIP'), 'objects': fields.ListOfObjectsField('FixedIP'),
@ -196,6 +213,7 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
'1.2': '1.2', '1.2': '1.2',
'1.3': '1.3', '1.3': '1.3',
'1.4': '1.4', '1.4': '1.4',
'1.5': '1.5',
} }
@obj_base.remotable_classmethod @obj_base.remotable_classmethod
@ -206,9 +224,11 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
@obj_base.remotable_classmethod @obj_base.remotable_classmethod
def get_by_instance_uuid(cls, context, instance_uuid): def get_by_instance_uuid(cls, context, instance_uuid):
expected_attrs = ['network', 'virtual_interface', 'floating_ips']
db_fixedips = db.fixed_ip_get_by_instance(context, instance_uuid) db_fixedips = db.fixed_ip_get_by_instance(context, instance_uuid)
return obj_base.obj_make_list(context, cls(context), return obj_base.obj_make_list(context, cls(context),
objects.FixedIP, db_fixedips) objects.FixedIP, db_fixedips,
expected_attrs=expected_attrs)
@obj_base.remotable_classmethod @obj_base.remotable_classmethod
def get_by_host(cls, context, host): def get_by_host(cls, context, host):
@ -218,9 +238,11 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject):
@obj_base.remotable_classmethod @obj_base.remotable_classmethod
def get_by_virtual_interface_id(cls, context, vif_id): def get_by_virtual_interface_id(cls, context, vif_id):
expected_attrs = ['network', 'floating_ips']
db_fixedips = db.fixed_ips_by_virtual_interface(context, vif_id) db_fixedips = db.fixed_ips_by_virtual_interface(context, vif_id)
return obj_base.obj_make_list(context, cls(context), return obj_base.obj_make_list(context, cls(context),
objects.FixedIP, db_fixedips) objects.FixedIP, db_fixedips,
expected_attrs=expected_attrs)
@obj_base.remotable_classmethod @obj_base.remotable_classmethod
def get_by_network(cls, context, network, host=None): def get_by_network(cls, context, network, host=None):

@ -28,7 +28,8 @@ class FloatingIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
# Version 1.2: FixedIP <= version 1.2 # Version 1.2: FixedIP <= version 1.2
# Version 1.3: FixedIP <= version 1.3 # Version 1.3: FixedIP <= version 1.3
# Version 1.4: FixedIP <= version 1.4 # Version 1.4: FixedIP <= version 1.4
VERSION = '1.4' # Version 1.5: FixedIP <= version 1.5
VERSION = '1.5'
fields = { fields = {
'id': fields.IntegerField(), 'id': fields.IntegerField(),
'address': fields.IPAddressField(), 'address': fields.IPAddressField(),
@ -55,6 +56,10 @@ class FloatingIP(obj_base.NovaPersistentObject, obj_base.NovaObject):
self.fixed_ip.obj_make_compatible( self.fixed_ip.obj_make_compatible(
primitive['fixed_ip']['nova_object.data'], '1.3') primitive['fixed_ip']['nova_object.data'], '1.3')
primitive['fixed_ip']['nova_object.version'] = '1.3' primitive['fixed_ip']['nova_object.version'] = '1.3'
elif target_version < (1, 5) and self.obj_attr_is_set('fixed_ip'):
self.fixed_ip.obj_make_compatible(
primitive['fixed_ip']['nova_object.data'], '1.4')
primitive['fixed_ip']['nova_object.version'] = '1.4'
@staticmethod @staticmethod
def _from_db_object(context, floatingip, db_floatingip, def _from_db_object(context, floatingip, db_floatingip,
@ -173,6 +178,7 @@ class FloatingIPList(obj_base.ObjectListBase, obj_base.NovaObject):
# Version 1.3: FloatingIP 1.2 # Version 1.3: FloatingIP 1.2
# Version 1.4: FloatingIP 1.3 # Version 1.4: FloatingIP 1.3
# Version 1.5: FloatingIP 1.4 # Version 1.5: FloatingIP 1.4
# Version 1.6: FloatingIP 1.5
fields = { fields = {
'objects': fields.ListOfObjectsField('FloatingIP'), 'objects': fields.ListOfObjectsField('FloatingIP'),
} }
@ -183,8 +189,9 @@ class FloatingIPList(obj_base.ObjectListBase, obj_base.NovaObject):
'1.3': '1.2', '1.3': '1.2',
'1.4': '1.3', '1.4': '1.3',
'1.5': '1.4', '1.5': '1.4',
'1.6': '1.5',
} }
VERSION = '1.5' VERSION = '1.6'
@obj_base.remotable_classmethod @obj_base.remotable_classmethod
def get_all(cls, context): def get_all(cls, context):

@ -3610,7 +3610,8 @@ class FixedIPTestCase(BaseInstanceTypeTestCase):
] ]
db.fixed_ip_bulk_create(self.ctxt, params) db.fixed_ip_bulk_create(self.ctxt, params)
ignored_keys = ['created_at', 'id', 'deleted_at', 'updated_at'] ignored_keys = ['created_at', 'id', 'deleted_at', 'updated_at',
'virtual_interface', 'network', 'floating_ips']
fixed_ip_data = db.fixed_ip_get_by_instance(self.ctxt, instance_uuid) fixed_ip_data = db.fixed_ip_get_by_instance(self.ctxt, instance_uuid)
# we have no `id` in incoming data so we can not use # we have no `id` in incoming data so we can not use

@ -40,13 +40,16 @@ fake_fixed_ip = {
'leased': False, 'leased': False,
'reserved': False, 'reserved': False,
'host': None, 'host': None,
'network': None,
'virtual_interface': None,
'floating_ips': [],
} }
class _TestFixedIPObject(object): class _TestFixedIPObject(object):
def _compare(self, obj, db_obj): def _compare(self, obj, db_obj):
for field in obj.fields: for field in obj.fields:
if field in ('virtual_interface', 'default_route'): if field in ('default_route', 'floating_ips'):
continue continue
if field in fixed_ip.FIXED_IP_OPTIONAL_ATTRS: if field in fixed_ip.FIXED_IP_OPTIONAL_ATTRS:
if obj.obj_attr_is_set(field) and db_obj[field] is not None: if obj.obj_attr_is_set(field) and db_obj[field] is not None:
@ -257,6 +260,10 @@ class _TestFixedIPObject(object):
get.assert_called_once_with(self.context, 123) get.assert_called_once_with(self.context, 123)
self._compare(fixedips[0], fake_fixed_ip) self._compare(fixedips[0], fake_fixed_ip)
def test_floating_ips_do_not_lazy_load(self):
fixedip = fixed_ip.FixedIP()
self.assertRaises(NotImplementedError, lambda: fixedip.floating_ips)
@mock.patch('nova.db.fixed_ip_bulk_create') @mock.patch('nova.db.fixed_ip_bulk_create')
def test_bulk_create(self, bulk): def test_bulk_create(self, bulk):
fixed_ips = [fixed_ip.FixedIP(address='192.168.1.1'), fixed_ips = [fixed_ip.FixedIP(address='192.168.1.1'),

@ -944,12 +944,12 @@ object_data = {
'EC2InstanceMapping': '1.0-627baaf4b12c9067200979bdc4558a99', 'EC2InstanceMapping': '1.0-627baaf4b12c9067200979bdc4558a99',
'EC2SnapshotMapping': '1.0-26cf315be1f8abab4289d4147671c836', 'EC2SnapshotMapping': '1.0-26cf315be1f8abab4289d4147671c836',
'EC2VolumeMapping': '1.0-2f8c3bf077c65a425294ec2b361c9143', 'EC2VolumeMapping': '1.0-2f8c3bf077c65a425294ec2b361c9143',
'FixedIP': '1.4-c86389e85d762b7857db084b0dad0f24', 'FixedIP': '1.5-2472964d39e50da67202109eb85cd173',
'FixedIPList': '1.4-663f3790a8783daede3d56748329361e', 'FixedIPList': '1.5-b150167937c905b207769a17e7201d15',
'Flavor': '1.1-096cfd023c35d07542cf732fb29b45e4', 'Flavor': '1.1-096cfd023c35d07542cf732fb29b45e4',
'FlavorList': '1.1-a3d5551267cb8f62ff38ded125900721', 'FlavorList': '1.1-a3d5551267cb8f62ff38ded125900721',
'FloatingIP': '1.4-27eb68b7c9c620dd5f0561b5a3be0e82', 'FloatingIP': '1.5-27eb68b7c9c620dd5f0561b5a3be0e82',
'FloatingIPList': '1.5-28f21c9c8a12afd51535b3f17900667d', 'FloatingIPList': '1.6-6b50a8954fbd03b2bdd01df088210e86',
'Instance': '1.15-1154dc29398bc3c57f053b8e449bb03d', 'Instance': '1.15-1154dc29398bc3c57f053b8e449bb03d',
'InstanceAction': '1.1-6b1d0a6dbd522b5a83c20757ec659663', 'InstanceAction': '1.1-6b1d0a6dbd522b5a83c20757ec659663',
'InstanceActionEvent': '1.1-42dbdba74bd06e0619ca75cd3397cd1b', 'InstanceActionEvent': '1.1-42dbdba74bd06e0619ca75cd3397cd1b',
@ -996,8 +996,9 @@ object_data = {
object_relationships = { object_relationships = {
'BlockDeviceMapping': {'Instance': '1.15'}, 'BlockDeviceMapping': {'Instance': '1.15'},
'FixedIP': {'Instance': '1.15', 'Network': '1.2', 'FixedIP': {'Instance': '1.15', 'Network': '1.2',
'VirtualInterface': '1.0'}, 'VirtualInterface': '1.0',
'FloatingIP': {'FixedIP': '1.4'}, 'FloatingIPList': '1.6'},
'FloatingIP': {'FixedIP': '1.5'},
'Instance': {'InstanceFault': '1.2', 'Instance': {'InstanceFault': '1.2',
'InstanceInfoCache': '1.5', 'InstanceInfoCache': '1.5',
'InstanceNUMATopology': '1.0', 'InstanceNUMATopology': '1.0',