Merge "Unify `MetadataProxyHandlerBaseSocketServer` class" into stable/2025.1
This commit is contained in:
@@ -14,14 +14,12 @@
|
||||
|
||||
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 _
|
||||
@@ -29,10 +27,8 @@ 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__)
|
||||
@@ -58,95 +54,9 @@ class MetadataPluginAPI(base_agent_rpc.BasePluginApi):
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__)
|
||||
@@ -140,3 +150,91 @@ 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)
|
||||
# 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)
|
||||
|
||||
Reference in New Issue
Block a user