Skip to content

Commit

Permalink
Minimize handlers and manager methods
Browse files Browse the repository at this point in the history
Eliminated the Kernel and Kernelspec handlers.  The Websocket (ZMQ)
channels handler still remains.  This required turning a few methods
into coroutines in the Notebook server.

Renamed the Gateway config object to GatewayClient in case we want
to extend NB server (probably jupyter_server at that point) with
Gateway server functionality - so an NB server could be a Gateway
client or a server depending on launch settings.

Add code to _replace_ the channels handler rather than rely on position
within the handlers lists.

Updated mock-gateway to return the appropriate form of results.
  • Loading branch information
kevin-bates committed Dec 6, 2018
1 parent b82f26d commit 73411de
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 412 deletions.
2 changes: 1 addition & 1 deletion docs/source/public_server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ or in :file:`jupyter_notebook_config.py`:

.. code-block:: python
c.Gateway.url = http://my-gateway-server:8888
c.GatewayClient.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
Expand Down
165 changes: 9 additions & 156 deletions notebook/gateway/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,23 @@
# Distributed under the terms of the Modified BSD License.

import os
import json
import logging
from socket import gaierror

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

from tornado import gen, web
from tornado.concurrent import Future
from tornado.ioloop import IOLoop
from tornado.websocket import WebSocketHandler, websocket_connect
from tornado.httpclient import HTTPRequest
from tornado.simple_httpclient import HTTPTimeoutError
from tornado.escape import url_escape, json_decode, utf8

from ipython_genutils.py3compat import cast_unicode
from jupyter_client.session import Session
from traitlets.config.configurable import LoggingConfigurable

from .managers import Gateway
from .managers import GatewayClient


class WebSocketChannelsHandler(WebSocketHandler, IPythonHandler):
Expand Down Expand Up @@ -57,7 +54,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=Gateway.instance().url)
self.gateway = GatewayWebSocketClient(gateway_url=GatewayClient.instance().url)

@gen.coroutine
def get(self, kernel_id, *args, **kwargs):
Expand Down Expand Up @@ -124,12 +121,12 @@ def __init__(self, **kwargs):
def _connect(self, kernel_id):
self.kernel_id = kernel_id
ws_url = url_path_join(
Gateway.instance().ws_url,
Gateway.instance().kernels_endpoint, url_escape(kernel_id), 'channels'
GatewayClient.instance().ws_url,
GatewayClient.instance().kernels_endpoint, url_escape(kernel_id), 'channels'
)
self.log.info('Connecting to {}'.format(ws_url))
kwargs = {}
kwargs = Gateway.instance().load_connection_args(**kwargs)
kwargs = GatewayClient.instance().load_connection_args(**kwargs)

request = HTTPRequest(ws_url, **kwargs)
self.ws_future = websocket_connect(request)
Expand All @@ -141,8 +138,8 @@ def _connection_done(self, fut):
self.log.debug("Connection is ready: ws: {}".format(self.ws))
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, Gateway.instance().url))
"Kernel with ID '{}' may not be terminated on GatewayClient: {}".
format(self.kernel_id, GatewayClient.instance().url))

def _disconnect(self):
if self.ws is not None:
Expand Down Expand Up @@ -203,152 +200,8 @@ def on_close(self):
self._disconnect()


# -----------------------------------------------------------------------------
# kernel handlers
# -----------------------------------------------------------------------------

class MainKernelHandler(APIHandler):
"""Replace default MainKernelHandler to enable async lookup of kernels."""

@web.authenticated
@gen.coroutine
def get(self):
km = self.kernel_manager
kernels = yield gen.maybe_future(km.list_kernels())
self.finish(json.dumps(kernels))

@web.authenticated
@gen.coroutine
def post(self):
km = self.kernel_manager
model = self.get_json_body()
if model is None:
model = {
'name': km.default_kernel_name
}
else:
model.setdefault('name', km.default_kernel_name)

kernel_id = yield gen.maybe_future(km.start_kernel(kernel_name=model['name']))
# This is now an async operation
model = yield gen.maybe_future(km.kernel_model(kernel_id))
location = url_path_join(self.base_url, 'api', 'kernels', url_escape(kernel_id))
self.set_header('Location', location)
self.set_status(201)
self.finish(json.dumps(model))


class KernelHandler(APIHandler):
"""Replace default KernelHandler to enable async lookup of kernels."""

@web.authenticated
@gen.coroutine
def get(self, kernel_id):
km = self.kernel_manager
# This is now an async operation
model = yield gen.maybe_future(km.kernel_model(kernel_id))
if model is None:
raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
self.finish(json.dumps(model))

@web.authenticated
@gen.coroutine
def delete(self, kernel_id):
km = self.kernel_manager
yield gen.maybe_future(km.shutdown_kernel(kernel_id))
self.set_status(204)
self.finish()


class KernelActionHandler(APIHandler):
"""Replace default KernelActionHandler to enable async lookup of kernels."""

@web.authenticated
@gen.coroutine
def post(self, kernel_id, action):
km = self.kernel_manager

if action == 'interrupt':
km.interrupt_kernel(kernel_id)
self.set_status(204)

if action == 'restart':
try:
yield gen.maybe_future(km.restart_kernel(kernel_id))
except Exception as e:
self.log.error("Exception restarting kernel", exc_info=True)
self.set_status(500)
else:
# This is now an async operation
model = yield gen.maybe_future(km.kernel_model(kernel_id))
self.write(json.dumps(model))
self.finish()

# -----------------------------------------------------------------------------
# kernel spec handlers
# -----------------------------------------------------------------------------


class MainKernelSpecHandler(APIHandler):
@web.authenticated
@gen.coroutine
def get(self):
ksm = self.kernel_spec_manager
try:
kernel_specs = yield gen.maybe_future(ksm.list_kernel_specs())
# TODO: Remove resources until we support them
for name, spec in kernel_specs['kernelspecs'].items():
spec['resources'] = {}
self.set_header("Content-Type", 'application/json')
self.write(json.dumps(kernel_specs))

# Trap a set of common exceptions so that we can inform the user that their Gateway url is incorrect
# or the server is not running.
# NOTE: We do this here since this handler is called during the Notebook's startup and subsequent refreshes
# of the tree view.
except ConnectionRefusedError:
self.log.error("Connection refused from Gateway server 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.
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.instance().url))
except gaierror as e:
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.instance().url))

self.finish()


class KernelSpecHandler(APIHandler):
@web.authenticated
@gen.coroutine
def get(self, kernel_name):
ksm = self.kernel_spec_manager
kernel_spec = yield ksm.get_kernel_spec(kernel_name)
if kernel_spec is None:
raise web.HTTPError(404, u'Kernel spec %s not found' % kernel_name)
# TODO: Remove resources until we support them
kernel_spec['resources'] = {}
self.set_header("Content-Type", 'application/json')
self.finish(json.dumps(kernel_spec))

# -----------------------------------------------------------------------------
# URL to handler mappings
# -----------------------------------------------------------------------------


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

default_handlers = [
(r"/api/kernels", MainKernelHandler),
(r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
(r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
(r"/api/kernels/%s/channels" % _kernel_id_regex, WebSocketChannelsHandler),
(r"/api/kernelspecs", MainKernelSpecHandler),
(r"/api/kernelspecs/%s" % kernel_name_regex, KernelSpecHandler),
# TODO: support kernel spec resources
# (r"/kernelspecs/%s/(?P<path>.*)" % kernel_name_regex, KernelSpecResourceHandler),

]
Loading

0 comments on commit 73411de

Please sign in to comment.