support kubespawner running on a IPv6 only cluster

This commit is contained in:
Steffen Vogel
2020-04-15 23:21:53 +02:00
parent aa459aeb39
commit 6283e7ec83
5 changed files with 25 additions and 11 deletions

View File

@@ -561,7 +561,11 @@ class JupyterHub(Application):
def _url_part_changed(self, change): def _url_part_changed(self, change):
"""propagate deprecated ip/port/base_url config to the bind_url""" """propagate deprecated ip/port/base_url config to the bind_url"""
urlinfo = urlparse(self.bind_url) urlinfo = urlparse(self.bind_url)
urlinfo = urlinfo._replace(netloc='%s:%i' % (self.ip, self.port)) if ':' in self.ip:
fmt = '[%s]:%i'
else:
fmt = '%s:%i'
urlinfo = urlinfo._replace(netloc=fmt % (self.ip, self.port))
urlinfo = urlinfo._replace(path=self.base_url) urlinfo = urlinfo._replace(path=self.base_url)
bind_url = urlunparse(urlinfo) bind_url = urlunparse(urlinfo)
if bind_url != self.bind_url: if bind_url != self.bind_url:
@@ -727,10 +731,10 @@ class JupyterHub(Application):
help="""The ip or hostname for proxies and spawners to use help="""The ip or hostname for proxies and spawners to use
for connecting to the Hub. for connecting to the Hub.
Use when the bind address (`hub_ip`) is 0.0.0.0 or otherwise different Use when the bind address (`hub_ip`) is 0.0.0.0, :: or otherwise different
from the connect address. from the connect address.
Default: when `hub_ip` is 0.0.0.0, use `socket.gethostname()`, otherwise use `hub_ip`. Default: when `hub_ip` is 0.0.0.0 or ::, use `socket.gethostname()`, otherwise use `hub_ip`.
Note: Some spawners or proxy implementations might not support hostnames. Check your Note: Some spawners or proxy implementations might not support hostnames. Check your
spawner or proxy documentation to see if they have extra requirements. spawner or proxy documentation to see if they have extra requirements.

View File

@@ -53,7 +53,7 @@ class Server(HasTraits):
Never used in APIs, only logging, Never used in APIs, only logging,
since it can be non-connectable value, such as '', meaning all interfaces. since it can be non-connectable value, such as '', meaning all interfaces.
""" """
if self.ip in {'', '0.0.0.0'}: if self.ip in {'', '0.0.0.0', '::'}:
return self.url.replace(self._connect_ip, self.ip or '*', 1) return self.url.replace(self._connect_ip, self.ip or '*', 1)
return self.url return self.url
@@ -87,13 +87,13 @@ class Server(HasTraits):
"""The address to use when connecting to this server """The address to use when connecting to this server
When `ip` is set to a real ip address, the same value is used. When `ip` is set to a real ip address, the same value is used.
When `ip` refers to 'all interfaces' (e.g. '0.0.0.0'), When `ip` refers to 'all interfaces' (e.g. '0.0.0.0' or '::'),
clients connect via hostname by default. clients connect via hostname by default.
Setting `connect_ip` explicitly overrides any default behavior. Setting `connect_ip` explicitly overrides any default behavior.
""" """
if self.connect_ip: if self.connect_ip:
return self.connect_ip return self.connect_ip
elif self.ip in {'', '0.0.0.0'}: elif self.ip in {'', '0.0.0.0', '::'}:
# if listening on all interfaces, default to hostname for connect # if listening on all interfaces, default to hostname for connect
return socket.gethostname() return socket.gethostname()
else: else:
@@ -149,7 +149,12 @@ class Server(HasTraits):
if self.connect_url: if self.connect_url:
parsed = urlparse(self.connect_url) parsed = urlparse(self.connect_url)
return "{proto}://{host}".format(proto=parsed.scheme, host=parsed.netloc) return "{proto}://{host}".format(proto=parsed.scheme, host=parsed.netloc)
return "{proto}://{ip}:{port}".format(
if ':' in self._connect_ip:
fmt = "{proto}://[{ip}]:{port}"
else:
fmt = "{proto}://{ip}:{port}"
return fmt.format(
proto=self.proto, ip=self._connect_ip, port=self._connect_port proto=self.proto, ip=self._connect_ip, port=self._connect_port
) )

View File

@@ -342,7 +342,7 @@ class Service(LoggingConfigurable):
env['JUPYTERHUB_SERVICE_PREFIX'] = self.server.base_url env['JUPYTERHUB_SERVICE_PREFIX'] = self.server.base_url
hub = self.hub hub = self.hub
if self.hub.ip in ('0.0.0.0', ''): if self.hub.ip in ('', '0.0.0.0', '::'):
# if the Hub is listening on all interfaces, # if the Hub is listening on all interfaces,
# tell services to connect via localhost # tell services to connect via localhost
# since they are always local subprocesses # since they are always local subprocesses

View File

@@ -566,7 +566,12 @@ class User:
else: else:
# >= 0.7 returns (ip, port) # >= 0.7 returns (ip, port)
proto = 'https' if self.settings['internal_ssl'] else 'http' proto = 'https' if self.settings['internal_ssl'] else 'http'
url = '%s://%s:%i' % ((proto,) + url)
# check if spawner returned an IPv6 address
if ':' in url[0]:
url = '%s://[%s]:%i' % ((proto,) + url)
else:
url = '%s://%s:%i' % ((proto,) + url)
urlinfo = urlparse(url) urlinfo = urlparse(url)
server.proto = urlinfo.scheme server.proto = urlinfo.scheme
server.ip = urlinfo.hostname server.ip = urlinfo.hostname

View File

@@ -66,7 +66,7 @@ def can_connect(ip, port):
Return True if we can connect, False otherwise. Return True if we can connect, False otherwise.
""" """
if ip in {'', '0.0.0.0'}: if ip in {'', '0.0.0.0', '::'}:
ip = '127.0.0.1' ip = '127.0.0.1'
try: try:
socket.create_connection((ip, port)).close() socket.create_connection((ip, port)).close()
@@ -179,7 +179,7 @@ async def exponential_backoff(
async def wait_for_server(ip, port, timeout=10): async def wait_for_server(ip, port, timeout=10):
"""Wait for any server to show up at ip:port.""" """Wait for any server to show up at ip:port."""
if ip in {'', '0.0.0.0'}: if ip in {'', '0.0.0.0', '::'}:
ip = '127.0.0.1' ip = '127.0.0.1'
await exponential_backoff( await exponential_backoff(
lambda: can_connect(ip, port), lambda: can_connect(ip, port),