Skip to content

Commit 062b5f4

Browse files
committed
Restructure files
1 parent fc8a057 commit 062b5f4

File tree

2 files changed

+712
-320
lines changed

2 files changed

+712
-320
lines changed
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
# Copyright (c) Jupyter Development Team.
2+
# Distributed under the terms of the Modified BSD License.
3+
4+
import json
5+
import os
6+
7+
from socket import gaierror
8+
from tornado import web
9+
from tornado.httpclient import AsyncHTTPClient, HTTPError
10+
from traitlets import Unicode, Int, Float, Bool, default, validate, TraitError
11+
from traitlets.config import SingletonConfigurable
12+
13+
14+
class GatewayClient(SingletonConfigurable):
15+
"""This class manages the configuration. It's its own singleton class so that we
16+
can share these values across all objects. It also contains some helper methods
17+
to build request arguments out of the various config options.
18+
19+
"""
20+
21+
url = Unicode(default_value=None, allow_none=True, config=True,
22+
help="""The url of the Kernel or Enterprise Gateway server where
23+
kernel specifications are defined and kernel management takes place.
24+
If defined, this Notebook server acts as a proxy for all kernel
25+
management and kernel specification retrieval. (JUPYTER_GATEWAY_URL env var)
26+
"""
27+
)
28+
29+
url_env = 'JUPYTER_GATEWAY_URL'
30+
31+
@default('url')
32+
def _url_default(self):
33+
return os.environ.get(self.url_env)
34+
35+
@validate('url')
36+
def _url_validate(self, proposal):
37+
value = proposal['value']
38+
# Ensure value, if present, starts with 'http'
39+
if value is not None and len(value) > 0:
40+
if not str(value).lower().startswith('http'):
41+
raise TraitError("GatewayClient url must start with 'http': '%r'" % value)
42+
return value
43+
44+
ws_url = Unicode(default_value=None, allow_none=True, config=True,
45+
help="""The websocket url of the Kernel or Enterprise Gateway server. If not provided, this value
46+
will correspond to the value of the Gateway url with 'ws' in place of 'http'. (JUPYTER_GATEWAY_WS_URL env var)
47+
"""
48+
)
49+
50+
ws_url_env = 'JUPYTER_GATEWAY_WS_URL'
51+
52+
@default('ws_url')
53+
def _ws_url_default(self):
54+
default_value = os.environ.get(self.ws_url_env)
55+
if default_value is None:
56+
if self.gateway_enabled:
57+
default_value = self.url.lower().replace('http', 'ws')
58+
return default_value
59+
60+
@validate('ws_url')
61+
def _ws_url_validate(self, proposal):
62+
value = proposal['value']
63+
# Ensure value, if present, starts with 'ws'
64+
if value is not None and len(value) > 0:
65+
if not str(value).lower().startswith('ws'):
66+
raise TraitError("GatewayClient ws_url must start with 'ws': '%r'" % value)
67+
return value
68+
69+
kernels_endpoint_default_value = '/api/kernels'
70+
kernels_endpoint_env = 'JUPYTER_GATEWAY_KERNELS_ENDPOINT'
71+
kernels_endpoint = Unicode(default_value=kernels_endpoint_default_value, config=True,
72+
help="""The gateway API endpoint for accessing kernel resources (JUPYTER_GATEWAY_KERNELS_ENDPOINT env var)""")
73+
74+
@default('kernels_endpoint')
75+
def _kernels_endpoint_default(self):
76+
return os.environ.get(self.kernels_endpoint_env, self.kernels_endpoint_default_value)
77+
78+
kernelspecs_endpoint_default_value = '/api/kernelspecs'
79+
kernelspecs_endpoint_env = 'JUPYTER_GATEWAY_KERNELSPECS_ENDPOINT'
80+
kernelspecs_endpoint = Unicode(default_value=kernelspecs_endpoint_default_value, config=True,
81+
help="""The gateway API endpoint for accessing kernelspecs (JUPYTER_GATEWAY_KERNELSPECS_ENDPOINT env var)""")
82+
83+
@default('kernelspecs_endpoint')
84+
def _kernelspecs_endpoint_default(self):
85+
return os.environ.get(self.kernelspecs_endpoint_env, self.kernelspecs_endpoint_default_value)
86+
87+
kernelspecs_resource_endpoint_default_value = '/kernelspecs'
88+
kernelspecs_resource_endpoint_env = 'JUPYTER_GATEWAY_KERNELSPECS_RESOURCE_ENDPOINT'
89+
kernelspecs_resource_endpoint = Unicode(default_value=kernelspecs_resource_endpoint_default_value, config=True,
90+
help="""The gateway endpoint for accessing kernelspecs resources
91+
(JUPYTER_GATEWAY_KERNELSPECS_RESOURCE_ENDPOINT env var)""")
92+
93+
@default('kernelspecs_resource_endpoint')
94+
def _kernelspecs_resource_endpoint_default(self):
95+
return os.environ.get(self.kernelspecs_resource_endpoint_env, self.kernelspecs_resource_endpoint_default_value)
96+
97+
connect_timeout_default_value = 40.0
98+
connect_timeout_env = 'JUPYTER_GATEWAY_CONNECT_TIMEOUT'
99+
connect_timeout = Float(default_value=connect_timeout_default_value, config=True,
100+
help="""The time allowed for HTTP connection establishment with the Gateway server.
101+
(JUPYTER_GATEWAY_CONNECT_TIMEOUT env var)""")
102+
103+
@default('connect_timeout')
104+
def connect_timeout_default(self):
105+
return float(os.environ.get('JUPYTER_GATEWAY_CONNECT_TIMEOUT', self.connect_timeout_default_value))
106+
107+
request_timeout_default_value = 40.0
108+
request_timeout_env = 'JUPYTER_GATEWAY_REQUEST_TIMEOUT'
109+
request_timeout = Float(default_value=request_timeout_default_value, config=True,
110+
help="""The time allowed for HTTP request completion. (JUPYTER_GATEWAY_REQUEST_TIMEOUT env var)""")
111+
112+
@default('request_timeout')
113+
def request_timeout_default(self):
114+
return float(os.environ.get('JUPYTER_GATEWAY_REQUEST_TIMEOUT', self.request_timeout_default_value))
115+
116+
client_key = Unicode(default_value=None, allow_none=True, config=True,
117+
help="""The filename for client SSL key, if any. (JUPYTER_GATEWAY_CLIENT_KEY env var)
118+
"""
119+
)
120+
client_key_env = 'JUPYTER_GATEWAY_CLIENT_KEY'
121+
122+
@default('client_key')
123+
def _client_key_default(self):
124+
return os.environ.get(self.client_key_env)
125+
126+
client_cert = Unicode(default_value=None, allow_none=True, config=True,
127+
help="""The filename for client SSL certificate, if any. (JUPYTER_GATEWAY_CLIENT_CERT env var)
128+
"""
129+
)
130+
client_cert_env = 'JUPYTER_GATEWAY_CLIENT_CERT'
131+
132+
@default('client_cert')
133+
def _client_cert_default(self):
134+
return os.environ.get(self.client_cert_env)
135+
136+
ca_certs = Unicode(default_value=None, allow_none=True, config=True,
137+
help="""The filename of CA certificates or None to use defaults. (JUPYTER_GATEWAY_CA_CERTS env var)
138+
"""
139+
)
140+
ca_certs_env = 'JUPYTER_GATEWAY_CA_CERTS'
141+
142+
@default('ca_certs')
143+
def _ca_certs_default(self):
144+
return os.environ.get(self.ca_certs_env)
145+
146+
http_user = Unicode(default_value=None, allow_none=True, config=True,
147+
help="""The username for HTTP authentication. (JUPYTER_GATEWAY_HTTP_USER env var)
148+
"""
149+
)
150+
http_user_env = 'JUPYTER_GATEWAY_HTTP_USER'
151+
152+
@default('http_user')
153+
def _http_user_default(self):
154+
return os.environ.get(self.http_user_env)
155+
156+
http_pwd = Unicode(default_value=None, allow_none=True, config=True,
157+
help="""The password for HTTP authentication. (JUPYTER_GATEWAY_HTTP_PWD env var)
158+
"""
159+
)
160+
http_pwd_env = 'JUPYTER_GATEWAY_HTTP_PWD'
161+
162+
@default('http_pwd')
163+
def _http_pwd_default(self):
164+
return os.environ.get(self.http_pwd_env)
165+
166+
headers_default_value = '{}'
167+
headers_env = 'JUPYTER_GATEWAY_HEADERS'
168+
headers = Unicode(default_value=headers_default_value, allow_none=True, config=True,
169+
help="""Additional HTTP headers to pass on the request. This value will be converted to a dict.
170+
(JUPYTER_GATEWAY_HEADERS env var)
171+
"""
172+
)
173+
174+
@default('headers')
175+
def _headers_default(self):
176+
return os.environ.get(self.headers_env, self.headers_default_value)
177+
178+
auth_token = Unicode(default_value=None, allow_none=True, config=True,
179+
help="""The authorization token used in the HTTP headers. (JUPYTER_GATEWAY_AUTH_TOKEN env var)
180+
"""
181+
)
182+
auth_token_env = 'JUPYTER_GATEWAY_AUTH_TOKEN'
183+
184+
@default('auth_token')
185+
def _auth_token_default(self):
186+
return os.environ.get(self.auth_token_env, '')
187+
188+
validate_cert_default_value = True
189+
validate_cert_env = 'JUPYTER_GATEWAY_VALIDATE_CERT'
190+
validate_cert = Bool(default_value=validate_cert_default_value, config=True,
191+
help="""For HTTPS requests, determines if server's certificate should be validated or not.
192+
(JUPYTER_GATEWAY_VALIDATE_CERT env var)"""
193+
)
194+
195+
@default('validate_cert')
196+
def validate_cert_default(self):
197+
return bool(os.environ.get(self.validate_cert_env, str(self.validate_cert_default_value)) not in ['no', 'false'])
198+
199+
def __init__(self, **kwargs):
200+
super(GatewayClient, self).__init__(**kwargs)
201+
self._static_args = {} # initialized on first use
202+
203+
env_whitelist_default_value = ''
204+
env_whitelist_env = 'JUPYTER_GATEWAY_ENV_WHITELIST'
205+
env_whitelist = Unicode(default_value=env_whitelist_default_value, config=True,
206+
help="""A comma-separated list of environment variable names that will be included, along with
207+
their values, in the kernel startup request. The corresponding `env_whitelist` configuration
208+
value must also be set on the Gateway server - since that configuration value indicates which
209+
environmental values to make available to the kernel. (JUPYTER_GATEWAY_ENV_WHITELIST env var)""")
210+
211+
@default('env_whitelist')
212+
def _env_whitelist_default(self):
213+
return os.environ.get(self.env_whitelist_env, self.env_whitelist_default_value)
214+
215+
gateway_retry_interval_default_value = 1.0
216+
gateway_retry_interval_env = 'JUPYTER_GATEWAY_RETRY_INTERVAL'
217+
gateway_retry_interval = Float(default_value=gateway_retry_interval_default_value, config=True,
218+
help="""The time allowed for HTTP reconnection with the Gateway server for the first time.
219+
Next will be JUPYTER_GATEWAY_RETRY_INTERVAL multiplied by two in factor of numbers of retries
220+
but less than JUPYTER_GATEWAY_RETRY_INTERVAL_MAX.
221+
(JUPYTER_GATEWAY_RETRY_INTERVAL env var)""")
222+
223+
@default('gateway_retry_interval')
224+
def gateway_retry_interval_default(self):
225+
return float(os.environ.get('JUPYTER_GATEWAY_RETRY_INTERVAL', self.gateway_retry_interval_default_value))
226+
227+
gateway_retry_interval_max_default_value = 30.0
228+
gateway_retry_interval_max_env = 'JUPYTER_GATEWAY_RETRY_INTERVAL_MAX'
229+
gateway_retry_interval_max = Float(default_value=gateway_retry_interval_max_default_value, config=True,
230+
help="""The maximum time allowed for HTTP reconnection retry with the Gateway server.
231+
(JUPYTER_GATEWAY_RETRY_INTERVAL_MAX env var)""")
232+
233+
@default('gateway_retry_interval_max')
234+
def gateway_retry_interval_max_default(self):
235+
return float(os.environ.get('JUPYTER_GATEWAY_RETRY_INTERVAL_MAX', self.gateway_retry_interval_max_default_value))
236+
237+
gateway_retry_max_default_value = 5
238+
gateway_retry_max_env = 'JUPYTER_GATEWAY_RETRY_MAX'
239+
gateway_retry_max = Int(default_value=gateway_retry_max_default_value, config=True,
240+
help="""The maximum retries allowed for HTTP reconnection with the Gateway server.
241+
(JUPYTER_GATEWAY_RETRY_MAX env var)""")
242+
243+
@default('gateway_retry_max')
244+
def gateway_retry_max_default(self):
245+
return int(os.environ.get('JUPYTER_GATEWAY_RETRY_MAX', self.gateway_retry_max_default_value))
246+
247+
@property
248+
def gateway_enabled(self):
249+
return bool(self.url is not None and len(self.url) > 0)
250+
251+
# Ensure KERNEL_LAUNCH_TIMEOUT has a default value.
252+
KERNEL_LAUNCH_TIMEOUT = int(os.environ.get('KERNEL_LAUNCH_TIMEOUT', 40))
253+
254+
def init_static_args(self):
255+
"""Initialize arguments used on every request. Since these are static values, we'll
256+
perform this operation once.
257+
258+
"""
259+
# Ensure that request timeout and KERNEL_LAUNCH_TIMEOUT are the same, taking the
260+
# greater value of the two.
261+
if self.request_timeout < float(GatewayClient.KERNEL_LAUNCH_TIMEOUT):
262+
self.request_timeout = float(GatewayClient.KERNEL_LAUNCH_TIMEOUT)
263+
elif self.request_timeout > float(GatewayClient.KERNEL_LAUNCH_TIMEOUT):
264+
GatewayClient.KERNEL_LAUNCH_TIMEOUT = int(self.request_timeout)
265+
# Ensure any adjustments are reflected in env.
266+
os.environ['KERNEL_LAUNCH_TIMEOUT'] = str(GatewayClient.KERNEL_LAUNCH_TIMEOUT)
267+
268+
self._static_args['headers'] = json.loads(self.headers)
269+
if 'Authorization' not in self._static_args['headers'].keys():
270+
self._static_args['headers'].update({
271+
'Authorization': 'token {}'.format(self.auth_token)
272+
})
273+
self._static_args['connect_timeout'] = self.connect_timeout
274+
self._static_args['request_timeout'] = self.request_timeout
275+
self._static_args['validate_cert'] = self.validate_cert
276+
if self.client_cert:
277+
self._static_args['client_cert'] = self.client_cert
278+
self._static_args['client_key'] = self.client_key
279+
if self.ca_certs:
280+
self._static_args['ca_certs'] = self.ca_certs
281+
if self.http_user:
282+
self._static_args['auth_username'] = self.http_user
283+
if self.http_pwd:
284+
self._static_args['auth_password'] = self.http_pwd
285+
286+
def load_connection_args(self, **kwargs):
287+
"""Merges the static args relative to the connection, with the given keyword arguments. If statics
288+
have yet to be initialized, we'll do that here.
289+
290+
"""
291+
if len(self._static_args) == 0:
292+
self.init_static_args()
293+
294+
kwargs.update(self._static_args)
295+
return kwargs
296+
297+
298+
async def gateway_request(endpoint, **kwargs):
299+
"""Make an async request to kernel gateway endpoint, returns a response """
300+
client = AsyncHTTPClient()
301+
kwargs = GatewayClient.instance().load_connection_args(**kwargs)
302+
try:
303+
response = await client.fetch(endpoint, **kwargs)
304+
# Trap a set of common exceptions so that we can inform the user that their Gateway url is incorrect
305+
# or the server is not running.
306+
# NOTE: We do this here since this handler is called during the Notebook's startup and subsequent refreshes
307+
# of the tree view.
308+
except ConnectionRefusedError as e:
309+
raise web.HTTPError(503, "Connection refused from Gateway server url '{}'. "
310+
"Check to be sure the Gateway instance is running.".format(GatewayClient.instance().url)) from e
311+
except HTTPError as e:
312+
# This can occur if the host is valid (e.g., foo.com) but there's nothing there.
313+
raise web.HTTPError(e.code, "Error attempting to connect to Gateway server url '{}'. "
314+
"Ensure gateway url is valid and the Gateway instance is running.".
315+
format(GatewayClient.instance().url)) from e
316+
except gaierror as e:
317+
raise web.HTTPError(404, "The Gateway server specified in the gateway_url '{}' doesn't appear to be valid. "
318+
"Ensure gateway url is valid and the Gateway instance is running.".
319+
format(GatewayClient.instance().url)) from e
320+
321+
return response
322+

0 commit comments

Comments
 (0)