1
1
package main
2
2
3
3
import (
4
+ "context"
4
5
"encoding/json"
5
6
"fmt"
6
7
"io/ioutil"
@@ -30,28 +31,35 @@ const (
30
31
)
31
32
32
33
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"`
34
+ Type volumetype `json:"type"`
35
+ Name string `json:"name"`
36
+ TemplatedName string `json:"templatedName"`
37
+ Class string `json:"class"`
38
+ ExtraFields map [string ]interface {} `json:"extraFields"`
39
+ Path string `json:"path"`
40
+ Size resource.Quantity `json:"size"`
41
+ Mode corev1.PersistentVolumeAccessMode `json:"mode"`
42
+ }
43
+
44
+ type gpurequest struct {
45
+ Quantity resource.Quantity `json:"num"`
46
+ Vendor string `json:"vendor"`
39
47
}
40
48
41
49
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"`
50
+ Name string `json:"name"`
51
+ Namespace string `json:"namespace"`
52
+ Image string `json:"image"`
53
+ CustomImage string `json:"customImage"`
54
+ CustomImageCheck bool `json:"customImageCheck"`
55
+ CPU resource.Quantity `json:"cpu"`
56
+ Memory resource.Quantity `json:"memory"`
57
+ GPUs gpurequest `json:"gpus"`
58
+ NoWorkspace bool `json:"noWorkspace"`
59
+ Workspace volumerequest `json:"workspace"`
60
+ DataVolumes []volumerequest `json:"datavols"`
61
+ EnableSharedMemory bool `json:"shm"`
62
+ Configurations []string `json:"configurations"`
55
63
}
56
64
57
65
type notebookresponse struct {
@@ -226,27 +234,51 @@ func (s *server) GetNotebooks(w http.ResponseWriter, r *http.Request) {
226
234
s .respond (w , r , resp )
227
235
}
228
236
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 ,
237
+ func (s * server ) handleVolume (ctx context.Context , req volumerequest , notebook * notebooksv1.Notebook ) error {
238
+ if req .Type == VolumeTypeNew {
239
+ // Create the PVC
240
+ pvc := corev1.PersistentVolumeClaim {
241
+ ObjectMeta : v1.ObjectMeta {
242
+ Name : req .Name ,
243
+ Namespace : notebook .Namespace ,
244
+ },
245
+ Spec : corev1.PersistentVolumeClaimSpec {
246
+ AccessModes : []corev1.PersistentVolumeAccessMode {req .Mode },
247
+ Resources : corev1.ResourceRequirements {
248
+ Requests : corev1.ResourceList {
249
+ corev1 .ResourceStorage : req .Size ,
250
+ },
236
251
},
237
252
},
238
- })
253
+ }
239
254
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 {
255
+ // Add the storage class, if set and not set to an "empty" value
256
+ if req .Class != "" && req .Class != "{none}" && req .Class != "{empty}" {
257
+ pvc .Spec .StorageClassName = & req .Class
258
+ }
259
+
260
+ if _ , err := s .clientsets .kubernetes .CoreV1 ().PersistentVolumeClaims (notebook .Namespace ).Create (ctx , & pvc , v1.CreateOptions {}); err != nil {
261
+ return err
262
+ }
263
+ } else if req .Type != VolumeTypeExisting {
247
264
return fmt .Errorf ("unknown volume type %q" , req .Type )
248
265
}
249
266
267
+ // Add the volume and volume mount ot the notebook spec
268
+ notebook .Spec .Template .Spec .Volumes = append (notebook .Spec .Template .Spec .Volumes , corev1.Volume {
269
+ Name : req .Name ,
270
+ VolumeSource : corev1.VolumeSource {
271
+ PersistentVolumeClaim : & corev1.PersistentVolumeClaimVolumeSource {
272
+ ClaimName : req .Name ,
273
+ },
274
+ },
275
+ })
276
+
277
+ notebook .Spec .Template .Spec .Containers [0 ].VolumeMounts = append (notebook .Spec .Template .Spec .Containers [0 ].VolumeMounts , corev1.VolumeMount {
278
+ Name : req .Name ,
279
+ MountPath : req .Path ,
280
+ })
281
+
250
282
return nil
251
283
}
252
284
@@ -276,7 +308,6 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) {
276
308
277
309
// Setup the notebook
278
310
// TODO: Work with default CPU/memory limits from config
279
- // TODO: Add GPU support
280
311
notebook := notebooksv1.Notebook {
281
312
ObjectMeta : v1.ObjectMeta {
282
313
Name : req .Name ,
@@ -310,15 +341,15 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) {
310
341
// Add workspace volume
311
342
if ! req .NoWorkspace {
312
343
req .Workspace .Path = WorkspacePath
313
- err = s .handleVolume (req .Workspace , & notebook )
344
+ err = s .handleVolume (r . Context (), req .Workspace , & notebook )
314
345
if err != nil {
315
346
s .error (w , r , err )
316
347
return
317
348
}
318
349
}
319
350
320
351
for _ , volreq := range req .DataVolumes {
321
- err = s .handleVolume (volreq , & notebook )
352
+ err = s .handleVolume (r . Context (), volreq , & notebook )
322
353
if err != nil {
323
354
s .error (w , r , err )
324
355
return
@@ -342,6 +373,12 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) {
342
373
})
343
374
}
344
375
376
+ // Add GPU
377
+ if ! req .GPUs .Quantity .IsZero () {
378
+ notebook .Spec .Template .Spec .Containers [0 ].Resources .Requests [corev1 .ResourceName (req .GPUs .Vendor )] = req .GPUs .Quantity
379
+ notebook .Spec .Template .Spec .Containers [0 ].Resources .Limits [corev1 .ResourceName (req .GPUs .Vendor )] = req .GPUs .Quantity
380
+ }
381
+
345
382
log .Printf ("creating notebook %q for %q" , notebook .ObjectMeta .Name , namespace )
346
383
347
384
// Submit the notebook to the API server
0 commit comments