IPv6 fix in Glance for malformed URLs.
Fix for a bug 1599123. URL construction is now considering what is defined as a hostname (IPv6 address or something else). The change results in a url constructed as http?://IPv4_address:port/, or http?://hostname:port/, or http?://[IPv6_address]:port/. There should be no more malformed URLs like http://fd00::f00d:9191/ generated for IPv6 addresses. It also includes a test in glance/tests/functional/test_images.py, named TestImagesIPv6. Additional functions which work on IPv6 only were added since the whole testing suite is hardcoded for IPv4. Change-Id: I66d6f2c57d1ccd086f941fc9e3764b4cc321241f Closes-Bug: #1599123
This commit is contained in:
parent
f72d9556b4
commit
8be3e10586
@ -41,6 +41,7 @@ except ImportError:
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import netutils
|
||||
import six
|
||||
from six.moves import http_client
|
||||
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange
|
||||
@ -379,7 +380,10 @@ class BaseClient(object):
|
||||
action = urlparse.quote(action)
|
||||
path = '/'.join([self.doc_root or '', action.lstrip('/')])
|
||||
scheme = "https" if self.use_ssl else "http"
|
||||
netloc = "%s:%d" % (self.host, self.port)
|
||||
if netutils.is_valid_ipv6(self.host):
|
||||
netloc = "[%s]:%d" % (self.host, self.port)
|
||||
else:
|
||||
netloc = "%s:%d" % (self.host, self.port)
|
||||
|
||||
if isinstance(params, dict):
|
||||
for (key, value) in list(params.items()):
|
||||
|
@ -281,6 +281,8 @@ class ApiServer(Server):
|
||||
self.server_name = 'api'
|
||||
self.server_module = 'glance.cmd.%s' % self.server_name
|
||||
self.default_store = kwargs.get("default_store", "file")
|
||||
self.bind_host = "127.0.0.1"
|
||||
self.registry_host = "127.0.0.1"
|
||||
self.key_file = ""
|
||||
self.cert_file = ""
|
||||
self.metadata_encryption_key = "012345678901234567890123456789ab"
|
||||
@ -321,12 +323,12 @@ class ApiServer(Server):
|
||||
self.conf_base = """[DEFAULT]
|
||||
debug = %(debug)s
|
||||
default_log_levels = eventlet.wsgi.server=DEBUG
|
||||
bind_host = 127.0.0.1
|
||||
bind_host = %(bind_host)s
|
||||
bind_port = %(bind_port)s
|
||||
key_file = %(key_file)s
|
||||
cert_file = %(cert_file)s
|
||||
metadata_encryption_key = %(metadata_encryption_key)s
|
||||
registry_host = 127.0.0.1
|
||||
registry_host = %(registry_host)s
|
||||
registry_port = %(registry_port)s
|
||||
use_user_token = %(use_user_token)s
|
||||
send_identity_credentials = %(send_identity_credentials)s
|
||||
@ -462,6 +464,7 @@ class RegistryServer(Server):
|
||||
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
|
||||
default_sql_connection)
|
||||
|
||||
self.bind_host = "127.0.0.1"
|
||||
self.pid_file = os.path.join(self.test_dir, "registry.pid")
|
||||
self.log_file = os.path.join(self.test_dir, "registry.log")
|
||||
self.owner_is_tenant = True
|
||||
@ -475,7 +478,7 @@ class RegistryServer(Server):
|
||||
|
||||
self.conf_base = """[DEFAULT]
|
||||
debug = %(debug)s
|
||||
bind_host = 127.0.0.1
|
||||
bind_host = %(bind_host)s
|
||||
bind_port = %(bind_port)s
|
||||
log_file = %(log_file)s
|
||||
sql_connection = %(sql_connection)s
|
||||
@ -534,6 +537,8 @@ class ScrubberDaemon(Server):
|
||||
self.server_module = 'glance.cmd.%s' % self.server_name
|
||||
self.daemon = daemon
|
||||
|
||||
self.registry_host = "127.0.0.1"
|
||||
|
||||
self.image_dir = os.path.join(self.test_dir, "images")
|
||||
self.scrub_time = 5
|
||||
self.pid_file = os.path.join(self.test_dir, "scrubber.pid")
|
||||
@ -556,7 +561,7 @@ log_file = %(log_file)s
|
||||
daemon = %(daemon)s
|
||||
wakeup_time = 2
|
||||
scrub_time = %(scrub_time)s
|
||||
registry_host = 127.0.0.1
|
||||
registry_host = %(registry_host)s
|
||||
registry_port = %(registry_port)s
|
||||
metadata_encryption_key = %(metadata_encryption_key)s
|
||||
lock_path = %(lock_path)s
|
||||
@ -817,6 +822,25 @@ class FunctionalTest(test_utils.BaseTestCase):
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def ping_server_ipv6(self, port):
|
||||
"""
|
||||
Simple ping on the port. If responsive, return True, else
|
||||
return False.
|
||||
|
||||
:note We use raw sockets, not ping here, since ping uses ICMP and
|
||||
has no concept of ports...
|
||||
|
||||
The function uses IPv6 (therefore AF_INET6 and ::1).
|
||||
"""
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.connect(("::1", port))
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def wait_for_servers(self, servers, expect_launch=True, timeout=30):
|
||||
"""
|
||||
Tight loop, waiting for the given server port(s) to be available.
|
||||
|
@ -2910,6 +2910,76 @@ class TestImagesWithRegistry(TestImages):
|
||||
self.registry_server.deployment_flavor = 'trusted-auth'
|
||||
|
||||
|
||||
class TestImagesIPv6(functional.FunctionalTest):
|
||||
"""Verify that API and REG servers running IPv6 can communicate"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
First applying monkey patches of functions and methods which have
|
||||
IPv4 hardcoded.
|
||||
"""
|
||||
# Setting up initial monkey patch (1)
|
||||
test_utils.get_unused_port_ipv4 = test_utils.get_unused_port
|
||||
test_utils.get_unused_port_and_socket_ipv4 = (
|
||||
test_utils.get_unused_port_and_socket)
|
||||
test_utils.get_unused_port = test_utils.get_unused_port_ipv6
|
||||
test_utils.get_unused_port_and_socket = (
|
||||
test_utils.get_unused_port_and_socket_ipv6)
|
||||
super(TestImagesIPv6, self).setUp()
|
||||
self.cleanup()
|
||||
# Setting up monkey patch (2), after object is ready...
|
||||
self.ping_server_ipv4 = self.ping_server
|
||||
self.ping_server = self.ping_server_ipv6
|
||||
|
||||
def tearDown(self):
|
||||
# Cleaning up monkey patch (2).
|
||||
self.ping_server = self.ping_server_ipv4
|
||||
super(TestImagesIPv6, self).tearDown()
|
||||
# Cleaning up monkey patch (1).
|
||||
test_utils.get_unused_port = test_utils.get_unused_port_ipv4
|
||||
test_utils.get_unused_port_and_socket = (
|
||||
test_utils.get_unused_port_and_socket_ipv4)
|
||||
|
||||
def _url(self, path):
|
||||
return "http://[::1]:%d%s" % (self.api_port, path)
|
||||
|
||||
def _headers(self, custom_headers=None):
|
||||
base_headers = {
|
||||
'X-Identity-Status': 'Confirmed',
|
||||
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
|
||||
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
|
||||
'X-Tenant-Id': TENANT1,
|
||||
'X-Roles': 'member',
|
||||
}
|
||||
base_headers.update(custom_headers or {})
|
||||
return base_headers
|
||||
|
||||
def test_image_list_ipv6(self):
|
||||
# Image list should be empty
|
||||
self.api_server.data_api = (
|
||||
'glance.tests.functional.v2.registry_data_api')
|
||||
self.registry_server.deployment_flavor = 'trusted-auth'
|
||||
|
||||
# Setting up configuration parameters properly
|
||||
# (bind_host is not needed since it is replaced by monkey patches,
|
||||
# but it would be reflected in the configuration file, which is
|
||||
# at least improving consistency)
|
||||
self.registry_server.bind_host = "::1"
|
||||
self.api_server.bind_host = "::1"
|
||||
self.api_server.registry_host = "::1"
|
||||
self.scrubber_daemon.registry_host = "::1"
|
||||
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
requests.get(self._url('/'), headers=self._headers())
|
||||
|
||||
path = self._url('/v2/images')
|
||||
response = requests.get(path, headers=self._headers())
|
||||
self.assertEqual(200, response.status_code)
|
||||
images = jsonutils.loads(response.text)['images']
|
||||
self.assertEqual(0, len(images))
|
||||
|
||||
|
||||
class TestImageDirectURLVisibility(functional.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
|
@ -382,6 +382,27 @@ def get_unused_port_and_socket():
|
||||
return (port, s)
|
||||
|
||||
|
||||
def get_unused_port_ipv6():
|
||||
"""
|
||||
Returns an unused port on localhost on IPv6 (uses ::1).
|
||||
"""
|
||||
port, s = get_unused_port_and_socket_ipv6()
|
||||
s.close()
|
||||
return port
|
||||
|
||||
|
||||
def get_unused_port_and_socket_ipv6():
|
||||
"""
|
||||
Returns an unused port on localhost and the open socket
|
||||
from which it was created, but uses IPv6 (::1).
|
||||
"""
|
||||
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
s.bind(('::1', 0))
|
||||
# Ignoring flowinfo and scopeid...
|
||||
addr, port, flowinfo, scopeid = s.getsockname()
|
||||
return (port, s)
|
||||
|
||||
|
||||
def xattr_writes_supported(path):
|
||||
"""
|
||||
Returns True if the we can write a file to the supplied
|
||||
|
Loading…
Reference in New Issue
Block a user