Skip to content

Commit 8c175f5

Browse files
authored
Merge pull request #62 from eth-cscs/production-fixes
Improvements and better error handling
2 parents 81e50bb + a59c288 commit 8c175f5

File tree

12 files changed

+289
-74
lines changed

12 files changed

+289
-74
lines changed

chart/f7t4jhub/files/jupyterhub-config.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,10 @@ async def get_node_ip_from_output(spawner):
105105
c.JupyterHub.spawner_class = 'firecrestspawner.spawner.SlurmSpawner'
106106
c.Spawner.polling_with_service_account = {{ .Values.serviceAccount.enabled | toJson | replace "true" "True" | replace "false" "False" }}
107107
c.Spawner.req_host = '{{ .Values.config.spawner.host }}'
108+
c.Spawner.req_reservation = "{{ .Values.config.spawner.reservation }}"
109+
c.Spawner.req_constraint = "{{ .Values.config.spawner.constraint }}"
108110
c.Spawner.node_name_template = '{{ .Values.config.spawner.nodeNameTemplate }}'
109111
c.Spawner.req_partition = '{{ .Values.config.spawner.partition }}'
110-
c.Spawner.req_constraint = '{{ .Values.config.spawner.constraint }}'
111112
c.Spawner.req_srun = '{{ .Values.config.spawner.srun }}'
112113
c.Spawner.batch_script = """#!/bin/bash
113114
@@ -120,21 +121,13 @@ async def get_node_ip_from_output(spawner):
120121
{% if gres %}#SBATCH --gres={{`{{gres}}`}}{% endif %}
121122
{% if nprocs %}#SBATCH --cpus-per-task={{`{{nprocs}}`}}{% endif %}
122123
{% if nnodes %}#SBATCH --nodes={{`{{nnodes[0]}}`}}{% endif %}
124+
{% if reservation %}#SBATCH --reservation={{`{{reservation[0]}}`}}{% endif %}
125+
{% if constraint %}#SBATCH --constraint={{`{{constraint[0]}}`}}{% endif %}
123126
{% if account is string %}
124127
#SBATCH --account={{`{{account}}`}}
125128
{% else %}
126129
#SBATCH --account={{`{{account[0]}}`}}
127130
{% endif %}
128-
{% if reservation is string %}
129-
#SBATCH --reservation={{`{{reservation}}`}}
130-
{% else %}
131-
#SBATCH --reservation={{`{{reservation[0]}}`}}
132-
{% endif %}
133-
{% if constraint is string %}
134-
#SBATCH --constraint={{`{{constraint}}`}}
135-
{% else %}
136-
#SBATCH --constraint={{`{{constraint[0]}}`}}
137-
{% endif %}
138131
{% if options %}#SBATCH {{`{{options}}`}}{% endif %}
139132
140133
# Activate a virtual environment, load modules, etc
@@ -143,8 +136,8 @@ async def get_node_ip_from_output(spawner):
143136
#
144137
{{ .Values.config.spawner.prelaunchCmds }}
145138
146-
export JUPYTERHUB_API_URL="http://{{ .Values.config.commonName }}/hub/api"
147-
export JUPYTERHUB_ACTIVITY_URL="http://{{ .Values.config.commonName }}/hub/api/users/${USER}/activity"
139+
export JUPYTERHUB_API_URL="http://{{ index .Values.config.commonNames 0 }}/hub/api"
140+
export JUPYTERHUB_ACTIVITY_URL="http://{{ index .Values.config.commonNames 0 }}/hub/api/users/${USER}/activity"
148141
149142
export JUPYTERHUB_OAUTH_ACCESS_SCOPES=$(echo $JUPYTERHUB_OAUTH_ACCESS_SCOPES | base64 --decode)
150143
export JUPYTERHUB_OAUTH_SCOPES=$(echo $JUPYTERHUB_OAUTH_SCOPES | base64 --decode)
@@ -165,6 +158,23 @@ async def get_node_ip_from_output(spawner):
165158
c.Spawner.options_form = """
166159
{{ .Values.config.spawner.optionsForm }}
167160
"""
161+
162+
def spawner_options_form(formdata, spawner):
163+
"""Function to process the script flags `reservation` and
164+
`constraint` since they could come from the `values.yaml` or
165+
the options form.
166+
"""
167+
reservation = formdata.get("reservation", [""])[0].strip()
168+
constraint = formdata.get("constraint", [""])[0].strip()
169+
if not reservation or reservation == [""]:
170+
formdata["reservation"] = [spawner.req_reservation]
171+
172+
if not constraint or constraint == [""]:
173+
formdata["constraint"] = [spawner.req_constraint]
174+
175+
return formdata
176+
177+
c.Spawner.options_from_form = spawner_options_form
168178
c.Spawner.poll_interval = 300
169179
c.Spawner.port = {{ .Values.config.spawner.port }}
170180
c.Spawner.start_timeout = {{ .Values.config.spawner.start_timeout }}

chart/f7t4jhub/templates/certificate.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ kind: Certificate
33
metadata:
44
name: {{ .Release.Name }}-cert
55
spec:
6-
commonName: {{ .Values.config.commonName }}
6+
commonName: {{ index .Values.config.commonNames 0 }}
77
dnsNames:
8-
- {{ .Values.config.commonName }}
8+
{{- range .Values.config.commonNames }}
9+
- {{ . }}
10+
{{- end }}
911
issuerRef:
1012
kind: ClusterIssuer
11-
name: letsencrypt
13+
name: letsencrypt-http01
1214
secretName: {{ .Release.Name }}-cert-secret

chart/f7t4jhub/templates/ingress.yaml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,35 @@ metadata:
1212
spec:
1313
ingressClassName: nginx
1414
rules:
15-
- host: {{ .Values.config.commonName }}
15+
{{- range .Values.config.commonNames }}
16+
- host: {{ . }}
1617
http:
1718
paths:
1819
- path: /
1920
pathType: Prefix
2021
backend:
2122
service:
22-
name: {{ .Release.Name }}-proxy-svc
23+
name: {{ $.Release.Name }}-proxy-svc
2324
port:
24-
number: {{ .Values.network.appPort }}
25+
number: {{ $.Values.network.appPort }}
2526
- path: /hub/api
2627
pathType: Prefix
2728
backend:
2829
service:
29-
name: {{ .Release.Name }}-proxy-svc
30+
name: {{ $.Release.Name }}-proxy-svc
3031
port:
31-
number: {{ .Values.network.appPort }}
32+
number: {{ $.Values.network.appPort }}
3233
- path: /api
3334
pathType: Prefix
3435
backend:
3536
service:
36-
name: {{ .Release.Name }}-proxy-svc
37+
name: {{ $.Release.Name }}-proxy-svc
3738
port:
38-
number: {{ .Values.network.apiPort }}
39+
number: {{ $.Values.network.apiPort }}
40+
{{- end }}
3941
tls:
4042
- hosts:
41-
- {{ .Values.config.commonName }}
43+
{{- range .Values.config.commonNames }}
44+
- {{ . }}
45+
{{- end }}
4246
secretName: {{ .Release.Name }}-cert-secret

chart/values.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,10 @@ f7t4jhub:
120120
externalPort: 8081
121121

122122
config:
123-
# URL for the JupyterHub instance (replace with your own domain)
124-
commonName: 'jupyterhub-<cluster-name>.cscs.ch'
123+
# List of URLs for the JupyterHub instance
124+
commonNames:
125+
- 'jupyterhub-<cluster-name-1>.cscs.ch'
126+
- 'jupyterhub-<cluster-name-2>.cscs.ch'
125127

126128
# Admin users for the JupyterHub instance (replace with your own admin users)
127129
adminUsers: '{"user1", "user2"}'
@@ -170,6 +172,9 @@ f7t4jhub:
170172
# Name of the partition of the job scheduler (e.g. normal, debug, long)
171173
partition: '<slurm-partition>'
172174

175+
# Name of a reservation in the job scheduler
176+
reservation: '<slurm-reservation>'
177+
173178
# Constraint for the job scheduler (e.g. gpu, mc, nvgpu)
174179
constraint: '<slurm-constraint>'
175180

dockerfiles/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ RUN . /opt/jhub-env/bin/activate && \
2828
RUN rm -r /opt/jhub-env/share/jupyterhub/templates
2929
COPY dockerfiles/cscs-style-jh4/templates /opt/jhub-env/share/jupyterhub/templates
3030
COPY dockerfiles/cscs-style-jh4/static/css/cscs /opt/jhub-env/share/jupyterhub/static/css/cscs
31+
COPY dockerfiles/cscs-style-jh4/static/js/home.js /opt/jhub-env/share/jupyterhub/static/js/home.js
3132

3233

3334
EXPOSE 8000
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright (c) Jupyter Development Team.
2+
// Distributed under the terms of the Modified BSD License.
3+
4+
require(["jquery", "moment", "jhapi"], function ($, moment, JHAPI) {
5+
"use strict";
6+
7+
var base_url = window.jhdata.base_url;
8+
var user = window.jhdata.user;
9+
var api = new JHAPI(base_url);
10+
11+
// Named servers buttons
12+
13+
function getRow(element) {
14+
while (!element.hasClass("home-server-row")) {
15+
element = element.parent();
16+
}
17+
return element;
18+
}
19+
20+
function disableRow(row) {
21+
row.find(".btn").attr("disabled", true).off("click");
22+
}
23+
24+
function enableRow(row, running) {
25+
// enable buttons on a server row
26+
// once the server is running or not
27+
row.find(".btn").attr("disabled", false);
28+
row.find(".stop-server").click(stopServer);
29+
row.find(".delete-server").click(deleteServer);
30+
31+
if (running) {
32+
row.find(".start-server").addClass("hidden");
33+
row.find(".delete-server").addClass("hidden");
34+
row.find(".stop-server").removeClass("hidden");
35+
row.find(".server-link").removeClass("hidden");
36+
} else {
37+
row.find(".start-server").removeClass("hidden");
38+
row.find(".delete-server").removeClass("hidden");
39+
row.find(".stop-server").addClass("hidden");
40+
row.find(".server-link").addClass("hidden");
41+
}
42+
}
43+
44+
function startServer() {
45+
var row = getRow($(this));
46+
var serverName = row.find(".new-server-name").val();
47+
if (serverName === "") {
48+
// ../spawn/user/ causes a 404, ../spawn/user redirects correctly to the default server
49+
window.location.href = "./spawn/" + user;
50+
} else {
51+
window.location.href = "./spawn/" + user + "/" + serverName;
52+
}
53+
}
54+
55+
function stopServer() {
56+
var row = getRow($(this));
57+
var serverName = row.data("server-name");
58+
59+
// before request
60+
disableRow(row);
61+
62+
// request
63+
api.stop_named_server(user, serverName, {
64+
success: function () {
65+
enableRow(row, false);
66+
},
67+
});
68+
}
69+
70+
function deleteServer() {
71+
var row = getRow($(this));
72+
var serverName = row.data("server-name");
73+
74+
// before request
75+
disableRow(row);
76+
77+
// request
78+
api.delete_named_server(user, serverName, {
79+
success: function () {
80+
row.remove();
81+
},
82+
});
83+
}
84+
85+
// initial state: hook up click events
86+
$("#stop").click(function () {
87+
88+
// check if credentials are valid
89+
var isValid = $(this).data("access-token-is-valid");
90+
var isRunning = $(this).data("access-server-is-running");
91+
92+
if (!isValid && isRunning) {
93+
// Display the message in your custom style
94+
$("#note-note span.info-block")
95+
.html('<br><i class="fa fa-info-circle"></i> Your session has expired. Please <a href="/hub/logout">log out</a> and log back in to refresh your credentials.')
96+
.closest("#note-note") // Show the parent <p> element
97+
.show();
98+
return; // Prevent further execution
99+
}
100+
101+
// original script
102+
$("#start")
103+
.attr("disabled", true)
104+
.attr("title", "Your server is stopping")
105+
.click(function () {
106+
return false;
107+
});
108+
api.stop_server(user, {
109+
success: function () {
110+
$("#stop").hide();
111+
$("#start")
112+
.text("Start My Server")
113+
.attr("title", "Start your default server")
114+
.attr("disabled", false)
115+
.attr("href", base_url + "spawn/" + user)
116+
.off("click");
117+
},
118+
});
119+
});
120+
121+
$(".new-server-btn").click(startServer);
122+
$(".new-server-name").on("keypress", function (e) {
123+
if (e.which === 13) {
124+
startServer.call(this);
125+
}
126+
});
127+
128+
$(".stop-server").click(stopServer);
129+
$(".delete-server").click(deleteServer);
130+
131+
// render timestamps
132+
$(".time-col").map(function (i, el) {
133+
// convert ISO datestamps to nice momentjs ones
134+
el = $(el);
135+
var m = moment(new Date(el.text().trim()));
136+
el.text(m.isValid() ? m.fromNow() : "Never");
137+
});
138+
139+
// signal that page has finished loading (mostly for tests)
140+
window._jupyterhub_page_loaded = true;
141+
});

0 commit comments

Comments
 (0)