1
1
package main
2
2
3
3
import (
4
+ "encoding/json"
4
5
"fmt"
6
+ "io/ioutil"
5
7
"log"
6
8
"net/http"
7
9
"sort"
@@ -15,6 +17,43 @@ import (
15
17
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16
18
)
17
19
20
+ const DefaultServiceAccountName string = "default-editor"
21
+ const SharedMemoryVolumeName string = "dshm"
22
+ const SharedMemoryVolumePath string = "/dev/shm"
23
+ const WorkspacePath string = "/home/jovyan"
24
+
25
+ type volumetype string
26
+
27
+ const (
28
+ VolumeTypeExisting volumetype = "Existing"
29
+ VolumeTypeNew volumetype = "New"
30
+ )
31
+
32
+ type volumerequest struct {
33
+ Type volumetype `json:"type"`
34
+ Name string `json:"name"`
35
+ TemplatedName string `json:"templatedName"`
36
+ Class string `json:"class"`
37
+ ExtraFields map [string ]interface {} `json:"extraFields"`
38
+ Path string `json:"path"`
39
+ }
40
+
41
+ type newnotebookrequest struct {
42
+ Name string `json:"name"`
43
+ Namespace string `json:"namespace"`
44
+ Image string `json:"image"`
45
+ CustomImage string `json:"customImage"`
46
+ CustomImageCheck bool `json:"customImageCheck"`
47
+ CPU resource.Quantity `json:"cpu"`
48
+ Memory resource.Quantity `json:"memory"`
49
+ // TODO: GPU
50
+ NoWorkspace bool `json:"noWorkspace"`
51
+ Workspace volumerequest `json:"workspace"`
52
+ DataVolumes []volumerequest `json:"datavols"`
53
+ EnableSharedMemory bool `json:"shm"`
54
+ Configurations []string `json:"configurations"`
55
+ }
56
+
18
57
type notebookresponse struct {
19
58
Age string `json:"age"`
20
59
CPU resource.Quantity `json:"cpu"`
@@ -186,3 +225,154 @@ func (s *server) GetNotebooks(w http.ResponseWriter, r *http.Request) {
186
225
187
226
s .respond (w , r , resp )
188
227
}
228
+
229
+ func (s * server ) handleVolume (req volumerequest , notebook * notebooksv1.Notebook ) error {
230
+ if req .Type == VolumeTypeExisting {
231
+ notebook .Spec .Template .Spec .Volumes = append (notebook .Spec .Template .Spec .Volumes , corev1.Volume {
232
+ Name : req .Name ,
233
+ VolumeSource : corev1.VolumeSource {
234
+ PersistentVolumeClaim : & corev1.PersistentVolumeClaimVolumeSource {
235
+ ClaimName : req .Name ,
236
+ },
237
+ },
238
+ })
239
+
240
+ notebook .Spec .Template .Spec .Containers [0 ].VolumeMounts = append (notebook .Spec .Template .Spec .Containers [0 ].VolumeMounts , corev1.VolumeMount {
241
+ Name : req .Name ,
242
+ MountPath : req .Path ,
243
+ })
244
+ } else if req .Type == VolumeTypeNew {
245
+ return fmt .Errorf ("unsupported volume type %q" , req .Type )
246
+ } else {
247
+ return fmt .Errorf ("unknown volume type %q" , req .Type )
248
+ }
249
+
250
+ return nil
251
+ }
252
+
253
+ func (s * server ) NewNotebook (w http.ResponseWriter , r * http.Request ) {
254
+ vars := mux .Vars (r )
255
+ namespace := vars ["namespace" ]
256
+
257
+ // Read the incoming notebook
258
+ body , err := ioutil .ReadAll (r .Body )
259
+ if err != nil {
260
+ s .error (w , r , err )
261
+ return
262
+ }
263
+ defer r .Body .Close ()
264
+
265
+ var req newnotebookrequest
266
+ err = json .Unmarshal (body , & req )
267
+ if err != nil {
268
+ s .error (w , r , err )
269
+ return
270
+ }
271
+
272
+ image := req .Image
273
+ if req .CustomImageCheck {
274
+ image = req .CustomImage
275
+ }
276
+
277
+ // Setup the notebook
278
+ // TODO: Work with default CPU/memory limits from config
279
+ // TODO: Add GPU support
280
+ notebook := notebooksv1.Notebook {
281
+ ObjectMeta : v1.ObjectMeta {
282
+ Name : req .Name ,
283
+ Namespace : namespace ,
284
+ },
285
+ Spec : notebooksv1.NotebookSpec {
286
+ Template : notebooksv1.NotebookTemplateSpec {
287
+ Spec : corev1.PodSpec {
288
+ ServiceAccountName : DefaultServiceAccountName ,
289
+ Containers : []corev1.Container {
290
+ corev1.Container {
291
+ Name : req .Name ,
292
+ Image : image ,
293
+ Resources : corev1.ResourceRequirements {
294
+ Limits : corev1.ResourceList {
295
+ corev1 .ResourceCPU : req .CPU ,
296
+ corev1 .ResourceMemory : req .Memory ,
297
+ },
298
+ Requests : corev1.ResourceList {
299
+ corev1 .ResourceCPU : req .CPU ,
300
+ corev1 .ResourceMemory : req .Memory ,
301
+ },
302
+ },
303
+ },
304
+ },
305
+ },
306
+ },
307
+ },
308
+ }
309
+
310
+ // Add workspace volume
311
+ if ! req .NoWorkspace {
312
+ req .Workspace .Path = WorkspacePath
313
+ err = s .handleVolume (req .Workspace , & notebook )
314
+ if err != nil {
315
+ s .error (w , r , err )
316
+ return
317
+ }
318
+ }
319
+
320
+ for _ , volreq := range req .DataVolumes {
321
+ err = s .handleVolume (volreq , & notebook )
322
+ if err != nil {
323
+ s .error (w , r , err )
324
+ return
325
+ }
326
+ }
327
+
328
+ // Add shared memory, if enabled
329
+ if req .EnableSharedMemory {
330
+ notebook .Spec .Template .Spec .Volumes = append (notebook .Spec .Template .Spec .Volumes , corev1.Volume {
331
+ Name : SharedMemoryVolumeName ,
332
+ VolumeSource : corev1.VolumeSource {
333
+ EmptyDir : & corev1.EmptyDirVolumeSource {
334
+ Medium : corev1 .StorageMediumMemory ,
335
+ },
336
+ },
337
+ })
338
+
339
+ notebook .Spec .Template .Spec .Containers [0 ].VolumeMounts = append (notebook .Spec .Template .Spec .Containers [0 ].VolumeMounts , corev1.VolumeMount {
340
+ Name : SharedMemoryVolumeName ,
341
+ MountPath : SharedMemoryVolumePath ,
342
+ })
343
+ }
344
+
345
+ log .Printf ("creating notebook %q for %q" , notebook .ObjectMeta .Name , namespace )
346
+
347
+ // Submit the notebook to the API server
348
+ _ , err = s .clientsets .notebooks .V1 ().Notebooks (namespace ).Create (r .Context (), & notebook )
349
+ if err != nil {
350
+ s .error (w , r , err )
351
+ return
352
+ }
353
+
354
+ s .respond (w , r , APIResponse {
355
+ Success : true ,
356
+ })
357
+ }
358
+
359
+ func (s * server ) DeleteNotebook (w http.ResponseWriter , r * http.Request ) {
360
+ vars := mux .Vars (r )
361
+ namespace := vars ["namespace" ]
362
+ notebook := vars ["notebook" ]
363
+
364
+ log .Printf ("deleting notebook %q for %q" , notebook , namespace )
365
+
366
+ propagation := v1 .DeletePropagationForeground
367
+ err := s .clientsets .notebooks .V1 ().Notebooks (namespace ).Delete (r .Context (), notebook , & v1.DeleteOptions {
368
+ PropagationPolicy : & propagation ,
369
+ })
370
+ if err != nil {
371
+ s .error (w , r , err )
372
+ return
373
+ }
374
+
375
+ s .respond (w , r , APIResponse {
376
+ Success : true ,
377
+ })
378
+ }
0 commit comments