Skip to content

Commit

Permalink
Update gateway support with recent changes
Browse files Browse the repository at this point in the history
Convey notebook working directory to the gateway (nb2kg pr-21)
Support retrieval of kernelspec resources from the gateway (nb2kg pr-23)
  • Loading branch information
kevin-bates committed Feb 26, 2019
1 parent d145301 commit 46bcf78
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 15 deletions.
21 changes: 20 additions & 1 deletion notebook/gateway/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

import os
import logging
import mimetypes

from ..base.handlers import IPythonHandler
from ..base.handlers import APIHandler, IPythonHandler
from ..utils import url_path_join

from tornado import gen, web
Expand Down Expand Up @@ -200,8 +201,26 @@ def on_close(self):
self._disconnect()


class GatewayResourceHandler(APIHandler):
"""Retrieves resources for specific kernelspec definitions from kernel/enterprise gateway."""

@web.authenticated
@gen.coroutine
def get(self, kernel_name, path, include_body=True):
ksm = self.kernel_spec_manager
kernel_spec_res = yield ksm.get_kernel_spec_resource(kernel_name, path)
if kernel_spec_res is None:
self.log.warning("Kernelspec resource '{}' for '{}' not found. Gateway may not support"
" resource serving.".format(path, kernel_name))
else:
self.set_header("Content-Type", mimetypes.guess_type(path)[0])
self.finish(kernel_spec_res)


from ..services.kernels.handlers import _kernel_id_regex
from ..services.kernelspecs.handlers import kernel_name_regex

default_handlers = [
(r"/api/kernels/%s/channels" % _kernel_id_regex, WebSocketChannelsHandler),
(r"/kernelspecs/%s/(?P<path>.*)" % kernel_name_regex, GatewayResourceHandler),
]
56 changes: 45 additions & 11 deletions notebook/gateway/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ def _kernels_endpoint_default(self):
def _kernelspecs_endpoint_default(self):
return os.environ.get(self.kernelspecs_endpoint_env, self.kernelspecs_endpoint_default_value)

kernelspecs_resource_endpoint_default_value = '/kernelspecs'
kernelspecs_resource_endpoint_env = 'JUPYTER_GATEWAY_KERNELSPECS_RESOURCE_ENDPOINT'
kernelspecs_resource_endpoint = Unicode(default_value=kernelspecs_resource_endpoint_default_value, config=True,
help="""The gateway endpoint for accessing kernelspecs resources
(JUPYTER_GATEWAY_KERNELSPECS_RESOURCE_ENDPOINT env var)""")

@default('kernelspecs_resource_endpoint')
def _kernelspecs_resource_endpoint_default(self):
return os.environ.get(self.kernelspecs_resource_endpoint_env, self.kernelspecs_resource_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,
Expand Down Expand Up @@ -333,6 +343,11 @@ def start_kernel(self, kernel_id=None, path=None, **kwargs):

kernel_env = {k: v for (k, v) in dict(os.environ).items() if k.startswith('KERNEL_')
or k in GatewayClient.instance().env_whitelist.split(",")}

# Convey the full path to where this notebook file is located.
if path is not None and kernel_env.get('KERNEL_WORKING_DIR') is None:
kernel_env['KERNEL_WORKING_DIR'] = kwargs['cwd']

json_body = json_encode({'name': kernel_name, 'env': kernel_env})

response = yield gateway_request(kernel_url, method='POST', body=json_body)
Expand Down Expand Up @@ -467,7 +482,10 @@ class GatewayKernelSpecManager(KernelSpecManager):

def __init__(self, **kwargs):
super(GatewayKernelSpecManager, self).__init__(**kwargs)
self.base_endpoint = url_path_join(GatewayClient.instance().url, GatewayClient.instance().kernelspecs_endpoint)
self.base_endpoint = url_path_join(GatewayClient.instance().url,
GatewayClient.instance().kernelspecs_endpoint)
self.base_resource_endpoint = url_path_join(GatewayClient.instance().url,
GatewayClient.instance().kernelspecs_resource_endpoint)

def _get_kernelspecs_endpoint_url(self, kernel_name=None):
"""Builds a url for the kernels endpoint
Expand Down Expand Up @@ -498,14 +516,7 @@ def get_all_specs(self):
notebook_default=km.default_kernel_name))
km.default_kernel_name = remote_default_kernel_name

# gateway doesn't support resources (requires transfer for use by NB client)
# so add `resource_dir` to each kernelspec and value of 'not supported in gateway mode'
remote_kspecs = fetched_kspecs.get('kernelspecs')
for kernel_name, kspec_info in remote_kspecs.items():
if not kspec_info.get('resource_dir'):
kspec_info['resource_dir'] = 'not supported in gateway mode'
remote_kspecs[kernel_name].update(kspec_info)

raise gen.Return(remote_kspecs)

@gen.coroutine
Expand Down Expand Up @@ -540,9 +551,32 @@ def get_kernel_spec(self, kernel_name, **kwargs):
raise
else:
kernel_spec = json_decode(response.body)
# Convert to instance of Kernelspec
kspec_instance = self.kernel_spec_class(resource_dir=u'', **kernel_spec['spec'])
raise gen.Return(kspec_instance)

raise gen.Return(kernel_spec)

@gen.coroutine
def get_kernel_spec_resource(self, kernel_name, path):
"""Get kernel spec for kernel_name.
Parameters
----------
kernel_name : str
The name of the kernel.
path : str
The name of the desired resource
"""
kernel_spec_resource_url = url_path_join(self.base_resource_endpoint, str(kernel_name), str(path))
self.log.debug("Request kernel spec resource '{}' at: {}".format(path, kernel_spec_resource_url))
try:
response = yield gateway_request(kernel_spec_resource_url, method='GET')
except HTTPError as error:
if error.code == 404:
kernel_spec_resource = None
else:
raise
else:
kernel_spec_resource = response.body
raise gen.Return(kernel_spec_resource)


class GatewaySessionManager(SessionManager):
Expand Down
18 changes: 15 additions & 3 deletions notebook/services/kernelspecs/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ...base.handlers import APIHandler
from ...utils import url_path_join, url_unescape


def kernelspec_model(handler, name, spec_dict, resource_dir):
"""Load a KernelSpec by name and return the REST API model"""
d = {
Expand Down Expand Up @@ -45,6 +46,12 @@ def kernelspec_model(handler, name, spec_dict, resource_dir):
)
return d


def is_kernelspec_model(spec_dict):
"""Returns True if spec_dict is already in proper form. This will occur when using a gateway."""
return isinstance(spec_dict, dict) and 'name' in spec_dict and 'spec' in spec_dict and 'resources' in spec_dict


class MainKernelSpecHandler(APIHandler):

@web.authenticated
Expand All @@ -58,8 +65,10 @@ def get(self):
kspecs = yield gen.maybe_future(ksm.get_all_specs())
for kernel_name, kernel_info in kspecs.items():
try:
d = kernelspec_model(self, kernel_name, kernel_info['spec'],
kernel_info['resource_dir'])
if is_kernelspec_model(kernel_info):
d = kernel_info
else:
d = kernelspec_model(self, kernel_name, kernel_info['spec'], kernel_info['resource_dir'])
except Exception:
self.log.error("Failed to load kernel spec: '%s'", kernel_name, exc_info=True)
continue
Expand All @@ -79,7 +88,10 @@ def get(self, kernel_name):
spec = yield gen.maybe_future(ksm.get_kernel_spec(kernel_name))
except KeyError:
raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name)
model = kernelspec_model(self, kernel_name, spec.to_dict(), spec.resource_dir)
if is_kernelspec_model(spec):
model = spec
else:
model = kernelspec_model(self, kernel_name, spec.to_dict(), spec.resource_dir)
self.set_header("Content-Type", 'application/json')
self.finish(json.dumps(model))

Expand Down

0 comments on commit 46bcf78

Please sign in to comment.