##// END OF EJS Templates
manual rebase services/kernels/
Zachary Sailer -
Show More
@@ -1,182 +1,187 b''
1 1 """Tornado handlers for the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import logging
20 20 from tornado import web
21 21
22 22 from zmq.utils import jsonapi
23 23
24 24 from IPython.utils.jsonutil import date_default
25 25
26 26 from ...base.handlers import IPythonHandler
27 27 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Kernel handlers
31 31 #-----------------------------------------------------------------------------
32 32
33 33
34 34 class MainKernelHandler(IPythonHandler):
35 35
36 36 @web.authenticated
37 37 def get(self):
38 38 km = self.kernel_manager
39 self.finish(jsonapi.dumps(km.list_kernel_ids()))
39 self.finish(jsonapi.dumps(km.list_kernels()))
40 40
41 41 @web.authenticated
42 42 def post(self):
43 43 km = self.kernel_manager
44 44 nbm = self.notebook_manager
45 notebook_id = self.get_argument('notebook', default=None)
46 kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
47 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
45 kernel_id = km.start_kernel(cwd=nbm.notebook_dir)
46 model = km.kernel_model(kernel_id, self.ws_url)
48 47 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
49 self.finish(jsonapi.dumps(data))
48 self.finish(jsonapi.dumps(model))
50 49
51 50
52 51 class KernelHandler(IPythonHandler):
53 52
54 SUPPORTED_METHODS = ('DELETE')
53 SUPPORTED_METHODS = ('DELETE', 'GET')
54
55 @web.authenticated
56 def get(self, kernel_id):
57 km = self.kernel_manager
58 model = km.kernel_model(kernel_id,self.ws_url)
59 self.finish(jsonapi.dumps(model))
55 60
56 61 @web.authenticated
57 62 def delete(self, kernel_id):
58 63 km = self.kernel_manager
59 64 km.shutdown_kernel(kernel_id)
60 65 self.set_status(204)
61 66 self.finish()
62 67
63 68
64 69 class KernelActionHandler(IPythonHandler):
65 70
66 71 @web.authenticated
67 72 def post(self, kernel_id, action):
68 73 km = self.kernel_manager
69 74 if action == 'interrupt':
70 75 km.interrupt_kernel(kernel_id)
71 76 self.set_status(204)
72 77 if action == 'restart':
73 78 km.restart_kernel(kernel_id)
74 data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
79 model = km.kernel_model(kernel_id,self.ws_url)
75 80 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
76 self.write(jsonapi.dumps(data))
81 self.write(jsonapi.dumps(model))
77 82 self.finish()
78 83
79 84
80 85 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
81 86
82 87 def create_stream(self):
83 88 km = self.kernel_manager
84 89 meth = getattr(km, 'connect_%s' % self.channel)
85 90 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
86 91
87 92 def initialize(self, *args, **kwargs):
88 93 self.zmq_stream = None
89 94
90 95 def on_first_message(self, msg):
91 96 try:
92 97 super(ZMQChannelHandler, self).on_first_message(msg)
93 98 except web.HTTPError:
94 99 self.close()
95 100 return
96 101 try:
97 102 self.create_stream()
98 103 except web.HTTPError:
99 104 # WebSockets don't response to traditional error codes so we
100 105 # close the connection.
101 106 if not self.stream.closed():
102 107 self.stream.close()
103 108 self.close()
104 109 else:
105 110 self.zmq_stream.on_recv(self._on_zmq_reply)
106 111
107 112 def on_message(self, msg):
108 113 msg = jsonapi.loads(msg)
109 114 self.session.send(self.zmq_stream, msg)
110 115
111 116 def on_close(self):
112 117 # This method can be called twice, once by self.kernel_died and once
113 118 # from the WebSocket close event. If the WebSocket connection is
114 119 # closed before the ZMQ streams are setup, they could be None.
115 120 if self.zmq_stream is not None and not self.zmq_stream.closed():
116 121 self.zmq_stream.on_recv(None)
117 122 self.zmq_stream.close()
118 123
119 124
120 125 class IOPubHandler(ZMQChannelHandler):
121 126 channel = 'iopub'
122 127
123 128 def create_stream(self):
124 129 super(IOPubHandler, self).create_stream()
125 130 km = self.kernel_manager
126 131 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
127 132 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
128 133
129 134 def on_close(self):
130 135 km = self.kernel_manager
131 136 if self.kernel_id in km:
132 137 km.remove_restart_callback(
133 138 self.kernel_id, self.on_kernel_restarted,
134 139 )
135 140 km.remove_restart_callback(
136 141 self.kernel_id, self.on_restart_failed, 'dead',
137 142 )
138 143 super(IOPubHandler, self).on_close()
139 144
140 145 def _send_status_message(self, status):
141 146 msg = self.session.msg("status",
142 147 {'execution_state': status}
143 148 )
144 149 self.write_message(jsonapi.dumps(msg, default=date_default))
145 150
146 151 def on_kernel_restarted(self):
147 152 logging.warn("kernel %s restarted", self.kernel_id)
148 153 self._send_status_message('restarting')
149 154
150 155 def on_restart_failed(self):
151 156 logging.error("kernel %s restarted failed!", self.kernel_id)
152 157 self._send_status_message('dead')
153 158
154 159 def on_message(self, msg):
155 160 """IOPub messages make no sense"""
156 161 pass
157 162
158 163
159 164 class ShellHandler(ZMQChannelHandler):
160 165 channel = 'shell'
161 166
162 167
163 168 class StdinHandler(ZMQChannelHandler):
164 169 channel = 'stdin'
165 170
166 171
167 172 #-----------------------------------------------------------------------------
168 173 # URL to handler mappings
169 174 #-----------------------------------------------------------------------------
170 175
171 176
172 177 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
173 178 _kernel_action_regex = r"(?P<action>restart|interrupt)"
174 179
175 180 default_handlers = [
176 (r"/kernels", MainKernelHandler),
177 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
178 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
179 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
180 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
181 (r"/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
181 (r"/api/kernels", MainKernelHandler),
182 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
183 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
184 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
185 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
186 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
182 187 ]
@@ -1,110 +1,96 b''
1 1 """A kernel manager relating notebooks and kernels
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from IPython.kernel.multikernelmanager import MultiKernelManager
22 22 from IPython.utils.traitlets import (
23 23 Dict, List, Unicode,
24 24 )
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Classes
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31 class MappingKernelManager(MultiKernelManager):
32 32 """A KernelManager that handles notebook mapping and HTTP error handling"""
33 33
34 34 def _kernel_manager_class_default(self):
35 35 return "IPython.kernel.ioloop.IOLoopKernelManager"
36 36
37 37 kernel_argv = List(Unicode)
38
39 _notebook_mapping = Dict()
38 kernels = []
40 39
41 40 #-------------------------------------------------------------------------
42 41 # Methods for managing kernels and sessions
43 42 #-------------------------------------------------------------------------
44 43
45 def kernel_for_notebook(self, notebook_id):
46 """Return the kernel_id for a notebook_id or None."""
47 return self._notebook_mapping.get(notebook_id)
48
49 def set_kernel_for_notebook(self, notebook_id, kernel_id):
50 """Associate a notebook with a kernel."""
51 if notebook_id is not None:
52 self._notebook_mapping[notebook_id] = kernel_id
53
54 def notebook_for_kernel(self, kernel_id):
55 """Return the notebook_id for a kernel_id or None."""
56 for notebook_id, kid in self._notebook_mapping.iteritems():
57 if kernel_id == kid:
58 return notebook_id
59 return None
60
61 def delete_mapping_for_kernel(self, kernel_id):
62 """Remove the kernel/notebook mapping for kernel_id."""
63 notebook_id = self.notebook_for_kernel(kernel_id)
64 if notebook_id is not None:
65 del self._notebook_mapping[notebook_id]
66
67 44 def _handle_kernel_died(self, kernel_id):
68 45 """notice that a kernel died"""
69 46 self.log.warn("Kernel %s died, removing from map.", kernel_id)
70 self.delete_mapping_for_kernel(kernel_id)
71 47 self.remove_kernel(kernel_id)
72 48
73 def start_kernel(self, notebook_id=None, **kwargs):
74 """Start a kernel for a notebook an return its kernel_id.
49 def start_kernel(self, **kwargs):
50 """Start a kernel for a session an return its kernel_id.
75 51
76 52 Parameters
77 53 ----------
78 notebook_id : uuid
79 The uuid of the notebook to associate the new kernel with. If this
80 is not None, this kernel will be persistent whenever the notebook
54 session_id : uuid
55 The uuid of the session to associate the new kernel with. If this
56 is not None, this kernel will be persistent whenever the session
81 57 requests a kernel.
82 58 """
83 kernel_id = self.kernel_for_notebook(notebook_id)
59 kernel_id = None
84 60 if kernel_id is None:
85 61 kwargs['extra_arguments'] = self.kernel_argv
86 62 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
87 self.set_kernel_for_notebook(notebook_id, kernel_id)
88 63 self.log.info("Kernel started: %s" % kernel_id)
89 64 self.log.debug("Kernel args: %r" % kwargs)
90 65 # register callback for failed auto-restart
91 66 self.add_restart_callback(kernel_id,
92 67 lambda : self._handle_kernel_died(kernel_id),
93 68 'dead',
94 69 )
95 70 else:
96 71 self.log.info("Using existing kernel: %s" % kernel_id)
97 72
98 73 return kernel_id
99 74
100 75 def shutdown_kernel(self, kernel_id, now=False):
101 76 """Shutdown a kernel by kernel_id"""
77 i = 0
78 for kernel in self.kernels:
79 if kernel['kernel_id'] == kernel_id:
80 del self.kernels[i]
81 i = i+1
102 82 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
103 self.delete_mapping_for_kernel(kernel_id)
83
84 def kernel_model(self, kernel_id, ws_url):
85 model = {"kernel_id":kernel_id, "ws_url": ws_url}
86 self.kernels.append(model)
87 return model
88
89 def list_kernels(self):
90 return self.kernels
104 91
105 92 # override _check_kernel_id to raise 404 instead of KeyError
106 93 def _check_kernel_id(self, kernel_id):
107 94 """Check a that a kernel_id exists and raise 404 if not."""
108 95 if kernel_id not in self:
109 96 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
110
General Comments 0
You need to be logged in to leave comments. Login now