Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 3 additions & 93 deletions neutron/agent/metadata/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,21 @@

import io
import socketserver
import urllib

from neutron_lib.agent import topics
from neutron_lib import constants
from neutron_lib import context
from oslo_config import cfg
from oslo_log import log as logging
import requests
import webob

from neutron._i18n import _
from neutron.agent.common import base_agent_rpc
from neutron.agent.linux import utils as agent_utils
from neutron.agent.metadata import proxy_base
from neutron.agent import rpc as agent_rpc
from neutron.common import ipv6_utils
from neutron.common import loopingcall
from neutron.common import metadata as common_metadata
from neutron.common import utils as common_utils


LOG = logging.getLogger(__name__)
Expand All @@ -58,95 +54,9 @@ def __init__(self, topic):
version='1.0')


class MetadataProxyHandlerBaseSocketServer(
proxy_base.MetadataProxyHandlerBase):
@staticmethod
def _http_response(http_response, request):
_res = webob.Response(
body=http_response.content,
status=http_response.status_code,
content_type=http_response.headers['content-type'],
charset=http_response.encoding)
# NOTE(ralonsoh): there should be a better way to format the HTTP
# response, adding the HTTP version to the ``webob.Response``
# output string.
out = request.http_version + ' ' + str(_res)
if (int(_res.headers['content-length']) == 0 and
_res.status_code == 200):
# Add 2 extra \r\n to the result. HAProxy is also expecting
# it even when the body is empty.
out += '\r\n\r\n'
return out.encode(http_response.encoding)

def _proxy_request(self, instance_id, project_id, req):
headers = {
'X-Forwarded-For': req.headers.get('X-Forwarded-For'),
'X-Instance-ID': instance_id,
'X-Tenant-ID': project_id,
'X-Instance-ID-Signature': common_utils.sign_instance_id(
self.conf, instance_id)
}

nova_host_port = ipv6_utils.valid_ipv6_url(
self.conf.nova_metadata_host,
self.conf.nova_metadata_port)

url = urllib.parse.urlunsplit((
self.conf.nova_metadata_protocol,
nova_host_port,
req.path_info,
req.query_string,
''))

disable_ssl_certificate_validation = self.conf.nova_metadata_insecure
if self.conf.auth_ca_cert and not disable_ssl_certificate_validation:
verify_cert = self.conf.auth_ca_cert
else:
verify_cert = not disable_ssl_certificate_validation

client_cert = None
if self.conf.nova_client_cert and self.conf.nova_client_priv_key:
client_cert = (self.conf.nova_client_cert,
self.conf.nova_client_priv_key)

try:
resp = requests.request(method=req.method, url=url,
headers=headers,
data=req.body,
cert=client_cert,
verify=verify_cert,
timeout=60)
except requests.ConnectionError:
msg = _('The remote metadata server is temporarily unavailable. '
'Please try again later.')
LOG.warning(msg)
title = '503 Service Unavailable'
return common_metadata.encode_http_reponse(title, title, msg)

if resp.status_code == 200:
return self._http_response(resp, req)
if resp.status_code == 403:
LOG.warning(
'The remote metadata server responded with Forbidden. This '
'response usually occurs when shared secrets do not match.'
)
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
if resp.status_code == 500:
msg = _(
'Remote metadata server experienced an internal server error.'
)
LOG.warning(msg)
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
if resp.status_code in (400, 404, 409, 502, 503, 504):
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
raise Exception(_('Unexpected response code: %s') % resp.status_code)


class MetadataProxyHandler(MetadataProxyHandlerBaseSocketServer,
socketserver.StreamRequestHandler):
class MetadataProxyHandler(
common_metadata.MetadataProxyHandlerBaseSocketServer,
socketserver.StreamRequestHandler):
NETWORK_ID_HEADER = 'X-Neutron-Network-ID'
ROUTER_ID_HEADER = 'X-Neutron-Router-ID'
_conf = None
Expand Down
97 changes: 3 additions & 94 deletions neutron/agent/ovn/metadata/server_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,117 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.


import io
import socketserver
import urllib

from oslo_config import cfg
from oslo_log import log as logging
import requests
import webob

from neutron._i18n import _
from neutron.agent.linux import utils as agent_utils
from neutron.agent.metadata import proxy_base
from neutron.common import ipv6_utils
from neutron.common import metadata as common_metadata
from neutron.common.ovn import constants as ovn_const
from neutron.common import utils as common_utils


LOG = logging.getLogger(__name__)


class MetadataProxyHandlerBaseSocketServer(
proxy_base.MetadataProxyHandlerBase):
@staticmethod
def _http_response(http_response, request):
_res = webob.Response(
body=http_response.content,
status=http_response.status_code,
content_type=http_response.headers['content-type'],
charset=http_response.encoding)
# NOTE(ralonsoh): there should be a better way to format the HTTP
# response, adding the HTTP version to the ``webob.Response``
# output string.
out = request.http_version + ' ' + str(_res)
if (int(_res.headers['content-length']) == 0 and
_res.status_code == 200):
# Add 2 extra \r\n to the result. HAProxy is also expecting
# it even when the body is empty.
out += '\r\n\r\n'
return out.encode(http_response.encoding)

def _proxy_request(self, instance_id, project_id, req):
headers = {
'X-Forwarded-For': req.headers.get('X-Forwarded-For'),
'X-Instance-ID': instance_id,
'X-Tenant-ID': project_id,
'X-Instance-ID-Signature': common_utils.sign_instance_id(
self.conf, instance_id)
}

nova_host_port = ipv6_utils.valid_ipv6_url(
self.conf.nova_metadata_host,
self.conf.nova_metadata_port)

url = urllib.parse.urlunsplit((
self.conf.nova_metadata_protocol,
nova_host_port,
req.path_info,
req.query_string,
''))

disable_ssl_certificate_validation = self.conf.nova_metadata_insecure
if self.conf.auth_ca_cert and not disable_ssl_certificate_validation:
verify_cert = self.conf.auth_ca_cert
else:
verify_cert = not disable_ssl_certificate_validation

client_cert = None
if self.conf.nova_client_cert and self.conf.nova_client_priv_key:
client_cert = (self.conf.nova_client_cert,
self.conf.nova_client_priv_key)

try:
resp = requests.request(method=req.method, url=url,
headers=headers,
data=req.body,
cert=client_cert,
verify=verify_cert,
timeout=60)
except requests.ConnectionError:
msg = _('The remote metadata server is temporarily unavailable. '
'Please try again later.')
LOG.warning(msg)
title = '503 Service Unavailable'
return common_metadata.encode_http_reponse(title, title, msg)

if resp.status_code == 200:
return self._http_response(resp, req)
if resp.status_code == 403:
LOG.warning(
'The remote metadata server responded with Forbidden. This '
'response usually occurs when shared secrets do not match.'
)
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
if resp.status_code == 500:
msg = _(
'Remote metadata server experienced an internal server error.'
)
LOG.warning(msg)
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
if resp.status_code in (400, 404, 409, 502, 503, 504):
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
raise Exception(_('Unexpected response code: %s') % resp.status_code)


class MetadataProxyHandler(MetadataProxyHandlerBaseSocketServer,
socketserver.StreamRequestHandler):
class MetadataProxyHandler(
common_metadata.MetadataProxyHandlerBaseSocketServer,
socketserver.StreamRequestHandler):
NETWORK_ID_HEADER = 'X-OVN-Network-ID'
ROUTER_ID_HEADER = ''
_conf = None
Expand Down
106 changes: 106 additions & 0 deletions neutron/common/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,20 @@
# License for the specific language governing permissions and limitations
# under the License.

import abc
from urllib import parse

import jinja2
from neutron_lib import constants
from oslo_log import log as logging
from oslo_utils import encodeutils
import requests
import webob

from neutron._i18n import _
from neutron.agent.metadata import proxy_base
from neutron.common import ipv6_utils
from neutron.common import utils as common_utils


LOG = logging.getLogger(__name__)
Expand All @@ -23,6 +33,8 @@
PROXY_SERVICE_NAME = 'haproxy'
PROXY_SERVICE_CMD = 'haproxy'

CONTENT_ENCODERS = ('gzip', 'deflate')


class InvalidUserOrGroupException(Exception):
pass
Expand Down Expand Up @@ -140,3 +152,97 @@ def encode_http_reponse(http_code, title, message):
reponse = RESPONSE.render(http_code=http_code, title=title,
body_title=title, body=message, len=length)
return encodeutils.to_utf8(reponse)


class MetadataProxyHandlerBaseSocketServer(
proxy_base.MetadataProxyHandlerBase,
metaclass=abc.ABCMeta):
@staticmethod
def _http_response(http_response, request):
_res = webob.Response(
body=http_response.content,
status=http_response.status_code,
content_type=http_response.headers['content-type'],
charset=http_response.encoding)
# The content of the response is decoded depending on the
# "Context-Enconding" header, if present. The operation is limited to
# ("gzip", "deflate"), as is in the ``webob.response.Response`` class.
if _res.content_encoding in CONTENT_ENCODERS:
_res.decode_content()

# NOTE(ralonsoh): there should be a better way to format the HTTP
# response, adding the HTTP version to the ``webob.Response``
# output string.
out = request.http_version + ' ' + str(_res)
if (int(_res.headers['content-length']) == 0 and
_res.status_code == 200):
# Add 2 extra \r\n to the result. HAProxy is also expecting
# it even when the body is empty.
out += '\r\n\r\n'
return out.encode(http_response.encoding)

def _proxy_request(self, instance_id, project_id, req):
headers = {
'X-Forwarded-For': req.headers.get('X-Forwarded-For'),
'X-Instance-ID': instance_id,
'X-Tenant-ID': project_id,
'X-Instance-ID-Signature': common_utils.sign_instance_id(
self.conf, instance_id)
}

nova_host_port = ipv6_utils.valid_ipv6_url(
self.conf.nova_metadata_host,
self.conf.nova_metadata_port)

url = parse.urlunsplit((
self.conf.nova_metadata_protocol,
nova_host_port,
req.path_info,
req.query_string,
''))

disable_ssl_certificate_validation = self.conf.nova_metadata_insecure
if self.conf.auth_ca_cert and not disable_ssl_certificate_validation:
verify_cert = self.conf.auth_ca_cert
else:
verify_cert = not disable_ssl_certificate_validation

client_cert = None
if self.conf.nova_client_cert and self.conf.nova_client_priv_key:
client_cert = (self.conf.nova_client_cert,
self.conf.nova_client_priv_key)

try:
resp = requests.request(method=req.method, url=url,
headers=headers,
data=req.body,
cert=client_cert,
verify=verify_cert,
timeout=60)
except requests.ConnectionError:
msg = _('The remote metadata server is temporarily unavailable. '
'Please try again later.')
LOG.warning(msg)
title = '503 Service Unavailable'
return encode_http_reponse(title, title, msg)

if resp.status_code == 200:
return self._http_response(resp, req)
if resp.status_code == 403:
LOG.warning(
'The remote metadata server responded with Forbidden. This '
'response usually occurs when shared secrets do not match.'
)
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
if resp.status_code == 500:
msg = _(
'Remote metadata server experienced an internal server error.'
)
LOG.warning(msg)
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
if resp.status_code in (400, 404, 409, 502, 503, 504):
# TODO(ralonsoh): add info in the returned HTTP message to the VM.
return self._http_response(resp, req)
raise Exception(_('Unexpected response code: %s') % resp.status_code)
Loading