Move environment variables to SingletonConfigurable

Created a singleton class `Gateway` to store all configuration options
for a Gateway.  This class also holds some help methods to make it easier
to use the options and determine if the gateway option is enabled.

Updated the NotebookTestBase class to allow for subclasses to infuence
the patched environment as well as command line options via argv.

Added a test to ensure various gateway configuration items can be
set via the environment or command-line.
This commit is contained in:
Kevin Bates 2018-12-04 14:19:56 -08:00
parent 060a2b9aea
commit f74ef2f691
6 changed files with 296 additions and 159 deletions

View File

@ -359,13 +359,13 @@ the environment:
.. code-block:: bash
GATEWAY_URL=http://my-gateway-server:8888
JUPYTER_GATEWAY_URL=http://my-gateway-server:8888
or in :file:`jupyter_notebook_config.py`:
.. code-block:: python
c.NotebookApp.gateway_url = http://my-gateway-server:8888
c.Gateway.url = http://my-gateway-server:8888
When provided, all kernel specifications will be retrieved from the specified Gateway server and all
kernels will be managed by that server. This option enables the ability to target kernel processes

View File

@ -21,27 +21,7 @@ from ipython_genutils.py3compat import cast_unicode
from jupyter_client.session import Session
from traitlets.config.configurable import LoggingConfigurable
# Note: Although some of these are available via NotebookApp (command line), we will
# take the approach of using environment variables to enable separate sets of values
# for use with the local Notebook server (via command-line) and remote Gateway server
# (via environment variables).
GATEWAY_HEADERS = json.loads(os.getenv('GATEWAY_HEADERS', '{}'))
GATEWAY_HEADERS.update({
'Authorization': 'token {}'.format(os.getenv('GATEWAY_AUTH_TOKEN', ''))
})
VALIDATE_GATEWAY_CERT = os.getenv('VALIDATE_GATEWAY_CERT') not in ['no', 'false']
GATEWAY_CLIENT_KEY = os.getenv('GATEWAY_CLIENT_KEY')
GATEWAY_CLIENT_CERT = os.getenv('GATEWAY_CLIENT_CERT')
GATEWAY_CLIENT_CA = os.getenv('GATEWAY_CLIENT_CA')
GATEWAY_HTTP_USER = os.getenv('GATEWAY_HTTP_USER')
GATEWAY_HTTP_PASS = os.getenv('GATEWAY_HTTP_PASS')
# Get env variables to handle timeout of request and connection
GATEWAY_CONNECT_TIMEOUT = float(os.getenv('GATEWAY_CONNECT_TIMEOUT', 20.0))
GATEWAY_REQUEST_TIMEOUT = float(os.getenv('GATEWAY_REQUEST_TIMEOUT', 20.0))
from .managers import Gateway
class WebSocketChannelsHandler(WebSocketHandler, IPythonHandler):
@ -77,7 +57,7 @@ class WebSocketChannelsHandler(WebSocketHandler, IPythonHandler):
def initialize(self):
self.log.debug("Initializing websocket connection %s", self.request.path)
self.session = Session(config=self.config)
self.gateway = GatewayWebSocketClient(gateway_url=self.kernel_manager.parent.gateway_url)
self.gateway = GatewayWebSocketClient(gateway_url=Gateway.instance().url)
@gen.coroutine
def get(self, kernel_id, *args, **kwargs):
@ -135,7 +115,6 @@ class GatewayWebSocketClient(LoggingConfigurable):
def __init__(self, **kwargs):
super(GatewayWebSocketClient, self).__init__(**kwargs)
self.gateway_url = kwargs['gateway_url']
self.kernel_id = None
self.ws = None
self.ws_future = Future()
@ -145,29 +124,14 @@ class GatewayWebSocketClient(LoggingConfigurable):
def _connect(self, kernel_id):
self.kernel_id = kernel_id
ws_url = url_path_join(
os.getenv('GATEWAY_WS_URL', self.gateway_url.replace('http', 'ws')),
'/api/kernels',
url_escape(kernel_id),
'channels'
Gateway.instance().ws_url,
Gateway.instance().kernels_endpoint, url_escape(kernel_id), 'channels'
)
self.log.info('Connecting to {}'.format(ws_url))
parameters = {
"headers": GATEWAY_HEADERS,
"validate_cert": VALIDATE_GATEWAY_CERT,
"connect_timeout": GATEWAY_CONNECT_TIMEOUT,
"request_timeout": GATEWAY_REQUEST_TIMEOUT
}
if GATEWAY_HTTP_USER:
parameters["auth_username"] = GATEWAY_HTTP_USER
if GATEWAY_HTTP_PASS:
parameters["auth_password"] = GATEWAY_HTTP_PASS
if GATEWAY_CLIENT_KEY:
parameters["client_key"] = GATEWAY_CLIENT_KEY
parameters["client_cert"] = GATEWAY_CLIENT_CERT
if GATEWAY_CLIENT_CA:
parameters["ca_certs"] = GATEWAY_CLIENT_CA
kwargs = {}
kwargs = Gateway.instance().load_connection_args(**kwargs)
request = HTTPRequest(ws_url, **parameters)
request = HTTPRequest(ws_url, **kwargs)
self.ws_future = websocket_connect(request)
self.ws_future.add_done_callback(self._connection_done)
@ -178,7 +142,7 @@ class GatewayWebSocketClient(LoggingConfigurable):
else:
self.log.warning("Websocket connection has been cancelled via client disconnect before its establishment. "
"Kernel with ID '{}' may not be terminated on Gateway: {}".
format(self.kernel_id, self.gateway_url))
format(self.kernel_id, Gateway.instance().url))
def _disconnect(self):
if self.ws is not None:
@ -343,18 +307,15 @@ class MainKernelSpecHandler(APIHandler):
# NOTE: We do this here since this handler is called during the Notebook's startup and subsequent refreshes
# of the tree view.
except ConnectionRefusedError:
gateway_url = ksm.parent.gateway_url
self.log.error("Connection refused from Gateway server url '{}'. "
"Check to be sure the Gateway instance is running.".format(gateway_url))
"Check to be sure the Gateway instance is running.".format(Gateway.instance().url))
except HTTPTimeoutError:
# This can occur if the host is valid (e.g., foo.com) but there's nothing there.
gateway_url = ksm.parent.gateway_url
self.log.error("Timeout error attempting to connect to Gateway server url '{}'. "
"Ensure gateway_url is valid and the Gateway instance is running.".format(gateway_url))
"Ensure gateway url is valid and the Gateway instance is running.".format(Gateway.instance().url))
except gaierror as e:
gateway_url = ksm.parent.gateway_url
self.log.error("The Gateway server specified in the gateway_url '{}' doesn't appear to be valid. "
"Ensure gateway_url is valid and the Gateway instance is running.".format(gateway_url))
"Ensure gateway url is valid and the Gateway instance is running.".format(Gateway.instance().url))
self.finish()

View File

@ -14,47 +14,8 @@ from ..services.sessions.sessionmanager import SessionManager
from jupyter_client.kernelspec import KernelSpecManager
from ..utils import url_path_join
from traitlets import Instance, Unicode, default
# Note: Although some of these are available via NotebookApp (command line), we will
# take the approach of using environment variables to enable separate sets of values
# for use with the local Notebook server (via command-line) and remote Gateway server
# (via environment variables).
GATEWAY_HEADERS = json.loads(os.getenv('GATEWAY_HEADERS', '{}'))
GATEWAY_HEADERS.update({
'Authorization': 'token {}'.format(os.getenv('GATEWAY_AUTH_TOKEN', ''))
})
VALIDATE_GATEWAY_CERT = os.getenv('VALIDATE_GATEWAY_CERT') not in ['no', 'false']
GATEWAY_CLIENT_KEY = os.getenv('GATEWAY_CLIENT_KEY')
GATEWAY_CLIENT_CERT = os.getenv('GATEWAY_CLIENT_CERT')
GATEWAY_CLIENT_CA = os.getenv('GATEWAY_CLIENT_CA')
GATEWAY_HTTP_USER = os.getenv('GATEWAY_HTTP_USER')
GATEWAY_HTTP_PASS = os.getenv('GATEWAY_HTTP_PASS')
GATEWAY_CONNECT_TIMEOUT = float(os.getenv('GATEWAY_CONNECT_TIMEOUT', 20.0))
GATEWAY_REQUEST_TIMEOUT = float(os.getenv('GATEWAY_REQUEST_TIMEOUT', 20.0))
def load_connection_args(**kwargs):
if GATEWAY_CLIENT_CERT:
kwargs["client_key"] = kwargs.get("client_key", GATEWAY_CLIENT_KEY)
kwargs["client_cert"] = kwargs.get("client_cert", GATEWAY_CLIENT_CERT)
if GATEWAY_CLIENT_CA:
kwargs["ca_certs"] = kwargs.get("ca_certs", GATEWAY_CLIENT_CA)
kwargs['connect_timeout'] = kwargs.get('connect_timeout', GATEWAY_CONNECT_TIMEOUT)
kwargs['request_timeout'] = kwargs.get('request_timeout', GATEWAY_REQUEST_TIMEOUT)
kwargs['headers'] = kwargs.get('headers', GATEWAY_HEADERS)
kwargs['validate_cert'] = kwargs.get('validate_cert', VALIDATE_GATEWAY_CERT)
if GATEWAY_HTTP_USER:
kwargs['auth_username'] = kwargs.get('auth_username', GATEWAY_HTTP_USER)
if GATEWAY_HTTP_PASS:
kwargs['auth_password'] = kwargs.get('auth_password', GATEWAY_HTTP_PASS)
return kwargs
from traitlets import Instance, Unicode, Float, Bool, default, validate, TraitError
from traitlets.config import SingletonConfigurable
@gen.coroutine
@ -62,30 +23,234 @@ def fetch_gateway(endpoint, **kwargs):
"""Make an async request to kernel gateway endpoint."""
client = AsyncHTTPClient()
kwargs = load_connection_args(**kwargs)
kwargs = Gateway.instance().load_connection_args(**kwargs)
response = yield client.fetch(endpoint, **kwargs)
raise gen.Return(response)
class GatewayKernelManager(MappingKernelManager):
"""Kernel manager that supports remote kernels hosted by Jupyter
kernel gateway."""
class Gateway(SingletonConfigurable):
"""This class manages the configuration. It's its own class so that we can avoid having command
line options of the likes `--GatewayKernelManager.connect_timeout` and use the shorter and more
applicable `--Gateway.connect_timeout`, etc. It also contains some helper methods to build
request arguments out of the various config options.
kernels_endpoint_env = 'GATEWAY_KERNELS_ENDPOINT'
kernels_endpoint = Unicode(config=True,
help="""The gateway API endpoint for accessing kernel resources (GATEWAY_KERNELS_ENDPOINT env var)""")
"""
url = Unicode(default_value=None, allow_none=True, config=True,
help="""The url of the Kernel or Enterprise Gateway server where
kernel specifications are defined and kernel management takes place.
If defined, this Notebook server acts as a proxy for all kernel
management and kernel specification retrieval. (JUPYTER_GATEWAY_URL env var)
"""
)
url_env = 'JUPYTER_GATEWAY_URL'
@default('url')
def _url_default(self):
return os.environ.get(self.url_env)
@validate('url')
def _url_validate(self, proposal):
value = proposal['value']
# Ensure value, if present, starts with 'http'
if value is not None and len(value) > 0:
if not str(value).lower().startswith('http'):
raise TraitError("Gateway url must start with 'http': '%r'" % value)
return value
ws_url = Unicode(default_value=None, allow_none=True, config=True,
help="""The websocket url of the Kernel or Enterprise Gateway server. If not provided, this value
will correspond to the value of the Gateway url with 'ws' in place of 'http'. (JUPYTER_GATEWAY_WS_URL env var)
"""
)
ws_url_env = 'JUPYTER_GATEWAY_WS_URL'
@default('ws_url')
def _ws_url_default(self):
default_value = os.environ.get(self.ws_url_env)
if default_value is None:
if self.gateway_enabled:
default_value = self.url.lower().replace('http', 'ws')
return default_value
@validate('ws_url')
def _ws_url_validate(self, proposal):
value = proposal['value']
# Ensure value, if present, starts with 'ws'
if value is not None and len(value) > 0:
if not str(value).lower().startswith('ws'):
raise TraitError("Gateway ws_url must start with 'ws': '%r'" % value)
return value
kernels_endpoint_default_value = '/api/kernels'
kernels_endpoint_env = 'JUPYTER_GATEWAY_KERNELS_ENDPOINT'
kernels_endpoint = Unicode(default_value=kernels_endpoint_default_value, config=True,
help="""The gateway API endpoint for accessing kernel resources (JUPYTER_GATEWAY_KERNELS_ENDPOINT env var)""")
@default('kernels_endpoint')
def kernels_endpoint_default(self):
return os.getenv(self.kernels_endpoint_env, '/api/kernels')
def _kernels_endpoint_default(self):
return os.environ.get(self.kernels_endpoint_env, self.kernels_endpoint_default_value)
kernelspecs_endpoint_default_value = '/api/kernelspecs'
kernelspecs_endpoint_env = 'JUPYTER_GATEWAY_KERNELSPECS_ENDPOINT'
kernelspecs_endpoint = Unicode(default_value=kernelspecs_endpoint_default_value, config=True,
help="""The gateway API endpoint for accessing kernelspecs (JUPYTER_GATEWAY_KERNELSPECS_ENDPOINT env var)""")
@default('kernelspecs_endpoint')
def _kernelspecs_endpoint_default(self):
return os.environ.get(self.kernelspecs_endpoint_env, self.kernelspecs_endpoint_default_value)
connect_timeout_default_value = 20.0
connect_timeout_env = 'JUPYTER_GATEWAY_CONNECT_TIMEOUT'
connect_timeout = Float(default_value=connect_timeout_default_value, config=True,
help="""The time allowed for HTTP connection establishment with the Gateway server.
(JUPYTER_GATEWAY_CONNECT_TIMEOUT env var)""")
@default('connect_timeout')
def connect_timeout_default(self):
return float(os.environ.get('JUPYTER_GATEWAY_CONNECT_TIMEOUT', self.connect_timeout_default_value))
request_timeout_default_value = 20.0
request_timeout_env = 'JUPYTER_GATEWAY_REQUEST_TIMEOUT'
request_timeout = Float(default_value=request_timeout_default_value, config=True,
help="""The time allowed for HTTP request completion. (JUPYTER_GATEWAY_REQUEST_TIMEOUT env var)""")
@default('request_timeout')
def request_timeout_default(self):
return float(os.environ.get('JUPYTER_GATEWAY_REQUEST_TIMEOUT', self.request_timeout_default_value))
client_key = Unicode(default_value=None, allow_none=True, config=True,
help="""The filename for client SSL key, if any. (JUPYTER_GATEWAY_CLIENT_KEY env var)
"""
)
client_key_env = 'JUPYTER_GATEWAY_CLIENT_KEY'
@default('client_key')
def _client_key_default(self):
return os.environ.get(self.client_key_env)
client_cert = Unicode(default_value=None, allow_none=True, config=True,
help="""The filename for client SSL certificate, if any. (JUPYTER_GATEWAY_CLIENT_CERT env var)
"""
)
client_cert_env = 'JUPYTER_GATEWAY_CLIENT_CERT'
@default('client_cert')
def _client_cert_default(self):
return os.environ.get(self.client_cert_env)
ca_certs = Unicode(default_value=None, allow_none=True, config=True,
help="""The filename of CA certificates or None to use defaults. (JUPYTER_GATEWAY_CA_CERTS env var)
"""
)
ca_certs_env = 'JUPYTER_GATEWAY_CA_CERTS'
@default('ca_certs')
def _ca_certs_default(self):
return os.environ.get(self.ca_certs_env)
http_user = Unicode(default_value=None, allow_none=True, config=True,
help="""The username for HTTP authentication. (JUPYTER_GATEWAY_HTTP_USER env var)
"""
)
http_user_env = 'JUPYTER_GATEWAY_HTTP_USER'
@default('http_user')
def _http_user_default(self):
return os.environ.get(self.http_user_env)
http_pwd = Unicode(default_value=None, allow_none=True, config=True,
help="""The password for HTTP authentication. (JUPYTER_GATEWAY_HTTP_PWD env var)
"""
)
http_pwd_env = 'JUPYTER_GATEWAY_HTTP_PWD'
@default('http_pwd')
def _http_pwd_default(self):
return os.environ.get(self.http_pwd_env)
headers_default_value = '{}'
headers_env = 'JUPYTER_GATEWAY_HEADERS'
headers = Unicode(default_value=headers_default_value, allow_none=True,config=True,
help="""Additional HTTP headers to pass on the request. This value will be converted to a dict.
(JUPYTER_GATEWAY_HEADERS env var)
"""
)
@default('headers')
def _headers_default(self):
return os.environ.get(self.headers_env, self.headers_default_value)
auth_token = Unicode(default_value=None, allow_none=True, config=True,
help="""The authorization token used in the HTTP headers. (JUPYTER_GATEWAY_AUTH_TOKEN env var)
"""
)
auth_token_env = 'JUPYTER_GATEWAY_AUTH_TOKEN'
@default('auth_token')
def _auth_token_default(self):
return os.environ.get(self.auth_token_env)
validate_cert_default_value = True
validate_cert_env = 'JUPYTER_GATEWAY_VALIDATE_CERT'
validate_cert = Bool(default_value=validate_cert_default_value, config=True,
help="""For HTTPS requests, determines if server's certificate should be validated or not.
(JUPYTER_GATEWAY_VALIDATE_CERT env var)"""
)
@default('validate_cert')
def validate_cert_default(self):
return bool(os.environ.get(self.validate_cert_env, str(self.validate_cert_default_value)) not in ['no', 'false'])
def __init__(self, **kwargs):
super(Gateway, self).__init__(**kwargs)
self._static_args = {} # initialized on first use
@property
def gateway_enabled(self):
return bool(self.url is not None and len(self.url) > 0)
def init_static_args(self):
"""Initialize arguments used on every request. Since these are static values, we'll
perform this operation once.
"""
self._static_args['headers'] = json.loads(self.headers)
self._static_args['headers'].update({'Authorization': 'token {}'.format(self.auth_token)})
self._static_args['connect_timeout'] = self.connect_timeout
self._static_args['request_timeout'] = self.request_timeout
self._static_args['validate_cert'] = self.validate_cert
if self.client_cert:
self._static_args['client_cert'] = self.client_cert
self._static_args['client_key'] = self.client_key
if self.ca_certs:
self._static_args['ca_certs'] = self.ca_certs
if self.http_user:
self._static_args['auth_username'] = self.http_user
if self.http_pwd:
self._static_args['auth_password'] = self.http_pwd
def load_connection_args(self, **kwargs):
"""Merges the static args relative to the connection, with the given keyword arguments. If statics
have yet to be initialized, we'll do that here.
"""
if len(self._static_args) == 0:
self.init_static_args()
kwargs.update(self._static_args)
return kwargs
class GatewayKernelManager(MappingKernelManager):
"""Kernel manager that supports remote kernels hosted by Jupyter Kernel or Enterprise Gateway."""
# We'll maintain our own set of kernel ids
_kernels = {}
def __init__(self, **kwargs):
super(GatewayKernelManager, self).__init__(**kwargs)
self.gateway_url = self.parent.gateway_url
self.base_endpoint = url_path_join(Gateway.instance().url, Gateway.instance().kernels_endpoint)
def __contains__(self, kernel_id):
return kernel_id in self._kernels
@ -105,9 +270,9 @@ class GatewayKernelManager(MappingKernelManager):
kernel_id: kernel UUID (optional)
"""
if kernel_id:
return url_path_join(self.gateway_url, self.kernels_endpoint, url_escape(str(kernel_id)))
return url_path_join(self.base_endpoint, url_escape(str(kernel_id)))
return url_path_join(self.gateway_url, self.kernels_endpoint)
return self.base_endpoint
@gen.coroutine
def start_kernel(self, kernel_id=None, path=None, **kwargs):
@ -243,7 +408,7 @@ class GatewayKernelManager(MappingKernelManager):
"""Shutdown all kernels."""
# Note: We have to make this sync because the NotebookApp does not wait for async.
kwargs = {'method': 'DELETE'}
kwargs = load_connection_args(**kwargs)
kwargs = Gateway.instance().load_connection_args(**kwargs)
client = HTTPClient()
for kernel_id in self._kernels.keys():
kernel_url = self._get_kernel_endpoint_url(kernel_id)
@ -259,18 +424,9 @@ class GatewayKernelManager(MappingKernelManager):
class GatewayKernelSpecManager(KernelSpecManager):
kernelspecs_endpoint_env = 'GATEWAY_KERNELSPECS_ENDPOINT'
kernelspecs_endpoint = Unicode(config=True,
help="""The kernel gateway API endpoint for accessing kernelspecs
(GATEWAY_KERNELSPECS_ENDPOINT env var)""")
@default('kernelspecs_endpoint')
def kernelspecs_endpoint_default(self):
return os.getenv(self.kernelspecs_endpoint_env, '/api/kernelspecs')
def __init__(self, **kwargs):
super(GatewayKernelSpecManager, self).__init__(**kwargs)
self.gateway_url = self.parent.gateway_url
self.base_endpoint = url_path_join(Gateway.instance().url, Gateway.instance().kernelspecs_endpoint)
def _get_kernelspecs_endpoint_url(self, kernel_name=None):
"""Builds a url for the kernels endpoint
@ -280,9 +436,9 @@ class GatewayKernelSpecManager(KernelSpecManager):
kernel_name: kernel name (optional)
"""
if kernel_name:
return url_path_join(self.gateway_url, self.kernelspecs_endpoint, url_escape(kernel_name))
return url_path_join(self.base_endpoint, url_escape(kernel_name))
return url_path_join(self.gateway_url, self.kernelspecs_endpoint)
return self.base_endpoint
@gen.coroutine
def list_kernel_specs(self):

View File

@ -84,9 +84,7 @@ from .services.contents.manager import ContentsManager
from .services.contents.filemanager import FileContentsManager
from .services.contents.largefilemanager import LargeFileManager
from .services.sessions.sessionmanager import SessionManager
from .gateway.managers import GatewayKernelManager
from .gateway.managers import GatewayKernelSpecManager
from .gateway.managers import GatewaySessionManager
from .gateway.managers import GatewayKernelManager, GatewayKernelSpecManager, GatewaySessionManager, Gateway
from .auth.login import LoginHandler
from .auth.logout import LogoutHandler
@ -153,14 +151,13 @@ class NotebookWebApplication(web.Application):
def __init__(self, jupyter_app, kernel_manager, contents_manager,
session_manager, kernel_spec_manager,
config_manager, extra_services, log,
base_url, default_url, settings_overrides, jinja_env_options,
gateway_url):
base_url, default_url, settings_overrides, jinja_env_options):
settings = self.init_settings(
jupyter_app, kernel_manager, contents_manager,
session_manager, kernel_spec_manager, config_manager,
extra_services, log, base_url,
default_url, settings_overrides, jinja_env_options, gateway_url)
default_url, settings_overrides, jinja_env_options)
handlers = self.init_handlers(settings)
super(NotebookWebApplication, self).__init__(handlers, **settings)
@ -169,7 +166,7 @@ class NotebookWebApplication(web.Application):
session_manager, kernel_spec_manager,
config_manager, extra_services,
log, base_url, default_url, settings_overrides,
jinja_env_options=None, gateway_url=None):
jinja_env_options=None):
_template_path = settings_overrides.get(
"template_path",
@ -283,7 +280,6 @@ class NotebookWebApplication(web.Application):
server_root_dir=root_dir,
jinja2_env=env,
terminals_available=False, # Set later if terminals are available
gateway_url=gateway_url,
)
# allow custom overrides for the tornado web app.
@ -317,7 +313,7 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('notebook.services.shutdown'))
# If gateway server is configured, replace appropriate handlers to perform redirection
if settings['gateway_url']:
if Gateway.instance().gateway_enabled:
handlers.extend(load_handlers('notebook.gateway.handlers'))
else:
handlers.extend(load_handlers('notebook.services.kernels.handlers'))
@ -558,7 +554,7 @@ aliases.update({
'notebook-dir': 'NotebookApp.notebook_dir',
'browser': 'NotebookApp.browser',
'pylab': 'NotebookApp.pylab',
'gateway-url': 'NotebookApp.gateway_url',
'gateway-url': 'Gateway.url',
})
#-----------------------------------------------------------------------------
@ -579,7 +575,7 @@ class NotebookApp(JupyterApp):
classes = [
KernelManager, Session, MappingKernelManager, KernelSpecManager,
ContentsManager, FileContentsManager, NotebookNotary,
GatewayKernelManager, GatewayKernelSpecManager, GatewaySessionManager,
GatewayKernelManager, GatewayKernelSpecManager, GatewaySessionManager, Gateway,
]
flags = Dict(flags)
aliases = Dict(aliases)
@ -1307,20 +1303,6 @@ class NotebookApp(JupyterApp):
is not available.
"""))
gateway_url_env = 'GATEWAY_URL'
@default('gateway_url')
def gateway_url_default(self):
return os.getenv(self.gateway_url_env)
gateway_url = Unicode(default_value=None, allow_none=True, config=True,
help="""The url of the Kernel or Enterprise Gateway server where
kernel specifications are defined and kernel management takes place.
If defined, this Notebook server acts as a proxy for all kernel
management and kernel specification retrieval. (GATEWAY_URL env var)
"""
)
def parse_command_line(self, argv=None):
super(NotebookApp, self).parse_command_line(argv)
@ -1344,7 +1326,9 @@ class NotebookApp(JupyterApp):
def init_configurables(self):
# If gateway server is configured, replace appropriate managers to perform redirection
if self.gateway_url:
self.gateway_config = Gateway.instance(parent=self)
if self.gateway_config.gateway_enabled:
self.kernel_manager_class = 'notebook.gateway.managers.GatewayKernelManager'
self.session_manager_class = 'notebook.gateway.managers.GatewaySessionManager'
self.kernel_spec_manager_class = 'notebook.gateway.managers.GatewayKernelSpecManager'
@ -1414,7 +1398,7 @@ class NotebookApp(JupyterApp):
self.session_manager, self.kernel_spec_manager,
self.config_manager, self.extra_services,
self.log, self.base_url, self.default_url, self.tornado_settings,
self.jinja_environment_options, self.gateway_url,
self.jinja_environment_options,
)
ssl_options = self.ssl_options
if self.certfile:
@ -1694,8 +1678,8 @@ class NotebookApp(JupyterApp):
info += "\n"
# Format the info so that the URL fits on a single line in 80 char display
info += _("The Jupyter Notebook is running at:\n%s") % self.display_url
if self.gateway_url:
info += _("\nKernels will be managed by the Gateway server running at:\n%s") % self.gateway_url
if self.gateway_config.gateway_enabled:
info += _("\nKernels will be managed by the Gateway server running at:\n%s") % self.gateway_config.url
return info
def server_info(self):

View File

@ -91,6 +91,22 @@ class NotebookTestBase(TestCase):
url_path_join(cls.base_url(), path),
**kwargs)
return response
@classmethod
def get_patch_env(cls):
return {
'HOME': cls.home_dir,
'PYTHONPATH': os.pathsep.join(sys.path),
'IPYTHONDIR': pjoin(cls.home_dir, '.ipython'),
'JUPYTER_NO_CONFIG': '1', # needed in the future
'JUPYTER_CONFIG_DIR' : cls.config_dir,
'JUPYTER_DATA_DIR' : cls.data_dir,
'JUPYTER_RUNTIME_DIR': cls.runtime_dir,
}
@classmethod
def get_argv(cls):
return []
@classmethod
def setup_class(cls):
@ -109,15 +125,7 @@ class NotebookTestBase(TestCase):
config_dir = cls.config_dir = tmp('config')
runtime_dir = cls.runtime_dir = tmp('runtime')
cls.notebook_dir = tmp('notebooks')
cls.env_patch = patch.dict('os.environ', {
'HOME': cls.home_dir,
'PYTHONPATH': os.pathsep.join(sys.path),
'IPYTHONDIR': pjoin(cls.home_dir, '.ipython'),
'JUPYTER_NO_CONFIG': '1', # needed in the future
'JUPYTER_CONFIG_DIR' : config_dir,
'JUPYTER_DATA_DIR' : data_dir,
'JUPYTER_RUNTIME_DIR': runtime_dir,
})
cls.env_patch = patch.dict('os.environ', cls.get_patch_env())
cls.env_patch.start()
cls.path_patch = patch.multiple(
jupyter_core.paths,
@ -157,7 +165,7 @@ class NotebookTestBase(TestCase):
# needs to be redone after initialize, which reconfigures logging
app.log.propagate = True
app.log.handlers = []
app.initialize(argv=[])
app.initialize(argv=cls.get_argv())
app.log.propagate = True
app.log.handlers = []
loop = IOLoop.current()

View File

@ -7,6 +7,7 @@ from tornado import gen
from tornado.httpclient import HTTPRequest, HTTPResponse, HTTPError
from traitlets.config import Config
from .launchnotebook import NotebookTestBase
from notebook.gateway.managers import Gateway
try:
from unittest.mock import patch, Mock
@ -137,12 +138,39 @@ mocked_gateway = patch('notebook.gateway.managers.fetch_gateway', mock_fetch_gat
class TestGateway(NotebookTestBase):
mock_gateway_url = 'http://mock-gateway-server:8889'
mock_http_user = 'alice'
@classmethod
def setup_class(cls):
cls.config = Config()
cls.config.NotebookApp.gateway_url = 'http://mock-gateway-server:8889'
Gateway.clear_instance()
super(TestGateway, cls).setup_class()
@classmethod
def teardown_class(cls):
Gateway.clear_instance()
super(TestGateway, cls).teardown_class()
@classmethod
def get_patch_env(cls):
test_env = super(TestGateway, cls).get_patch_env()
test_env.update({'JUPYTER_GATEWAY_URL': TestGateway.mock_gateway_url,
'JUPYTER_GATEWAY_REQUEST_TIMEOUT': '44.4'})
return test_env
@classmethod
def get_argv(cls):
argv = super(TestGateway, cls).get_argv()
argv.extend(['--Gateway.connect_timeout=44.4', '--Gateway.http_user=' + TestGateway.mock_http_user])
return argv
def test_gateway_options(self):
nt.assert_equal(self.notebook.gateway_config.gateway_enabled, True)
nt.assert_equal(self.notebook.gateway_config.url, TestGateway.mock_gateway_url)
nt.assert_equal(self.notebook.gateway_config.http_user, TestGateway.mock_http_user)
nt.assert_equal(self.notebook.gateway_config.connect_timeout, self.notebook.gateway_config.connect_timeout)
nt.assert_equal(self.notebook.gateway_config.connect_timeout, 44.4)
def test_gateway_class_mappings(self):
# Ensure appropriate class mappings are in place.
nt.assert_equal(self.notebook.kernel_manager_class.__name__, 'GatewayKernelManager')