diff options
-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}) |