mirror of
https://github.com/jupyter/notebook.git
synced 2025-02-23 12:49:41 +08:00
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:
parent
060a2b9aea
commit
f74ef2f691
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user