diff --git a/notebook/gateway/handlers.py b/notebook/gateway/handlers.py index 30098787e41..1e28bfa779f 100644 --- a/notebook/gateway/handlers.py +++ b/notebook/gateway/handlers.py @@ -21,27 +21,7 @@ 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 @@ def authenticate(self): 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 @@ def __init__(self, **kwargs): 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 - - request = HTTPRequest(ws_url, **parameters) + kwargs = {} + kwargs = Gateway.instance().load_connection_args(**kwargs) + + request = HTTPRequest(ws_url, **kwargs) self.ws_future = websocket_connect(request) self.ws_future.add_done_callback(self._connection_done) @@ -178,7 +142,7 @@ def _connection_done(self, fut): 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 @@ def get(self): # 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() diff --git a/notebook/gateway/managers.py b/notebook/gateway/managers.py index 31dcba9cdce..f8624cbeb3f 100644 --- a/notebook/gateway/managers.py +++ b/notebook/gateway/managers.py @@ -14,78 +14,243 @@ from jupyter_client.kernelspec import KernelSpecManager from ..utils import url_path_join -from traitlets import Instance, Unicode, default +from traitlets import Instance, Unicode, Float, Bool, default, validate, TraitError +from traitlets.config import SingletonConfigurable -# 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'] +@gen.coroutine +def fetch_gateway(endpoint, **kwargs): + """Make an async request to kernel gateway endpoint.""" + client = AsyncHTTPClient() -GATEWAY_CLIENT_KEY = os.getenv('GATEWAY_CLIENT_KEY') -GATEWAY_CLIENT_CERT = os.getenv('GATEWAY_CLIENT_CERT') -GATEWAY_CLIENT_CA = os.getenv('GATEWAY_CLIENT_CA') + kwargs = Gateway.instance().load_connection_args(**kwargs) -GATEWAY_HTTP_USER = os.getenv('GATEWAY_HTTP_USER') -GATEWAY_HTTP_PASS = os.getenv('GATEWAY_HTTP_PASS') + response = yield client.fetch(endpoint, **kwargs) + raise gen.Return(response) -GATEWAY_CONNECT_TIMEOUT = float(os.getenv('GATEWAY_CONNECT_TIMEOUT', 20.0)) -GATEWAY_REQUEST_TIMEOUT = float(os.getenv('GATEWAY_REQUEST_TIMEOUT', 20.0)) +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. -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) + 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.getenv(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)""") - return kwargs + @default('kernels_endpoint') + def _kernels_endpoint_default(self): + return os.getenv(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)""") -@gen.coroutine -def fetch_gateway(endpoint, **kwargs): - """Make an async request to kernel gateway endpoint.""" - client = AsyncHTTPClient() + @default('kernelspecs_endpoint') + def _kernelspecs_endpoint_default(self): + return os.getenv(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.getenv('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.getenv('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' - kwargs = load_connection_args(**kwargs) + @default('client_key') + def _client_key_default(self): + return os.getenv(self.client_key_env) - response = yield client.fetch(endpoint, **kwargs) - raise gen.Return(response) + 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.getenv(self.client_cert_env) -class GatewayKernelManager(MappingKernelManager): - """Kernel manager that supports remote kernels hosted by Jupyter - kernel gateway.""" + 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' - 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)""") + @default('ca_certs') + def _ca_certs_default(self): + return os.getenv(self.ca_certs_env) - @default('kernels_endpoint') - def kernels_endpoint_default(self): - return os.getenv(self.kernels_endpoint_env, '/api/kernels') + 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.getenv(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.getenv(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.getenv(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.getenv(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.getenv(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 @@ def _get_kernel_endpoint_url(self, kernel_id=None): 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 @@ def shutdown_all(self): """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 @@ def shutdown_all(self): 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 @@ def _get_kernelspecs_endpoint_url(self, kernel_name=None): 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): diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 9371e6a932b..f27bf2ed060 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -83,9 +83,7 @@ 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 @@ -152,14 +150,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) @@ -168,7 +165,7 @@ def init_settings(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=None, gateway_url=None): + jinja_env_options=None): _template_path = settings_overrides.get( "template_path", @@ -282,7 +279,6 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager, 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. @@ -316,7 +312,7 @@ def init_handlers(self, settings): 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')) @@ -557,7 +553,7 @@ def start(self): 'notebook-dir': 'NotebookApp.notebook_dir', 'browser': 'NotebookApp.browser', 'pylab': 'NotebookApp.pylab', - 'gateway-url': 'NotebookApp.gateway_url', + 'gateway-url': 'Gateway.url', }) #----------------------------------------------------------------------------- @@ -578,7 +574,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) @@ -1305,20 +1301,6 @@ def _update_server_extensions(self, change): 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) @@ -1342,7 +1324,9 @@ def parse_command_line(self, argv=None): 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' @@ -1415,7 +1399,7 @@ def init_webapp(self): 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: @@ -1695,8 +1679,8 @@ def notebook_info(self, kernel_count=True): 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): diff --git a/notebook/tests/launchnotebook.py b/notebook/tests/launchnotebook.py index 1b685df0ca4..9e84a5964ba 100644 --- a/notebook/tests/launchnotebook.py +++ b/notebook/tests/launchnotebook.py @@ -91,6 +91,22 @@ def request(cls, verb, path, **kwargs): 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 @@ def tmp(*parts): 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 @@ def start_thread(): # 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() diff --git a/notebook/tests/test_gateway.py b/notebook/tests/test_gateway.py index 385c1f1cd55..a007046966c 100644 --- a/notebook/tests/test_gateway.py +++ b/notebook/tests/test_gateway.py @@ -7,6 +7,7 @@ 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 @@ def mock_fetch_gateway(url, **kwargs): 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')