diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5313f2fb4cfd..0202d954e5a0 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1357,8 +1357,17 @@ def fixed_ip_get_by_instance(context, instance_uuid): if not uuidutils.is_uuid_like(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").\ 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() 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): result = model_query(context, models.FixedIp, read_deleted="no").\ filter_by(virtual_interface_id=vif_id).\ + options(joinedload('network')).\ + options(joinedload('floating_ips')).\ all() return result diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 6eaa21eda78d..3ae0ac36a577 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -891,6 +891,14 @@ class FixedIp(BASE, NovaBase): 'FixedIp.instance_uuid == Instance.uuid,' 'FixedIp.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): diff --git a/nova/objects/fixed_ip.py b/nova/objects/fixed_ip.py index 2482b635e5a5..f3d42ed6a694 100644 --- a/nova/objects/fixed_ip.py +++ b/nova/objects/fixed_ip.py @@ -21,7 +21,8 @@ from nova.openstack.common import timeutils 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): @@ -30,7 +31,8 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject): # Version 1.2: Instance version 1.14 # Version 1.3: Instance 1.15 # Version 1.4: Added default_route field - VERSION = '1.4' + # Version 1.5: Added floating_ips field + VERSION = '1.5' fields = { 'id': fields.IntegerField(), @@ -47,10 +49,16 @@ class FixedIP(obj_base.NovaPersistentObject, obj_base.NovaObject): 'network': fields.ObjectField('Network', nullable=True), 'virtual_interface': fields.ObjectField('VirtualInterface', 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): 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: del primitive['default_route'] 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.version'] = '1.13' - @property - def floating_ips(self): - return objects.FloatingIPList.get_by_fixed_ip_id(self._context, - self.id) - @staticmethod def _from_db_object(context, fixedip, db_fixedip, expected_attrs=None): if expected_attrs is None: expected_attrs = [] for field in fixedip.fields: - if field in ('virtual_interface', 'default_route'): - # NOTE(danms): These fields are only set when doing a + if field == 'default_route': + # NOTE(danms): This field is only set when doing a # FixedIPList.get_by_network() because it's a relatively - # special-case thing, so skip them here + # special-case thing, so skip it here continue if field not in FIXED_IP_OPTIONAL_ATTRS: 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 if 'network' in expected_attrs: 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.obj_reset_changes() return fixedip @@ -185,7 +201,8 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject): # Version 1.2: FixedIP <= version 1.2 # Version 1.3: FixedIP <= version 1.3 # 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 = { 'objects': fields.ListOfObjectsField('FixedIP'), @@ -196,6 +213,7 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject): '1.2': '1.2', '1.3': '1.3', '1.4': '1.4', + '1.5': '1.5', } @obj_base.remotable_classmethod @@ -206,9 +224,11 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject): @obj_base.remotable_classmethod 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) 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 def get_by_host(cls, context, host): @@ -218,9 +238,11 @@ class FixedIPList(obj_base.ObjectListBase, obj_base.NovaObject): @obj_base.remotable_classmethod 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) 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 def get_by_network(cls, context, network, host=None): diff --git a/nova/objects/floating_ip.py b/nova/objects/floating_ip.py index 173bc5209d32..39b06725bcc8 100644 --- a/nova/objects/floating_ip.py +++ b/nova/objects/floating_ip.py @@ -28,7 +28,8 @@ class FloatingIP(obj_base.NovaPersistentObject, obj_base.NovaObject): # Version 1.2: FixedIP <= version 1.2 # Version 1.3: FixedIP <= version 1.3 # Version 1.4: FixedIP <= version 1.4 - VERSION = '1.4' + # Version 1.5: FixedIP <= version 1.5 + VERSION = '1.5' fields = { 'id': fields.IntegerField(), 'address': fields.IPAddressField(), @@ -55,6 +56,10 @@ class FloatingIP(obj_base.NovaPersistentObject, obj_base.NovaObject): self.fixed_ip.obj_make_compatible( primitive['fixed_ip']['nova_object.data'], '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 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.4: FloatingIP 1.3 # Version 1.5: FloatingIP 1.4 + # Version 1.6: FloatingIP 1.5 fields = { 'objects': fields.ListOfObjectsField('FloatingIP'), } @@ -183,8 +189,9 @@ class FloatingIPList(obj_base.ObjectListBase, obj_base.NovaObject): '1.3': '1.2', '1.4': '1.3', '1.5': '1.4', + '1.6': '1.5', } - VERSION = '1.5' + VERSION = '1.6' @obj_base.remotable_classmethod def get_all(cls, context): diff --git a/nova/tests/db/test_db_api.py b/nova/tests/db/test_db_api.py index 9da6be3548ff..b06dcda1d583 100644 --- a/nova/tests/db/test_db_api.py +++ b/nova/tests/db/test_db_api.py @@ -3610,7 +3610,8 @@ class FixedIPTestCase(BaseInstanceTypeTestCase): ] 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) # we have no `id` in incoming data so we can not use diff --git a/nova/tests/objects/test_fixed_ip.py b/nova/tests/objects/test_fixed_ip.py index e30459a124d4..19b8b187afc2 100644 --- a/nova/tests/objects/test_fixed_ip.py +++ b/nova/tests/objects/test_fixed_ip.py @@ -40,13 +40,16 @@ fake_fixed_ip = { 'leased': False, 'reserved': False, 'host': None, + 'network': None, + 'virtual_interface': None, + 'floating_ips': [], } class _TestFixedIPObject(object): def _compare(self, obj, db_obj): for field in obj.fields: - if field in ('virtual_interface', 'default_route'): + if field in ('default_route', 'floating_ips'): continue if field in fixed_ip.FIXED_IP_OPTIONAL_ATTRS: 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) 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') def test_bulk_create(self, bulk): fixed_ips = [fixed_ip.FixedIP(address='192.168.1.1'), diff --git a/nova/tests/objects/test_objects.py b/nova/tests/objects/test_objects.py index 35fc5d9407b5..06ec4c1fc908 100644 --- a/nova/tests/objects/test_objects.py +++ b/nova/tests/objects/test_objects.py @@ -944,12 +944,12 @@ object_data = { 'EC2InstanceMapping': '1.0-627baaf4b12c9067200979bdc4558a99', 'EC2SnapshotMapping': '1.0-26cf315be1f8abab4289d4147671c836', 'EC2VolumeMapping': '1.0-2f8c3bf077c65a425294ec2b361c9143', - 'FixedIP': '1.4-c86389e85d762b7857db084b0dad0f24', - 'FixedIPList': '1.4-663f3790a8783daede3d56748329361e', + 'FixedIP': '1.5-2472964d39e50da67202109eb85cd173', + 'FixedIPList': '1.5-b150167937c905b207769a17e7201d15', 'Flavor': '1.1-096cfd023c35d07542cf732fb29b45e4', 'FlavorList': '1.1-a3d5551267cb8f62ff38ded125900721', - 'FloatingIP': '1.4-27eb68b7c9c620dd5f0561b5a3be0e82', - 'FloatingIPList': '1.5-28f21c9c8a12afd51535b3f17900667d', + 'FloatingIP': '1.5-27eb68b7c9c620dd5f0561b5a3be0e82', + 'FloatingIPList': '1.6-6b50a8954fbd03b2bdd01df088210e86', 'Instance': '1.15-1154dc29398bc3c57f053b8e449bb03d', 'InstanceAction': '1.1-6b1d0a6dbd522b5a83c20757ec659663', 'InstanceActionEvent': '1.1-42dbdba74bd06e0619ca75cd3397cd1b', @@ -996,8 +996,9 @@ object_data = { object_relationships = { 'BlockDeviceMapping': {'Instance': '1.15'}, 'FixedIP': {'Instance': '1.15', 'Network': '1.2', - 'VirtualInterface': '1.0'}, - 'FloatingIP': {'FixedIP': '1.4'}, + 'VirtualInterface': '1.0', + 'FloatingIPList': '1.6'}, + 'FloatingIP': {'FixedIP': '1.5'}, 'Instance': {'InstanceFault': '1.2', 'InstanceInfoCache': '1.5', 'InstanceNUMATopology': '1.0',