add metadata support for overlapping networks
implements blueprint metadata-overlapping-networks This is the Nova side of the changes required to support Metadata service on overlapping Quantum networks. This change is BACKWARDS COMPATIBLE and does not require any new configuration for Nova only deployments. Change-Id: I4dacd565e43e136e3faeb95184a4a2f7188415cd
This commit is contained in:
parent
210afea928
commit
4363a9a11b
@ -384,7 +384,14 @@ def get_metadata_by_address(address):
|
||||
ctxt = context.get_admin_context()
|
||||
fixed_ip = network.API().get_fixed_ip_by_address(ctxt, address)
|
||||
|
||||
instance = db.instance_get_by_uuid(ctxt, fixed_ip['instance_uuid'])
|
||||
return get_metadata_by_instance_id(fixed_ip['instance_uuid'],
|
||||
address,
|
||||
ctxt)
|
||||
|
||||
|
||||
def get_metadata_by_instance_id(instance_id, address, ctxt=None):
|
||||
ctxt = ctxt or context.get_admin_context()
|
||||
instance = db.instance_get_by_uuid(ctxt, instance_id)
|
||||
return InstanceMetadata(instance, address)
|
||||
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
# under the License.
|
||||
|
||||
"""Metadata request handler."""
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
|
||||
import webob.dec
|
||||
@ -28,10 +30,26 @@ from nova.openstack.common import cfg
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import wsgi
|
||||
|
||||
CACHE_EXPIRATION = 15 # in seconds
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('memcached_servers', 'nova.config')
|
||||
CONF.import_opt('use_forwarded_for', 'nova.api.auth')
|
||||
|
||||
metadata_proxy_opts = [
|
||||
cfg.BoolOpt(
|
||||
'service_quantum_metadata_proxy',
|
||||
default=False,
|
||||
help='Set flag to indicate Quantum will proxy metadata requests and '
|
||||
'resolve instance ids.'),
|
||||
cfg.StrOpt(
|
||||
'quantum_metadata_proxy_shared_secret',
|
||||
default='',
|
||||
help='Shared secret to validate proxies Quantum metadata requests')
|
||||
]
|
||||
|
||||
CONF.register_opts(metadata_proxy_opts)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
if CONF.memcached_servers:
|
||||
@ -46,7 +64,7 @@ class MetadataRequestHandler(wsgi.Application):
|
||||
def __init__(self):
|
||||
self._cache = memcache.Client(CONF.memcached_servers, debug=0)
|
||||
|
||||
def get_metadata(self, address):
|
||||
def get_metadata_by_remote_address(self, address):
|
||||
if not address:
|
||||
raise exception.FixedIpNotFoundForAddress(address=address)
|
||||
|
||||
@ -60,30 +78,41 @@ class MetadataRequestHandler(wsgi.Application):
|
||||
except exception.NotFound:
|
||||
return None
|
||||
|
||||
self._cache.set(cache_key, data, 15)
|
||||
self._cache.set(cache_key, data, CACHE_EXPIRATION)
|
||||
|
||||
return data
|
||||
|
||||
def get_metadata_by_instance_id(self, instance_id, address):
|
||||
cache_key = 'metadata-%s' % instance_id
|
||||
data = self._cache.get(cache_key)
|
||||
if data:
|
||||
return data
|
||||
|
||||
try:
|
||||
data = base.get_metadata_by_instance_id(instance_id, address)
|
||||
except exception.NotFound:
|
||||
return None
|
||||
|
||||
self._cache.set(cache_key, data, CACHE_EXPIRATION)
|
||||
|
||||
return data
|
||||
|
||||
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||
def __call__(self, req):
|
||||
remote_address = req.remote_addr
|
||||
if CONF.use_forwarded_for:
|
||||
remote_address = req.headers.get('X-Forwarded-For', remote_address)
|
||||
|
||||
if os.path.normpath("/" + req.path_info) == "/":
|
||||
return(base.ec2_md_print(base.VERSIONS + ["latest"]))
|
||||
|
||||
try:
|
||||
meta_data = self.get_metadata(remote_address)
|
||||
except Exception:
|
||||
LOG.exception(_('Failed to get metadata for ip: %s'),
|
||||
remote_address)
|
||||
msg = _('An unknown error has occurred. '
|
||||
'Please try your request again.')
|
||||
exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg))
|
||||
return exc
|
||||
if CONF.service_quantum_metadata_proxy:
|
||||
meta_data = self._handle_instance_id_request(req)
|
||||
else:
|
||||
if req.headers.get('X-Instance-ID'):
|
||||
LOG.warn(
|
||||
_("X-Instance-ID present in request headers. The "
|
||||
"'service_quantum_metadata_proxy' option must be enabled"
|
||||
" to process this header."))
|
||||
meta_data = self._handle_remote_ip_request(req)
|
||||
|
||||
if meta_data is None:
|
||||
LOG.error(_('Failed to get metadata for ip: %s'), remote_address)
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
try:
|
||||
@ -92,3 +121,70 @@ class MetadataRequestHandler(wsgi.Application):
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
return base.ec2_md_print(data)
|
||||
|
||||
def _handle_remote_ip_request(self, req):
|
||||
remote_address = req.remote_addr
|
||||
if CONF.use_forwarded_for:
|
||||
remote_address = req.headers.get('X-Forwarded-For', remote_address)
|
||||
|
||||
try:
|
||||
meta_data = self.get_metadata_by_remote_address(remote_address)
|
||||
except Exception:
|
||||
LOG.exception(_('Failed to get metadata for ip: %s'),
|
||||
remote_address)
|
||||
msg = _('An unknown error has occurred. '
|
||||
'Please try your request again.')
|
||||
raise webob.exc.HTTPInternalServerError(explanation=unicode(msg))
|
||||
|
||||
if meta_data is None:
|
||||
LOG.error(_('Failed to get metadata for ip: %s'), remote_address)
|
||||
|
||||
return meta_data
|
||||
|
||||
def _handle_instance_id_request(self, req):
|
||||
instance_id = req.headers.get('X-Instance-ID')
|
||||
signature = req.headers.get('X-Instance-ID-Signature')
|
||||
remote_address = req.remote_addr
|
||||
|
||||
# Ensure that only one header was passed
|
||||
|
||||
if instance_id is None:
|
||||
msg = _('X-Instance-ID header is missing from request.')
|
||||
elif not isinstance(instance_id, basestring):
|
||||
msg = _('Multiple X-Instance-ID headers found within request.')
|
||||
else:
|
||||
msg = None
|
||||
|
||||
if msg:
|
||||
raise webob.exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
expected_signature = hmac.new(
|
||||
CONF.quantum_metadata_proxy_shared_secret,
|
||||
instance_id,
|
||||
hashlib.sha256).hexdigest()
|
||||
|
||||
if expected_signature != signature:
|
||||
if instance_id:
|
||||
w = _('X-Instance-ID-Signature: %(signature)s does not match '
|
||||
'the expected value: %(expected_signature)s for id: '
|
||||
'%(instance_id)s. Request From: %(remote_address)s')
|
||||
LOG.warn(w % locals())
|
||||
|
||||
msg = _('Invalid proxy request signature.')
|
||||
raise webob.exc.HTTPForbidden(explanation=msg)
|
||||
|
||||
try:
|
||||
meta_data = self.get_metadata_by_instance_id(instance_id,
|
||||
remote_address)
|
||||
except Exception:
|
||||
LOG.exception(_('Failed to get metadata for instance id: %s'),
|
||||
instance_id)
|
||||
msg = _('An unknown error has occurred. '
|
||||
'Please try your request again.')
|
||||
raise webob.exc.HTTPInternalServerError(explanation=unicode(msg))
|
||||
|
||||
if meta_data is None:
|
||||
LOG.error(_('Failed to get metadata for instance id: %s'),
|
||||
instance_id)
|
||||
|
||||
return meta_data
|
||||
|
@ -83,18 +83,23 @@ def fake_InstanceMetadata(stubs, inst_data, address=None,
|
||||
|
||||
|
||||
def fake_request(stubs, mdinst, relpath, address="127.0.0.1",
|
||||
fake_get_metadata=None, headers=None):
|
||||
fake_get_metadata=None, headers=None,
|
||||
fake_get_metadata_by_instance_id=None):
|
||||
|
||||
def get_metadata(address):
|
||||
def get_metadata_by_remote_address(address):
|
||||
return mdinst
|
||||
|
||||
app = handler.MetadataRequestHandler()
|
||||
|
||||
if fake_get_metadata is None:
|
||||
fake_get_metadata = get_metadata
|
||||
fake_get_metadata = get_metadata_by_remote_address
|
||||
|
||||
if stubs:
|
||||
stubs.Set(app, 'get_metadata', fake_get_metadata)
|
||||
stubs.Set(app, 'get_metadata_by_remote_address', fake_get_metadata)
|
||||
|
||||
if fake_get_metadata_by_instance_id:
|
||||
stubs.Set(app, 'get_metadata_by_instance_id',
|
||||
fake_get_metadata_by_instance_id)
|
||||
|
||||
request = webob.Request.blank(relpath)
|
||||
request.remote_addr = address
|
||||
@ -405,3 +410,62 @@ class MetadataHandlerTestCase(test.TestCase):
|
||||
fake_get_metadata=fake_get_metadata,
|
||||
headers=None)
|
||||
self.assertEqual(response.status_int, 500)
|
||||
|
||||
def test_user_data_with_quantum_instance_id(self):
|
||||
expected_instance_id = 'a-b-c-d'
|
||||
|
||||
def fake_get_metadata(instance_id, remote_address):
|
||||
if instance_id == expected_instance_id:
|
||||
return self.mdinst
|
||||
else:
|
||||
# raise the exception to aid with 500 response code test
|
||||
raise Exception("Expected instance_id of %s, got %s" %
|
||||
(expected_instance_id, instance_id))
|
||||
|
||||
signed = ('d98d0dd53b026a24df2c06b464ffa5da'
|
||||
'db922ae41af7bd3ecc3cae75aef65771')
|
||||
|
||||
# try a request with service disabled
|
||||
response = fake_request(
|
||||
self.stubs, self.mdinst,
|
||||
relpath="/2009-04-04/user-data",
|
||||
address="192.192.192.2",
|
||||
headers={'X-Instance-ID': 'a-b-c-d',
|
||||
'X-Instance-ID-Signature': signed})
|
||||
self.assertEqual(response.status_int, 200)
|
||||
|
||||
# now enable the service
|
||||
|
||||
self.flags(service_quantum_metadata_proxy=True)
|
||||
response = fake_request(
|
||||
self.stubs, self.mdinst,
|
||||
relpath="/2009-04-04/user-data",
|
||||
address="192.192.192.2",
|
||||
fake_get_metadata_by_instance_id=fake_get_metadata,
|
||||
headers={'X-Instance-ID': 'a-b-c-d',
|
||||
'X-Instance-ID-Signature': signed})
|
||||
|
||||
self.assertEqual(response.status_int, 200)
|
||||
self.assertEqual(response.body,
|
||||
base64.b64decode(self.instance['user_data']))
|
||||
|
||||
response = fake_request(
|
||||
self.stubs, self.mdinst,
|
||||
relpath="/2009-04-04/user-data",
|
||||
address="192.192.192.2",
|
||||
fake_get_metadata_by_instance_id=fake_get_metadata,
|
||||
headers={'X-Instance-ID': 'a-b-c-d',
|
||||
'X-Instance-ID-Signature': ''})
|
||||
|
||||
self.assertEqual(response.status_int, 403)
|
||||
|
||||
response = fake_request(
|
||||
self.stubs, self.mdinst,
|
||||
relpath="/2009-04-04/user-data",
|
||||
address="192.192.192.2",
|
||||
fake_get_metadata_by_instance_id=fake_get_metadata,
|
||||
headers={'X-Instance-ID': 'z-z-z-z',
|
||||
'X-Instance-ID-Signature': '81f42e3fc77ba3a3e8d83142746e0'
|
||||
'8387b96cbc5bd2474665192d2ec28'
|
||||
'8ffb67'})
|
||||
self.assertEqual(response.status_int, 500)
|
||||
|
Loading…
Reference in New Issue
Block a user