forked from opendatahub-io-contrib/jupyterhub-odh
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjupyterhub_config.py
286 lines (217 loc) · 10.5 KB
/
jupyterhub_config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
c.KubeSpawner.http_timeout = 60 * 10 #Images are big, take time to pull, make it 10 mins for now because of storage issue
c.KubeSpawner.start_timeout = 60 * 10 #Images are big, take time to pull, make it 10 mins for now because of storage issue
import os
import distutils
#c.JupyterHub.log_level = 'DEBUG'
#c.Spawner.debug = True
# Do not shut down singleuser servers on restart
c.JupyterHub.cleanup_servers = False
import uuid
jsp_api_dict = {
'KUBERNETES_SERVICE_HOST': os.environ['KUBERNETES_SERVICE_HOST'],
'KUBERNETES_SERVICE_PORT': os.environ['KUBERNETES_SERVICE_PORT'],
'JUPYTERHUB_LOGIN_URL': None
}
custom_notebook_namespace = os.environ.get('NOTEBOOK_NAMESPACE')
if not custom_notebook_namespace:
custom_notebook_namespace = None;
else:
jsp_api_dict['NOTEBOOK_NAMESPACE'] = custom_notebook_namespace #Set only if not None, None type in env vars causes subprocess.py to crash with error
from jupyterhub_singleuser_profiles.openshift import OpenShift
openshift = OpenShift(namespace=namespace)
culler_secret = 'jupyterhub-idle-culler'
def get_culler_secret():
secret = openshift.read_secret(culler_secret)
if secret == {}:
return set_culler_secret()
else:
return secret['token']
def set_culler_secret():
secret_data = str(uuid.uuid4())
openshift.write_secret(culler_secret, {'token': secret_data})
return secret_data
idle_culler_api_token = get_culler_secret()
c.JupyterHub.services = [
{
'name': 'jsp-api',
'url': 'http://jupyterhub:8181',
'admin': True,
'command': ['jupyterhub-singleuser-profiles-api'],
'environment': jsp_api_dict
},
{
'name': 'idle-culler',
'api_token': idle_culler_api_token,
'admin': True
},
]
if "PROMETHEUS_API_TOKEN" in os.environ:
c.JupyterHub.services.append(dict(name='prometheus', api_token=os.environ.get("PROMETHEUS_API_TOKEN")))
DEFAULT_MOUNT_PATH = '/opt/app-root/src'
# Work out the public server address for the OpenShift REST API. Don't
# know how to get this via the REST API client so do a raw request to
# get it. Make sure request is done in a session so connection is closed
# and later calls against REST API don't attempt to reuse it. This is
# just to avoid potential for any problems with connection reuse.
# Enable the OpenShift authenticator.
from oauthenticator.openshift import OpenShiftOAuthenticator
c.JupyterHub.authenticator_class = OpenShiftOAuthenticator
c.Authenticator.auto_login = True
c.Authenticator.enable_auth_state = True
c.OpenShiftOAuthenticator.auth_refresh_age = 300
c.OpenShiftOAuthenticator.refresh_pre_spawn = True
# Override scope as oauthenticator code doesn't set it correctly.
# Need to lodge a PR against oauthenticator to have this fixed.
#OpenShiftOAuthenticator.scope = ['user:info']
# Setup authenticator configuration using details from environment.
service_name = os.environ['JUPYTERHUB_SERVICE_NAME']
service_account_name = '%s-hub' % service_name
service_account_path = '/var/run/secrets/kubernetes.io/serviceaccount'
with open(os.path.join(service_account_path, 'namespace')) as fp:
namespace = fp.read().strip()
client_id = 'system:serviceaccount:%s:%s' % (namespace, service_account_name)
c.OpenShiftOAuthenticator.client_id = client_id
with open(os.path.join(service_account_path, 'token')) as fp:
client_secret = fp.read().strip()
c.OpenShiftOAuthenticator.client_secret = client_secret
groups_default_denied = bool(distutils.util.strtobool(os.environ.get('JUPYTERHUB_GROUPS_DEFAULT_DENIED', "false")))
allowed_groups = os.environ.get('JUPYTERHUB_ALLOWED_GROUPS', "")
admin_groups = os.environ.get('JUPYTERHUB_ADMIN_GROUPS', "")
if allowed_groups or groups_default_denied:
c.OpenShiftOAuthenticator.allowed_groups = set(allowed_groups.split(','))
if admin_groups or groups_default_denied:
c.OpenShiftOAuthenticator.admin_groups = set(admin_groups.split(','))
# Work out hostname for the exposed route of the JupyterHub server. This
# is tricky as we need to use the REST API to query it.
verify_ssl = False
from kubernetes import client, config
from openshift.dynamic import DynamicClient
config.load_incluster_config()
configuration = client.Configuration()
configuration.verify_ssl = verify_ssl
oapi_client = DynamicClient(
client.ApiClient(configuration=configuration)
)
routes = oapi_client.resources.get(kind='Route', api_version='route.openshift.io/v1')
route_list = routes.get(namespace=namespace)
host = None
for route in route_list.items:
if route.metadata.name == service_name:
host = route.spec.host
if not host:
raise RuntimeError('Cannot calculate external host name for JupyterHub.')
c.OpenShiftOAuthenticator.oauth_callback_url = 'https://%s/hub/oauth_callback' % host
jsp_api_dict['JUPYTERHUB_LOGIN_URL'] = 'https://%s/hub/login' % host
from html.parser import HTMLParser
class UILinkParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.data = None
self.tag = None
self.attrs = None
self.result = []
self.html_tag = []
def handle_starttag(self, tag, attrs):
self.tag = tag
self.attrs = attrs
if self.tag == 'link':
self.generate_link()
if self.tag == 'html':
self.html_tag.append(self.getpos()[1])
def handle_endtag(self, tag):
if tag == 'html':
self.html_tag.append(self.getpos()[1])
def generate_link(self):
attr_strings = []
for attr in self.attrs:
attr_strings.append('{0[0]}={0[1]} '.format(attr))
string = '<%s ' % self.tag
for attr in attr_strings:
string += attr
self.result.append(string+' />')
parser = UILinkParser()
index = None
html_string = None
with open("/opt/app-root/share/jupyterhub/static/jsp-ui/index.html", "r") as f:
html_string = f.read()
parser.feed(html_string)
index = html_string.find('<body>')
links = parser.result
for link in links:
html_string = html_string[:index+6]+link+html_string[index+6:]
for tag in parser.html_tag:
html_string = html_string[:tag] + html_string[tag:]
from jupyterhub_singleuser_profiles.profiles import SingleuserProfiles
from kubespawner import KubeSpawner
class OpenShiftSpawner(KubeSpawner):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.single_user_services = []
self.single_user_profiles = SingleuserProfiles(gpu_mode=os.environ.get('GPU_MODE'), notebook_namespace=custom_notebook_namespace, verify_ssl=verify_ssl)
self.gpu_mode = self.single_user_profiles.gpu_mode
self.gpu_count = 0
self.deployment_size = None
self.uid = None
self.fs_gid = None
def _options_form_default(self):
response = html_string
return response
def options_from_form(self, formdata):
options = {}
cm_data = self.single_user_profiles.user.get(self.user.name)
options['size'] = cm_data['last_selected_size']
self.gpu_count = cm_data['gpu']
self.deployment_size = cm_data['last_selected_size']
return options
def get_env(self):
env = super(OpenShiftSpawner, self).get_env()
if custom_notebook_namespace:
env['JUPYTERHUB_API_URL'] = f'http://{service_name}.{namespace}:8081/hub/api'
env['JUPYTERHUB_ACTIVITY_URL'] = f'http://{service_name}.{namespace}:8081/hub/api/users/{self.user.name}/activity'
return env
def set_from_profile(self):
profile = self.single_user_profiles.user.get(self.user.name)
image = profile['last_selected_image']
if custom_notebook_namespace:
image = f'image-registry.openshift-image-registry.svc:5000/{namespace}/%s' % image
self.image = image
self.deployment_size = profile['last_selected_size']
def apply_pod_profile(spawner, pod):
spawner.single_user_profiles.load_profiles(username=spawner.user.name)
profile = spawner.single_user_profiles.get_merged_profile(spawner.image, user=spawner.user.name, size=spawner.deployment_size)
gpu_types = spawner.single_user_profiles.get_gpu_types()
return SingleuserProfiles.apply_pod_profile(spawner.user.name, pod, profile, gpu_types, DEFAULT_MOUNT_PATH, spawner.gpu_mode)
def setup_environment(spawner):
spawner.set_from_profile()
spawner.single_user_profiles.load_profiles(username=spawner.user.name)
spawner.single_user_profiles.setup_services(spawner, spawner.image, spawner.user.name)
def clean_environment(spawner):
spawner.single_user_profiles.clean_services(spawner, spawner.user.name)
c.JupyterHub.spawner_class = OpenShiftSpawner
c.OpenShiftSpawner.pre_spawn_hook = setup_environment
c.OpenShiftSpawner.post_stop_hook = clean_environment
c.OpenShiftSpawner.modify_pod_hook = apply_pod_profile
c.OpenShiftSpawner.cpu_limit = float(os.environ.get("SINGLEUSER_CPU_LIMIT", "1"))
c.OpenShiftSpawner.mem_limit = os.environ.get("SINGLEUSER_MEM_LIMIT", "1G")
c.OpenShiftSpawner.storage_pvc_ensure = True
if custom_notebook_namespace:
c.KubeSpawner.namespace = custom_notebook_namespace
c.KubeSpawner.storage_capacity = os.environ.get('SINGLEUSER_PVC_SIZE', '2Gi')
c.KubeSpawner.pvc_name_template = '%s-nb-{username}-pvc' % os.environ['JUPYTERHUB_SERVICE_NAME']
c.KubeSpawner.volumes = [dict(name='data', persistentVolumeClaim=dict(claimName=c.KubeSpawner.pvc_name_template))]
c.KubeSpawner.volume_mounts = [dict(name='data', mountPath=DEFAULT_MOUNT_PATH)]
c.KubeSpawner.user_storage_class = os.environ.get("JUPYTERHUB_STORAGE_CLASS", c.KubeSpawner.user_storage_class)
admin_users = os.environ.get('JUPYTERHUB_ADMIN_USERS')
if admin_users:
c.Authenticator.admin_users = set(admin_users.split(','))
#Enable Traefik Proxy instead of the configurableHTTPPRoxy
from jupyterhub_traefik_proxy import TraefikTomlConfigmapProxy
c.JupyterHub.proxy_class = TraefikTomlConfigmapProxy
c.TraefikTomlConfigmapProxy.traefik_api_url = "http://traefik-proxy:8099"
# traefik api endpoint login username
c.TraefikTomlConfigmapProxy.traefik_api_username = os.environ['TRAEFIK_API_USERNAME']
# traefik api endpoint login password
c.TraefikTomlConfigmapProxy.traefik_api_password = os.environ['TRAEFIK_API_PASSWORD']
c.TraefikTomlConfigmapProxy.cm_namespace = os.environ['NAMESPACE']
c.TraefikTomlConfigmapProxy.cm_name = "traefik-rules"
c.TraefikTomlConfigmapProxy.traefik_svc_namespace = os.environ['NAMESPACE']