diff options
author | Yaguang Tang <heut2008@gmail.com> | 2012-06-28 17:07:08 +0800 |
---|---|---|
committer | Vishvananda Ishaya <vishvananda@gmail.com> | 2012-06-28 13:01:55 -0700 |
commit | 0dc32690fe158e4cb11c2c9bcc65acaf73b94a7a (patch) | |
tree | 26e210b370783165edaf86f5ab8b03ab12313947 | |
parent | 2264c1c0b6ccfe7dc3e4c7e448b4a5eac92758d4 (diff) | |
download | nova-0dc32690fe158e4cb11c2c9bcc65acaf73b94a7a.tar.gz nova-0dc32690fe158e4cb11c2c9bcc65acaf73b94a7a.tar.xz nova-0dc32690fe158e4cb11c2c9bcc65acaf73b94a7a.zip |
Implement blueprint ec2-id-compatibilty.
Instance ids are used by the ec2 layer to create ec2-ids. This
currently uses the id column in the instances table. This patch
creates a new mapping table for ec2-ids. This decouples the ec2
layer from needing direct access to the instances table so that
it can eventually be pulled out if necessary. It also matches the
way that the ec2 layer maps image, volume, and snaphsot ids.
Finally, it allows us to eventually remove the id column from
the instances table and only have one canonical id (uuid) to
refer to instances.
Change-Id: I02ad9fad37e6a04675543398f686351634bc1bb9
-rw-r--r-- | nova/api/ec2/__init__.py | 2 | ||||
-rw-r--r-- | nova/api/ec2/cloud.py | 18 | ||||
-rw-r--r-- | nova/api/ec2/ec2utils.py | 36 | ||||
-rw-r--r-- | nova/api/metadata/base.py | 3 | ||||
-rw-r--r-- | nova/db/api.py | 18 | ||||
-rw-r--r-- | nova/db/sqlalchemy/api.py | 50 | ||||
-rw-r--r-- | nova/db/sqlalchemy/migrate_repo/versions/107_add_instance_id_mappings.py | 67 | ||||
-rw-r--r-- | nova/db/sqlalchemy/models.py | 7 | ||||
-rw-r--r-- | nova/tests/api/ec2/test_cloud.py | 19 |
9 files changed, 193 insertions, 27 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index edde3a9e4..6bb19e7b3 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -472,7 +472,7 @@ class Executor(wsgi.Application): except exception.InstanceNotFound as ex: LOG.info(_('InstanceNotFound raised: %s'), unicode(ex), context=context) - ec2_id = ec2utils.id_to_ec2_id(ex.kwargs['instance_id']) + ec2_id = ec2utils.id_to_ec2_inst_id(ex.kwargs['instance_id']) message = ex.message % {'instance_id': ec2_id} return ec2_error(req, request_id, type(ex).__name__, message) except exception.VolumeNotFound as ex: diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 75f28d510..126df0f0a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -690,8 +690,7 @@ class CloudController(object): instance = db.instance_get_by_uuid(context.elevated(), instance_uuid) - instance_id = instance['id'] - instance_ec2_id = ec2utils.id_to_ec2_id(instance_id) + instance_ec2_id = ec2utils.id_to_ec2_inst_id(instance_uuid) instance_data = '%s[%s]' % (instance_ec2_id, instance['host']) v = {} @@ -778,7 +777,7 @@ class CloudController(object): volume = self.volume_api.get(context, volume_id) return {'attachTime': volume['attach_time'], 'device': volume['mountpoint'], - 'instanceId': ec2utils.id_to_ec2_id(instance_id), + 'instanceId': ec2utils.id_to_ec2_inst_id(instance_id), 'requestId': context.request_id, 'status': volume['attach_status'], 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} @@ -797,7 +796,7 @@ class CloudController(object): return {'attachTime': volume['attach_time'], 'device': volume['mountpoint'], - 'instanceId': ec2utils.id_to_ec2_id(instance['id']), + 'instanceId': ec2utils.id_to_ec2_inst_id(instance['uuid']), 'requestId': context.request_id, 'status': volume['attach_status'], 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} @@ -987,9 +986,10 @@ class CloudController(object): if instance_id: instances = [] for ec2_id in instance_id: - internal_id = ec2utils.ec2_id_to_id(ec2_id) try: - instance = self.compute_api.get(context, internal_id) + instance_uuid = ec2utils.ec2_inst_id_to_uuid(context, + ec2_id) + instance = self.compute_api.get(context, instance_uuid) except exception.NotFound: continue instances.append(instance) @@ -1007,8 +1007,8 @@ class CloudController(object): if instance['image_ref'] == str(FLAGS.vpn_image_id): continue i = {} - instance_id = instance['id'] - ec2_id = ec2utils.id_to_ec2_id(instance_id) + instance_uuid = instance['uuid'] + ec2_id = ec2utils.id_to_ec2_inst_id(instance_uuid) i['instanceId'] = ec2_id image_uuid = instance['image_ref'] i['imageId'] = ec2utils.glance_id_to_ec2_id(context, image_uuid) @@ -1081,7 +1081,7 @@ class CloudController(object): fixed_id = floating_ip['fixed_ip_id'] fixed = self.network_api.get_fixed_ip(context, fixed_id) if fixed['instance_id'] is not None: - ec2_id = ec2utils.id_to_ec2_id(fixed['instance_id']) + ec2_id = ec2utils.id_to_ec2_inst_id(fixed['instance_id']) address = {'public_ip': floating_ip['address'], 'instance_id': ec2_id} if context.is_admin: diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 1735f8b3e..608628209 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -81,13 +81,6 @@ def ec2_id_to_id(ec2_id): raise exception.InvalidEc2Id(ec2_id=ec2_id) -def ec2_id_to_uuid(context, ec2_id): - """Convert an ec2 ID into an instance UUID""" - instance_id = ec2_id_to_id(ec2_id) - instance = db.instance_get(context, instance_id) - return instance['uuid'] - - def image_ec2_id(image_id, image_type='ami'): """Returns image ec2_id using id and three letter type.""" template = image_type + '-%08x' @@ -134,6 +127,26 @@ def id_to_ec2_id(instance_id, template='i-%08x'): return template % int(instance_id) +def id_to_ec2_inst_id(instance_id): + """Get or create an ec2 instance ID (i-[base 16 number]) from uuid.""" + if utils.is_uuid_like(instance_id): + ctxt = context.get_admin_context() + int_id = get_int_id_from_instance_uuid(ctxt, instance_id) + return id_to_ec2_id(int_id) + else: + return id_to_ec2_id(instance_id) + + +def ec2_inst_id_to_uuid(context, ec2_id): + """"Convert an instance id to uuid.""" + int_id = ec2_id_to_id(ec2_id) + return get_instance_uuid_from_int_id(context, int_id) + + +def get_instance_uuid_from_int_id(context, int_id): + return db.get_instance_uuid_by_ec2_id(context, int_id) + + def id_to_ec2_snap_id(snapshot_id): """Get or create an ec2 volume ID (vol-[base 16 number]) from uuid.""" if utils.is_uuid_like(snapshot_id): @@ -163,6 +176,15 @@ def ec2_vol_id_to_uuid(ec2_id): return get_volume_uuid_from_int_id(ctxt, int_id) +def get_int_id_from_instance_uuid(context, instance_uuid): + if instance_uuid is None: + return + try: + return db.get_ec2_instance_id_by_uuid(context, instance_uuid) + except exception.NotFound: + return db.ec2_instance_create(context, instance_uuid)['id'] + + def get_int_id_from_volume_uuid(context, volume_uuid): if volume_uuid is None: return diff --git a/nova/api/metadata/base.py b/nova/api/metadata/base.py index 1b0ac441b..06e290917 100644 --- a/nova/api/metadata/base.py +++ b/nova/api/metadata/base.py @@ -88,7 +88,8 @@ class InstanceMetadata(): self.ec2_ids = {} - self.ec2_ids['instance-id'] = ec2utils.id_to_ec2_id(instance['id']) + self.ec2_ids['instance-id'] = ec2utils.id_to_ec2_inst_id( + instance['id']) self.ec2_ids['ami-id'] = ec2utils.glance_id_to_ec2_id(ctxt, instance['image_ref']) diff --git a/nova/db/api.py b/nova/db/api.py index 989864a2b..695c083c9 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1930,3 +1930,21 @@ def instance_fault_create(context, values): def instance_fault_get_by_instance_uuids(context, instance_uuids): """Get all instance faults for the provided instance_uuids.""" return IMPL.instance_fault_get_by_instance_uuids(context, instance_uuids) + + +#################### + + +def get_ec2_instance_id_by_uuid(context, instance_id): + """Get ec2 id through uuid from instance_id_mappings table""" + return IMPL.get_ec2_instance_id_by_uuid(context, instance_id) + + +def get_instance_uuid_by_ec2_id(context, instance_id): + """Get uuid through ec2 id from instance_id_mappings table""" + return IMPL.get_instance_uuid_by_ec2_id(context, instance_id) + + +def ec2_instance_create(context, instance_ec2_id): + """Create the ec2 id to instance uuid mapping on demand""" + return IMPL.ec2_instance_create(context, instance_ec2_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 76578e061..bc0ba5307 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1367,6 +1367,9 @@ def instance_create(context, values): # exists in the ref when we return. Fixes lazy loading issues. instance_ref.instance_type + # create the instance uuid to ec2_id mapping entry for instance + ec2_instance_create(context, instance_ref['uuid']) + return instance_ref @@ -5163,3 +5166,50 @@ def instance_fault_get_by_instance_uuids(context, instance_uuids): output[row['instance_uuid']].append(data) return output + + +################## + + +@require_context +def ec2_instance_create(context, instance_uuid, id=None): + """Create ec2 compatable instance by provided uuid""" + ec2_instance_ref = models.InstanceIdMapping() + ec2_instance_ref.update({'uuid': instance_uuid}) + if id is not None: + ec2_instance_ref.update({'id': id}) + + ec2_instance_ref.save() + + return ec2_instance_ref + + +@require_context +def get_ec2_instance_id_by_uuid(context, instance_id, session=None): + result = _ec2_instance_get_query(context, + session=session).\ + filter_by(uuid=instance_id).\ + first() + + if not result: + raise exception.InstanceNotFound(uuid=instance_id) + + return result['id'] + + +@require_context +def get_instance_uuid_by_ec2_id(context, instance_id, session=None): + result = _ec2_instance_get_query(context, + session=session).\ + filter_by(id=instance_id).\ + first() + + if not result: + raise exception.InstanceNotFound(id=instance_id) + + return result['uuid'] + + +@require_context +def _ec2_instance_get_query(context, session=None): + return model_query(context, models.InstanceIdMapping, session=session) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/107_add_instance_id_mappings.py b/nova/db/sqlalchemy/migrate_repo/versions/107_add_instance_id_mappings.py new file mode 100644 index 000000000..94adbd89b --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/107_add_instance_id_mappings.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2012 SINA Corp. +# 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 sqlalchemy import Boolean, Column, DateTime, Integer +from sqlalchemy import Index, MetaData, String, Table +from nova import log as logging + +LOG = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + # create new table + instance_id_mappings = Table('instance_id_mappings', meta, + Column('created_at', DateTime(timezone=False)), + Column('updated_at', DateTime(timezone=False)), + Column('deleted_at', DateTime(timezone=False)), + Column('deleted', + Boolean(create_constraint=True, name=None)), + Column('id', Integer(), + primary_key=True, + nullable=False, + autoincrement=True), + Column('uuid', String(36), index=True, nullable=False)) + try: + instance_id_mappings.create() + except Exception: + LOG.exception("Exception while creating table 'instance_id_mappings'") + meta.drop_all(tables=[instance_id_mappings]) + raise + + if migrate_engine.name == "mysql": + migrate_engine.execute("ALTER TABLE instance_id_mappings " + "Engine=InnoDB") + + instances = Table('instances', meta, autoload=True) + instance_id_mappings = Table('instance_id_mappings', meta, autoload=True) + + instance_list = list(instances.select().execute()) + for instance in instance_list: + instance_id = instance['id'] + uuid = instance['uuid'] + row = instance_id_mappings.insert() + row.execute({'id': instance_id, 'uuid': uuid}) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + instance_id_mappings = Table('instance_id_mappings', meta, autoload=True) + instance_id_mappings.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f63004386..335989135 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -1036,3 +1036,10 @@ class InstanceFault(BASE, NovaBase): code = Column(Integer(), nullable=False) message = Column(String(255)) details = Column(Text) + + +class InstanceIdMapping(BASE, NovaBase): + """Compatability layer for the EC2 instance service""" + __tablename__ = 'instance_id_mappings' + id = Column(Integer, primary_key=True, nullable=False, autoincrement=True) + uuid = Column(String(36), nullable=False) diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py index 90724977a..0603cac62 100644 --- a/nova/tests/api/ec2/test_cloud.py +++ b/nova/tests/api/ec2/test_cloud.py @@ -244,7 +244,7 @@ class CloudTestCase(test.TestCase): project_id=project_id) fixed_ips = nw_info.fixed_ips() - ec2_id = ec2utils.id_to_ec2_id(inst['id']) + ec2_id = ec2utils.id_to_ec2_inst_id(inst['id']) self.stubs.Set(ec2utils, 'get_ip_info_for_instance', lambda *args: {'fixed_ips': ['10.0.0.1'], @@ -760,7 +760,7 @@ class CloudTestCase(test.TestCase): self.assertEqual(len(result['instancesSet']), 2) # Now try filtering. - instance_id = ec2utils.id_to_ec2_id(inst2['id']) + instance_id = ec2utils.id_to_ec2_inst_id(inst2['uuid']) result = self.cloud.describe_instances(self.context, instance_id=[instance_id]) result = result['reservationSet'][0] @@ -848,7 +848,7 @@ class CloudTestCase(test.TestCase): 'power_state': power_state_, 'vm_state': vm_state_}) inst = db.instance_create(self.context, values) - instance_id = ec2utils.id_to_ec2_id(inst['id']) + instance_id = ec2utils.id_to_ec2_inst_id(inst['uuid']) result = self.cloud.describe_instances(self.context, instance_id=[instance_id]) result = result['reservationSet'][0] @@ -886,7 +886,7 @@ class CloudTestCase(test.TestCase): result = result['reservationSet'][0] self.assertEqual(len(result['instancesSet']), 1) instance = result['instancesSet'][0] - instance_id = ec2utils.id_to_ec2_id(inst1['id']) + instance_id = ec2utils.id_to_ec2_inst_id(inst1['uuid']) self.assertEqual(instance['instanceId'], instance_id) self.assertEqual(instance['publicDnsName'], '1.2.3.4') self.assertEqual(instance['ipAddress'], '1.2.3.4') @@ -916,7 +916,7 @@ class CloudTestCase(test.TestCase): self.assertEqual(len(result['reservationSet']), 1) result1 = result['reservationSet'][0]['instancesSet'] self.assertEqual(result1[0]['instanceId'], - ec2utils.id_to_ec2_id(inst2.id)) + ec2utils.id_to_ec2_inst_id(inst2['uuid'])) def test_describe_instances_with_image_deleted(self): image_uuid = 'aebef54a-ed67-4d10-912f-14455edce176' @@ -1083,7 +1083,7 @@ class CloudTestCase(test.TestCase): self._tearDownBlockDeviceMapping(inst1, inst2, volumes) def _assertInstance(self, instance_id): - ec2_instance_id = ec2utils.id_to_ec2_id(instance_id) + ec2_instance_id = ec2utils.id_to_ec2_inst_id(instance_id) result = self.cloud.describe_instances(self.context, instance_id=[ec2_instance_id]) result = result['reservationSet'][0] @@ -1109,12 +1109,12 @@ class CloudTestCase(test.TestCase): """ (inst1, inst2, volumes) = self._setUpBlockDeviceMapping() - result = self._assertInstance(inst1['id']) + result = self._assertInstance(inst1['uuid']) self.assertSubDictMatch(self._expected_instance_bdm1, result) self._assertEqualBlockDeviceMapping( self._expected_block_device_mapping0, result['blockDeviceMapping']) - result = self._assertInstance(inst2['id']) + result = self._assertInstance(inst2['uuid']) self.assertSubDictMatch(self._expected_instance_bdm2, result) self._tearDownBlockDeviceMapping(inst1, inst2, volumes) @@ -1875,7 +1875,8 @@ class CloudTestCase(test.TestCase): 'max_count': 1, } instance_id = self._run_instance(**kwargs) - internal_uuid = ec2utils.ec2_id_to_uuid(self.context, instance_id) + internal_uuid = db.get_instance_uuid_by_ec2_id(self.context, + ec2utils.ec2_id_to_id(instance_id)) instance = db.instance_update(self.context, internal_uuid, {'disable_terminate': True}) |