test_kernels_api.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. """Test the kernels service API."""
  2. import json
  3. import time
  4. from tornado.httpclient import HTTPRequest
  5. from tornado.ioloop import IOLoop
  6. from tornado.websocket import websocket_connect
  7. from jupyter_client.kernelspec import NATIVE_KERNEL_NAME
  8. from notebook.utils import url_path_join
  9. from notebook.tests.launchnotebook import NotebookTestBase, assert_http_error
  10. class KernelAPI(object):
  11. """Wrapper for kernel REST API requests"""
  12. def __init__(self, request, base_url, headers):
  13. self.request = request
  14. self.base_url = base_url
  15. self.headers = headers
  16. def _req(self, verb, path, body=None):
  17. response = self.request(verb,
  18. url_path_join('api/kernels', path), data=body)
  19. if 400 <= response.status_code < 600:
  20. try:
  21. response.reason = response.json()['message']
  22. except:
  23. pass
  24. response.raise_for_status()
  25. return response
  26. def list(self):
  27. return self._req('GET', '')
  28. def get(self, id):
  29. return self._req('GET', id)
  30. def start(self, name=NATIVE_KERNEL_NAME):
  31. body = json.dumps({'name': name})
  32. return self._req('POST', '', body)
  33. def shutdown(self, id):
  34. return self._req('DELETE', id)
  35. def interrupt(self, id):
  36. return self._req('POST', url_path_join(id, 'interrupt'))
  37. def restart(self, id):
  38. return self._req('POST', url_path_join(id, 'restart'))
  39. def websocket(self, id):
  40. loop = IOLoop()
  41. loop.make_current()
  42. req = HTTPRequest(
  43. url_path_join(self.base_url.replace('http', 'ws', 1), 'api/kernels', id, 'channels'),
  44. headers=self.headers,
  45. )
  46. f = websocket_connect(req)
  47. return loop.run_sync(lambda : f)
  48. class KernelAPITest(NotebookTestBase):
  49. """Test the kernels web service API"""
  50. def setUp(self):
  51. self.kern_api = KernelAPI(self.request,
  52. base_url=self.base_url(),
  53. headers=self.auth_headers(),
  54. )
  55. def tearDown(self):
  56. for k in self.kern_api.list().json():
  57. self.kern_api.shutdown(k['id'])
  58. def test_no_kernels(self):
  59. """Make sure there are no kernels running at the start"""
  60. kernels = self.kern_api.list().json()
  61. self.assertEqual(kernels, [])
  62. def test_default_kernel(self):
  63. # POST request
  64. r = self.kern_api._req('POST', '')
  65. kern1 = r.json()
  66. self.assertEqual(r.headers['location'], url_path_join(self.url_prefix, 'api/kernels', kern1['id']))
  67. self.assertEqual(r.status_code, 201)
  68. self.assertIsInstance(kern1, dict)
  69. report_uri = url_path_join(self.url_prefix, 'api/security/csp-report')
  70. expected_csp = '; '.join([
  71. "frame-ancestors 'self'",
  72. 'report-uri ' + report_uri,
  73. "default-src 'none'"
  74. ])
  75. self.assertEqual(r.headers['Content-Security-Policy'], expected_csp)
  76. def test_main_kernel_handler(self):
  77. # POST request
  78. r = self.kern_api.start()
  79. kern1 = r.json()
  80. self.assertEqual(r.headers['location'], url_path_join(self.url_prefix, 'api/kernels', kern1['id']))
  81. self.assertEqual(r.status_code, 201)
  82. self.assertIsInstance(kern1, dict)
  83. report_uri = url_path_join(self.url_prefix, 'api/security/csp-report')
  84. expected_csp = '; '.join([
  85. "frame-ancestors 'self'",
  86. 'report-uri ' + report_uri,
  87. "default-src 'none'"
  88. ])
  89. self.assertEqual(r.headers['Content-Security-Policy'], expected_csp)
  90. # GET request
  91. r = self.kern_api.list()
  92. self.assertEqual(r.status_code, 200)
  93. assert isinstance(r.json(), list)
  94. self.assertEqual(r.json()[0]['id'], kern1['id'])
  95. self.assertEqual(r.json()[0]['name'], kern1['name'])
  96. # create another kernel and check that they both are added to the
  97. # list of kernels from a GET request
  98. kern2 = self.kern_api.start().json()
  99. assert isinstance(kern2, dict)
  100. r = self.kern_api.list()
  101. kernels = r.json()
  102. self.assertEqual(r.status_code, 200)
  103. assert isinstance(kernels, list)
  104. self.assertEqual(len(kernels), 2)
  105. # Interrupt a kernel
  106. r = self.kern_api.interrupt(kern2['id'])
  107. self.assertEqual(r.status_code, 204)
  108. # Restart a kernel
  109. r = self.kern_api.restart(kern2['id'])
  110. rekern = r.json()
  111. self.assertEqual(rekern['id'], kern2['id'])
  112. self.assertEqual(rekern['name'], kern2['name'])
  113. def test_kernel_handler(self):
  114. # GET kernel with given id
  115. kid = self.kern_api.start().json()['id']
  116. r = self.kern_api.get(kid)
  117. kern1 = r.json()
  118. self.assertEqual(r.status_code, 200)
  119. assert isinstance(kern1, dict)
  120. self.assertIn('id', kern1)
  121. self.assertEqual(kern1['id'], kid)
  122. # Request a bad kernel id and check that a JSON
  123. # message is returned!
  124. bad_id = '111-111-111-111-111'
  125. with assert_http_error(404, 'Kernel does not exist: ' + bad_id):
  126. self.kern_api.get(bad_id)
  127. # DELETE kernel with id
  128. r = self.kern_api.shutdown(kid)
  129. self.assertEqual(r.status_code, 204)
  130. kernels = self.kern_api.list().json()
  131. self.assertEqual(kernels, [])
  132. # Request to delete a non-existent kernel id
  133. bad_id = '111-111-111-111-111'
  134. with assert_http_error(404, 'Kernel does not exist: ' + bad_id):
  135. self.kern_api.shutdown(bad_id)
  136. def test_connections(self):
  137. kid = self.kern_api.start().json()['id']
  138. model = self.kern_api.get(kid).json()
  139. self.assertEqual(model['connections'], 0)
  140. ws = self.kern_api.websocket(kid)
  141. model = self.kern_api.get(kid).json()
  142. self.assertEqual(model['connections'], 1)
  143. ws.close()
  144. # give it some time to close on the other side:
  145. for i in range(10):
  146. model = self.kern_api.get(kid).json()
  147. if model['connections'] > 0:
  148. time.sleep(0.1)
  149. else:
  150. break
  151. model = self.kern_api.get(kid).json()
  152. self.assertEqual(model['connections'], 0)