Make the metadata paths use conductor

This patch makes the metadata service use conductor instead of hitting the
database directly.

It adds a get_ec2_ids() method to conductor, which encapsulates a bunch of
very small id translation database queries. It also does the AZ lookup
via conductor.

Fixes bug 1120402

Change-Id: Iceef753b1451ff53e29dfbf97a696b952b47d5a3
This commit is contained in:
Dan Smith 2013-02-09 10:32:49 -05:00
parent e7bc52d3f2
commit 91db10a0fd
9 changed files with 144 additions and 40 deletions

View File

@ -115,10 +115,10 @@ def get_ip_info_for_instance(context, instance):
return get_ip_info_for_instance_from_nw_info(nw_info)
def get_availability_zone_by_host(services, host):
def get_availability_zone_by_host(services, host, conductor_api=None):
if len(services) > 0:
return availability_zones.get_host_availability_zone(
context.get_admin_context(), host)
context.get_admin_context(), host, conductor_api)
return 'unknown zone'

View File

@ -26,8 +26,8 @@ import posixpath
from nova.api.ec2 import ec2utils
from nova.api.metadata import password
from nova import block_device
from nova import conductor
from nova import context
from nova import db
from nova import network
from nova.openstack.common import cfg
from nova.openstack.common import timeutils
@ -83,7 +83,8 @@ class InvalidMetadataPath(Exception):
class InstanceMetadata():
"""Instance metadata."""
def __init__(self, instance, address=None, content=[], extra_md=None):
def __init__(self, instance, address=None, content=[], extra_md=None,
conductor_api=None):
"""Creation of this object should basically cover all time consuming
collection. Methods after that should not cause time delays due to
network operations or lengthy cpu operations.
@ -95,39 +96,32 @@ class InstanceMetadata():
self.instance = instance
self.extra_md = extra_md
if conductor_api:
self.conductor_api = conductor_api
else:
self.conductor_api = conductor.API()
ctxt = context.get_admin_context()
services = db.service_get_all_by_host(ctxt.elevated(),
instance['host'])
capi = self.conductor_api
services = capi.service_get_all_by_host(ctxt.elevated(),
instance['host'])
self.availability_zone = ec2utils.get_availability_zone_by_host(
services, instance['host'])
services, instance['host'], capi)
self.ip_info = ec2utils.get_ip_info_for_instance(ctxt, instance)
self.security_groups = db.security_group_get_by_instance(ctxt,
instance['id'])
self.security_groups = capi.security_group_get_by_instance(ctxt,
instance)
self.mappings = _format_instance_mapping(ctxt, instance)
self.mappings = _format_instance_mapping(capi, ctxt, instance)
if instance.get('user_data', None) is not None:
self.userdata_raw = base64.b64decode(instance['user_data'])
else:
self.userdata_raw = None
self.ec2_ids = {}
self.ec2_ids['instance-id'] = ec2utils.id_to_ec2_inst_id(
instance['uuid'])
self.ec2_ids['ami-id'] = ec2utils.glance_id_to_ec2_id(ctxt,
instance['image_ref'])
for image_type in ['kernel', 'ramdisk']:
if self.instance.get('%s_id' % image_type):
image_id = self.instance['%s_id' % image_type]
ec2_image_type = ec2utils.image_type(image_type)
ec2_id = ec2utils.glance_id_to_ec2_id(ctxt, image_id,
ec2_image_type)
self.ec2_ids['%s-id' % image_type] = ec2_id
self.ec2_ids = capi.get_ec2_ids(ctxt, instance)
self.address = address
@ -404,23 +398,26 @@ class InstanceMetadata():
yield ('%s/%s/%s' % ("openstack", CONTENT_DIR, cid), content)
def get_metadata_by_address(address):
def get_metadata_by_address(conductor_api, address):
ctxt = context.get_admin_context()
fixed_ip = network.API().get_fixed_ip_by_address(ctxt, address)
return get_metadata_by_instance_id(fixed_ip['instance_uuid'],
return get_metadata_by_instance_id(conductor_api,
fixed_ip['instance_uuid'],
address,
ctxt)
def get_metadata_by_instance_id(instance_id, address, ctxt=None):
def get_metadata_by_instance_id(conductor_api, instance_id, address,
ctxt=None):
ctxt = ctxt or context.get_admin_context()
instance = db.instance_get_by_uuid(ctxt, instance_id)
instance = conductor_api.instance_get_by_uuid(ctxt, instance_id)
return InstanceMetadata(instance, address)
def _format_instance_mapping(ctxt, instance):
bdms = db.block_device_mapping_get_all_by_instance(ctxt, instance['uuid'])
def _format_instance_mapping(conductor_api, ctxt, instance):
bdms = conductor_api.block_device_mapping_get_all_by_instance(
ctxt, instance)
return block_device.instance_block_mapping(instance, bdms)

View File

@ -26,6 +26,7 @@ import webob.exc
from nova.api.metadata import base
from nova.common import memorycache
from nova import conductor
from nova import exception
from nova.openstack.common import cfg
from nova.openstack.common import log as logging
@ -58,6 +59,7 @@ class MetadataRequestHandler(wsgi.Application):
def __init__(self):
self._cache = memorycache.get_client()
self.conductor_api = conductor.API()
def get_metadata_by_remote_address(self, address):
if not address:
@ -69,7 +71,7 @@ class MetadataRequestHandler(wsgi.Application):
return data
try:
data = base.get_metadata_by_address(address)
data = base.get_metadata_by_address(self.conductor_api, address)
except exception.NotFound:
return None
@ -84,7 +86,8 @@ class MetadataRequestHandler(wsgi.Application):
return data
try:
data = base.get_metadata_by_instance_id(instance_id, address)
data = base.get_metadata_by_instance_id(self.conductor_api,
instance_id, address)
except exception.NotFound:
return None

View File

@ -52,9 +52,13 @@ def set_availability_zones(context, services):
return services
def get_host_availability_zone(context, host):
metadata = db.aggregate_metadata_get_by_host(
context, host, key='availability_zone')
def get_host_availability_zone(context, host, conductor_api=None):
if conductor_api:
metadata = conductor_api.aggregate_metadata_get_by_host(
context, host, key='availability_zone')
else:
metadata = db.aggregate_metadata_get_by_host(
context, host, key='availability_zone')
if 'availability_zone' in metadata:
return list(metadata['availability_zone'])[0]
else:

View File

@ -154,6 +154,12 @@ class LocalAPI(object):
aggregate,
key)
def aggregate_metadata_get_by_host(self, context, host,
key='availability_zone'):
return self._manager.aggregate_metadata_get_by_host(context,
host,
key)
def bw_usage_get(self, context, uuid, start_period, mac):
return self._manager.bw_usage_update(context, uuid, mac, start_period)
@ -319,6 +325,9 @@ class LocalAPI(object):
def quota_rollback(self, context, reservations):
return self._manager.quota_rollback(context, reservations)
def get_ec2_ids(self, context, instance):
return self._manager.get_ec2_ids(context, instance)
class API(object):
"""Conductor API that does updates via RPC to the ConductorManager."""
@ -463,6 +472,12 @@ class API(object):
aggregate,
key)
def aggregate_metadata_get_by_host(self, context, host,
key='availability_zone'):
return self.conductor_rpcapi.aggregate_metadata_get_by_host(context,
host,
key)
def bw_usage_get(self, context, uuid, start_period, mac):
return self.conductor_rpcapi.bw_usage_update(context, uuid, mac,
start_period)
@ -636,3 +651,6 @@ class API(object):
def quota_rollback(self, context, reservations):
return self.conductor_rpcapi.quota_rollback(context, reservations)
def get_ec2_ids(self, context, instance):
return self.conductor_rpcapi.get_ec2_ids(context, instance)

View File

@ -14,6 +14,7 @@
"""Handles database requests from other nova services."""
from nova.api.ec2 import ec2utils
from nova.compute import api as compute_api
from nova.compute import utils as compute_utils
from nova import exception
@ -47,7 +48,7 @@ datetime_fields = ['launched_at', 'terminated_at']
class ConductorManager(manager.SchedulerDependentManager):
"""Mission: TBD."""
RPC_API_VERSION = '1.41'
RPC_API_VERSION = '1.42'
def __init__(self, *args, **kwargs):
super(ConductorManager, self).__init__(service_name='conductor',
@ -175,6 +176,11 @@ class ConductorManager(manager.SchedulerDependentManager):
self.db.aggregate_metadata_delete(context.elevated(),
aggregate['id'], key)
def aggregate_metadata_get_by_host(self, context, host,
key='availability_zone'):
result = self.db.aggregate_metadata_get_by_host(context, host, key)
return jsonutils.to_primitive(result)
def bw_usage_update(self, context, uuid, mac, start_period,
bw_in=None, bw_out=None,
last_ctr_in=None, last_ctr_out=None,
@ -384,3 +390,19 @@ class ConductorManager(manager.SchedulerDependentManager):
def quota_rollback(self, context, reservations):
quota.QUOTAS.rollback(context, reservations)
def get_ec2_ids(self, context, instance):
ec2_ids = {}
ec2_ids['instance-id'] = ec2utils.id_to_ec2_inst_id(instance['uuid'])
ec2_ids['ami-id'] = ec2utils.glance_id_to_ec2_id(context,
instance['image_ref'])
for image_type in ['kernel', 'ramdisk']:
if '%s_id' % image_type in instance:
image_id = instance['%s_id' % image_type]
ec2_image_type = ec2utils.image_type(image_type)
ec2_id = ec2utils.glance_id_to_ec2_id(context, image_id,
ec2_image_type)
ec2_ids['%s-id' % image_type] = ec2_id
return ec2_ids

View File

@ -78,6 +78,7 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
1.41 - Added fixed_ip_get_by_instance, network_get,
instance_floating_address_get_all, quota_commit,
quota_rollback
1.42 - Added get_ec2_ids, aggregate_metadata_get_by_host
"""
BASE_RPC_API_VERSION = '1.0'
@ -176,6 +177,11 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
key=key)
return self.call(context, msg, version='1.7')
def aggregate_metadata_get_by_host(self, context, host, key):
msg = self.make_msg('aggregate_metadata_get_by_host', host=host,
key=key)
return self.call(context, msg, version='1.42')
def bw_usage_update(self, context, uuid, mac, start_period,
bw_in=None, bw_out=None,
last_ctr_in=None, last_ctr_out=None,
@ -409,3 +415,8 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
reservations_p = jsonutils.to_primitive(reservations)
msg = self.make_msg('quota_rollback', reservations=reservations_p)
return self.call(context, msg, version='1.41')
def get_ec2_ids(self, context, instance):
instance_p = jsonutils.to_primitive(instance)
msg = self.make_msg('get_ec2_ids', instance=instance_p)
return self.call(context, msg, version='1.42')

View File

@ -16,6 +16,7 @@
import mox
from nova.api.ec2 import ec2utils
from nova.compute import instance_types
from nova.compute import utils as compute_utils
from nova.compute import vm_states
@ -241,6 +242,15 @@ class _BaseTestCase(object):
aggregate,
'fake')
def test_aggregate_metadata_get_by_host(self):
self.mox.StubOutWithMock(db, 'aggregate_metadata_get_by_host')
db.aggregate_metadata_get_by_host(self.context, 'host',
'key').AndReturn('result')
self.mox.ReplayAll()
result = self.conductor.aggregate_metadata_get_by_host(self.context,
'host', 'key')
self.assertEqual(result, 'result')
def test_bw_usage_update(self):
self.mox.StubOutWithMock(db, 'bw_usage_update')
self.mox.StubOutWithMock(db, 'bw_usage_get')
@ -537,6 +547,39 @@ class _BaseTestCase(object):
self.mox.ReplayAll()
self.conductor.quota_rollback(self.context, 'reservations')
def test_get_ec2_ids(self):
expected = {
'instance-id': 'ec2-inst-id',
'ami-id': 'ec2-ami-id',
'kernel-id': 'ami-kernel-ec2-kernelid',
'ramdisk-id': 'ami-ramdisk-ec2-ramdiskid',
}
inst = {
'uuid': 'fake-uuid',
'kernel_id': 'ec2-kernelid',
'ramdisk_id': 'ec2-ramdiskid',
'image_ref': 'fake-image',
}
self.mox.StubOutWithMock(ec2utils, 'id_to_ec2_inst_id')
self.mox.StubOutWithMock(ec2utils, 'glance_id_to_ec2_id')
self.mox.StubOutWithMock(ec2utils, 'image_type')
ec2utils.id_to_ec2_inst_id(inst['uuid']).AndReturn(
expected['instance-id'])
ec2utils.glance_id_to_ec2_id(self.context,
inst['image_ref']).AndReturn(
expected['ami-id'])
for image_type in ['kernel', 'ramdisk']:
image_id = inst['%s_id' % image_type]
ec2utils.image_type(image_type).AndReturn('ami-' + image_type)
ec2utils.glance_id_to_ec2_id(self.context, image_id,
'ami-' + image_type).AndReturn(
'ami-%s-ec2-%sid' % (image_type, image_type))
self.mox.ReplayAll()
result = self.conductor.get_ec2_ids(self.context, inst)
self.assertEqual(result, expected)
class ConductorTestCase(_BaseTestCase, test.TestCase):
"""Conductor Manager Tests."""

View File

@ -31,6 +31,7 @@ from nova.api.metadata import base
from nova.api.metadata import handler
from nova.api.metadata import password
from nova import block_device
from nova.conductor import api as conductor_api
from nova import db
from nova.db.sqlalchemy import api
from nova import exception
@ -118,6 +119,7 @@ class MetadataTestCase(test.TestCase):
def setUp(self):
super(MetadataTestCase, self).setUp()
self.instance = INSTANCES[0]
self.flags(use_local=True, group='conductor')
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
spectacular=True)
@ -191,10 +193,11 @@ class MetadataTestCase(test.TestCase):
'swap': '/dev/sdc',
'ebs0': '/dev/sdh'}
self.assertEqual(base._format_instance_mapping(ctxt, instance_ref0),
block_device._DEFAULT_MAPPINGS)
self.assertEqual(base._format_instance_mapping(ctxt, instance_ref1),
expected)
capi = conductor_api.LocalAPI()
self.assertEqual(base._format_instance_mapping(capi, ctxt,
instance_ref0), block_device._DEFAULT_MAPPINGS)
self.assertEqual(base._format_instance_mapping(capi, ctxt,
instance_ref1), expected)
def test_pubkey(self):
md = fake_InstanceMetadata(self.stubs, copy.copy(self.instance))
@ -247,6 +250,7 @@ class OpenStackMetadataTestCase(test.TestCase):
def setUp(self):
super(OpenStackMetadataTestCase, self).setUp()
self.instance = INSTANCES[0]
self.flags(use_local=True, group='conductor')
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
spectacular=True)
@ -382,6 +386,7 @@ class MetadataHandlerTestCase(test.TestCase):
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
spectacular=True)
self.instance = INSTANCES[0]
self.flags(use_local=True, group='conductor')
self.mdinst = fake_InstanceMetadata(self.stubs, self.instance,
address=None, sgroups=None)
@ -547,6 +552,7 @@ class MetadataPasswordTestCase(test.TestCase):
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs,
spectacular=True)
self.instance = copy.copy(INSTANCES[0])
self.flags(use_local=True, group='conductor')
self.mdinst = fake_InstanceMetadata(self.stubs, self.instance,
address=None, sgroups=None)
self.flags(use_local=True, group='conductor')