Skip to content

Commit e3bcdf7

Browse files
author
Zachary Seguin
committed
feat(config): Add config
1 parent a8363a5 commit e3bcdf7

File tree

5 files changed

+356
-25
lines changed

5 files changed

+356
-25
lines changed

config.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package main
2+
3+
import "net/http"
4+
5+
type ConfigurationsConfiguration struct {
6+
Value []string `yaml:"value" json:"value"`
7+
ReadOnly bool `yaml:"readOnly" json:"readOnly"`
8+
}
9+
10+
type SharedMemoryConfiguration struct {
11+
Value bool `yaml:"value" json:"value"`
12+
ReadOnly bool `yaml:"readOnly" json:"readOnly"`
13+
}
14+
15+
type GPUVendorConfiguration struct {
16+
LimitsKey string `yaml:"limitsKey" json:"limitsKey"`
17+
UIName string `yaml:"uiName" json:"uiName"`
18+
}
19+
20+
type GPUValueConfiguration struct {
21+
Quantity string `yaml:"num" json:"num"`
22+
Vendors []GPUVendorConfiguration `yaml:"vendors" json:"vendors"`
23+
Vendor string `yaml:"vendor" json:"vendor"`
24+
}
25+
26+
type GPUConfiguration struct {
27+
Value GPUValueConfiguration `yaml:"value" json:"value"`
28+
ReadOnly bool `yaml:"readOnly" json:"readOnly"`
29+
}
30+
31+
type ValueConfiguration struct {
32+
Value string `yaml:"value" json:"value"`
33+
}
34+
35+
type VolumeValueConfiguration struct {
36+
Type ValueConfiguration `yaml:"type" json:"type"`
37+
Name ValueConfiguration `yaml:"name" json:"name"`
38+
Size ValueConfiguration `yaml:"size" json:"size"`
39+
MountPath ValueConfiguration `yaml:"mountPath" json:"mountPath"`
40+
AccessModes ValueConfiguration `yaml:"accessModes" json:"accessModes"`
41+
Class ValueConfiguration `yaml:"class" json:"class"`
42+
}
43+
44+
type DataVolumesConfiguration struct {
45+
Values []VolumeValueConfiguration `yaml:"value" json:"value"`
46+
ReadOnly bool `yaml:"readOnly" json:"readOnly"`
47+
}
48+
49+
type WorkspaceVolumeConfiguration struct {
50+
Value VolumeValueConfiguration `yaml:"value" json:"value"`
51+
ReadOnly bool `yaml:"readOnly" json:"readOnly"`
52+
}
53+
54+
type ResourceConfiguration struct {
55+
Value string `yaml:"value" json:"value"`
56+
ReadOnly bool `yaml:"readOnly" json:"readOnly"`
57+
}
58+
59+
type ImageConfiguration struct {
60+
Value string `yaml:"value" json:"value"`
61+
Options []string `yaml:"options" json:"options"`
62+
ReadOnly bool `yaml:"readOnly" json:"readOnly"`
63+
HideRegistry bool `yaml:"hideRegistry" json:"hideRegistry"`
64+
HideVersion bool `yaml:"hideVersion" json:"hideVersion"`
65+
}
66+
67+
type SpawnerFormDefaults struct {
68+
Image ImageConfiguration `yaml:"image" json:"image"`
69+
CPU ResourceConfiguration `yaml:"cpu" json:"cpu"`
70+
Memory ResourceConfiguration `yaml:"memory" json:"memory"`
71+
WorkspaceVolume WorkspaceVolumeConfiguration `yaml:"workspaceVolume" json:"workspaceVolume"`
72+
DataVolumes DataVolumesConfiguration `yaml:"dataVolumes" json:"dataVolumes"`
73+
GPUs GPUConfiguration `yaml:"gpus" json:"gpus"`
74+
SharedMemory SharedMemoryConfiguration `yaml:"shm" json:"shm"`
75+
Configurations ConfigurationsConfiguration `yaml:"configurations" json:"configurations"`
76+
}
77+
78+
type Configuration struct {
79+
SpawnerFormDefaults SpawnerFormDefaults `yaml:"spawnerFormDefaults" json:"spawnerFormDefaults"`
80+
}
81+
82+
type configresponse struct {
83+
APIResponse
84+
Config SpawnerFormDefaults `json:"config"`
85+
}
86+
87+
func (s *server) GetConfig(w http.ResponseWriter, r *http.Request) {
88+
s.respond(w, r, configresponse{
89+
APIResponse: APIResponse{
90+
Success: true,
91+
},
92+
Config: s.Config.SpawnerFormDefaults,
93+
})
94+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
golang.org/x/text v0.3.3 // indirect
2020
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
2121
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
22+
gopkg.in/yaml.v2 v2.3.0
2223
k8s.io/api v0.18.6
2324
k8s.io/apimachinery v0.18.6
2425
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible

main.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"flag"
6+
"io/ioutil"
67
"log"
78
"net/http"
89
"os"
@@ -18,6 +19,7 @@ import (
1819
kubeflowv1alpha1listers "github.com/StatCan/kubeflow-controller/pkg/generated/listers/kubeflowcontroller/v1alpha1"
1920
"github.com/gorilla/handlers"
2021
"github.com/gorilla/mux"
22+
"gopkg.in/yaml.v2"
2123
authorizationv1 "k8s.io/api/authorization/v1"
2224
corev1 "k8s.io/api/core/v1"
2325
"k8s.io/client-go/kubernetes"
@@ -28,6 +30,7 @@ import (
2830
)
2931

3032
var kubeconfig string
33+
var spawnerConfigPath string
3134
var userIDHeader string
3235

3336
type listers struct {
@@ -46,6 +49,8 @@ type clientsets struct {
4649
type server struct {
4750
mux sync.Mutex
4851

52+
Config Configuration
53+
4954
clientsets clientsets
5055
listers listers
5156
}
@@ -63,6 +68,24 @@ func main() {
6368
}
6469

6570
flag.StringVar(&userIDHeader, "userid-header", "kubeflow-userid", "header in the request which identifies the incoming user")
71+
flag.StringVar(&spawnerConfigPath, "spawner-config", "/etc/config/spawner_ui_config.yaml", "path to the spawner configuration file")
72+
73+
// Parse flags
74+
flag.Parse()
75+
76+
// Setup the server
77+
s := server{}
78+
79+
// Parse config
80+
cfdata, err := ioutil.ReadFile(spawnerConfigPath)
81+
if err != nil {
82+
log.Fatal(err)
83+
}
84+
85+
err = yaml.Unmarshal(cfdata, &s.Config)
86+
if err != nil {
87+
log.Fatal(err)
88+
}
6689

6790
// Construct the configuration based on the provided flags.
6891
// If no config file is provided, then the in-cluster config is used.
@@ -71,8 +94,6 @@ func main() {
7194
log.Fatal(err)
7295
}
7396

74-
s := server{}
75-
7697
// Generate the Kubernetes clientset
7798
s.clientsets.kubernetes, err = kubernetes.NewForConfig(config)
7899
if err != nil {
@@ -91,6 +112,7 @@ func main() {
91112
router := mux.NewRouter()
92113

93114
// Setup route handlers
115+
router.HandleFunc("/api/config", s.GetConfig).Methods("GET")
94116
router.HandleFunc("/api/storageclasses/default", s.GetDefaultStorageClass).Methods("GET")
95117

96118
router.HandleFunc("/api/namespaces/{namespace}/notebooks", s.checkAccess(authorizationv1.SubjectAccessReview{

notebooks.go

Lines changed: 103 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
const DefaultServiceAccountName string = "default-editor"
2323
const SharedMemoryVolumeName string = "dshm"
2424
const SharedMemoryVolumePath string = "/dev/shm"
25-
const WorkspacePath string = "/home/jovyan"
2625

2726
type volumetype string
2827

@@ -294,6 +293,9 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) {
294293
if req.CustomImageCheck {
295294
image = req.CustomImage
296295
}
296+
if s.Config.SpawnerFormDefaults.Image.ReadOnly {
297+
image = s.Config.SpawnerFormDefaults.Image.Value
298+
}
297299

298300
// Setup the notebook
299301
// TODO: Work with default CPU/memory limits from config
@@ -311,14 +313,8 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) {
311313
Name: req.Name,
312314
Image: image,
313315
Resources: corev1.ResourceRequirements{
314-
Limits: corev1.ResourceList{
315-
corev1.ResourceCPU: req.CPU,
316-
corev1.ResourceMemory: req.Memory,
317-
},
318-
Requests: corev1.ResourceList{
319-
corev1.ResourceCPU: req.CPU,
320-
corev1.ResourceMemory: req.Memory,
321-
},
316+
Requests: corev1.ResourceList{},
317+
Limits: corev1.ResourceList{},
322318
},
323319
},
324320
},
@@ -327,26 +323,97 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) {
327323
},
328324
}
329325

330-
// Add workspace volume
331-
if !req.NoWorkspace {
332-
req.Workspace.Path = WorkspacePath
333-
err = s.handleVolume(r.Context(), req.Workspace, &notebook)
326+
// Resources
327+
if s.Config.SpawnerFormDefaults.CPU.ReadOnly {
328+
val, err := resource.ParseQuantity(s.Config.SpawnerFormDefaults.CPU.Value)
334329
if err != nil {
335330
s.error(w, r, err)
336331
return
337332
}
333+
334+
notebook.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU] = val
335+
notebook.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = val
336+
} else {
337+
notebook.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceCPU] = req.CPU
338+
notebook.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceCPU] = req.CPU
338339
}
339340

340-
for _, volreq := range req.DataVolumes {
341-
err = s.handleVolume(r.Context(), volreq, &notebook)
341+
if s.Config.SpawnerFormDefaults.Memory.ReadOnly {
342+
val, err := resource.ParseQuantity(s.Config.SpawnerFormDefaults.Memory.Value)
342343
if err != nil {
343344
s.error(w, r, err)
344345
return
345346
}
347+
348+
notebook.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = val
349+
notebook.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = val
350+
} else {
351+
notebook.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = req.Memory
352+
notebook.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = req.Memory
353+
}
354+
355+
// Add workspace volume
356+
if s.Config.SpawnerFormDefaults.WorkspaceVolume.ReadOnly {
357+
size, err := resource.ParseQuantity(s.Config.SpawnerFormDefaults.WorkspaceVolume.Value.Size.Value)
358+
if err != nil {
359+
s.error(w, r, err)
360+
return
361+
}
362+
363+
workspaceVol := volumerequest{
364+
Name: s.Config.SpawnerFormDefaults.WorkspaceVolume.Value.Name.Value,
365+
Size: size,
366+
Path: s.Config.SpawnerFormDefaults.WorkspaceVolume.Value.MountPath.Value,
367+
Mode: corev1.PersistentVolumeAccessMode(s.Config.SpawnerFormDefaults.WorkspaceVolume.Value.AccessModes.Value),
368+
Class: s.Config.SpawnerFormDefaults.WorkspaceVolume.Value.Class.Value,
369+
}
370+
err = s.handleVolume(r.Context(), workspaceVol, &notebook)
371+
if err != nil {
372+
s.error(w, r, err)
373+
return
374+
}
375+
} else if !req.NoWorkspace {
376+
req.Workspace.Path = s.Config.SpawnerFormDefaults.WorkspaceVolume.Value.MountPath.Value
377+
err = s.handleVolume(r.Context(), req.Workspace, &notebook)
378+
if err != nil {
379+
s.error(w, r, err)
380+
return
381+
}
382+
}
383+
384+
if s.Config.SpawnerFormDefaults.DataVolumes.ReadOnly {
385+
for _, volreq := range s.Config.SpawnerFormDefaults.DataVolumes.Values {
386+
size, err := resource.ParseQuantity(s.Config.SpawnerFormDefaults.WorkspaceVolume.Value.Size.Value)
387+
if err != nil {
388+
s.error(w, r, err)
389+
return
390+
}
391+
392+
vol := volumerequest{
393+
Name: volreq.Name.Value,
394+
Size: size,
395+
Path: volreq.MountPath.Value,
396+
Mode: corev1.PersistentVolumeAccessMode(volreq.AccessModes.Value),
397+
Class: volreq.Class.Value,
398+
}
399+
err = s.handleVolume(r.Context(), vol, &notebook)
400+
if err != nil {
401+
s.error(w, r, err)
402+
return
403+
}
404+
}
405+
} else {
406+
for _, volreq := range req.DataVolumes {
407+
err = s.handleVolume(r.Context(), volreq, &notebook)
408+
if err != nil {
409+
s.error(w, r, err)
410+
return
411+
}
412+
}
346413
}
347414

348415
// Add shared memory, if enabled
349-
if req.EnableSharedMemory {
416+
if (s.Config.SpawnerFormDefaults.SharedMemory.ReadOnly && s.Config.SpawnerFormDefaults.SharedMemory.Value) || (!s.Config.SpawnerFormDefaults.SharedMemory.ReadOnly && req.EnableSharedMemory) {
350417
notebook.Spec.Template.Spec.Volumes = append(notebook.Spec.Template.Spec.Volumes, corev1.Volume{
351418
Name: SharedMemoryVolumeName,
352419
VolumeSource: corev1.VolumeSource{
@@ -363,15 +430,28 @@ func (s *server) NewNotebook(w http.ResponseWriter, r *http.Request) {
363430
}
364431

365432
// Add GPU
366-
if req.GPUs.Quantity != "none" {
367-
qty, err := resource.ParseQuantity(req.GPUs.Quantity)
368-
if err != nil {
369-
s.error(w, r, err)
370-
return
433+
if s.Config.SpawnerFormDefaults.GPUs.ReadOnly {
434+
if s.Config.SpawnerFormDefaults.GPUs.Value.Quantity != "none" {
435+
qty, err := resource.ParseQuantity(s.Config.SpawnerFormDefaults.GPUs.Value.Quantity)
436+
if err != nil {
437+
s.error(w, r, err)
438+
return
439+
}
440+
441+
notebook.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceName(s.Config.SpawnerFormDefaults.GPUs.Value.Vendor)] = qty
442+
notebook.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceName(s.Config.SpawnerFormDefaults.GPUs.Value.Vendor)] = qty
371443
}
444+
} else {
445+
if req.GPUs.Quantity != "none" {
446+
qty, err := resource.ParseQuantity(req.GPUs.Quantity)
447+
if err != nil {
448+
s.error(w, r, err)
449+
return
450+
}
372451

373-
notebook.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceName(req.GPUs.Vendor)] = qty
374-
notebook.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceName(req.GPUs.Vendor)] = qty
452+
notebook.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceName(req.GPUs.Vendor)] = qty
453+
notebook.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceName(req.GPUs.Vendor)] = qty
454+
}
375455
}
376456

377457
log.Printf("creating notebook %q for %q", notebook.ObjectMeta.Name, namespace)

0 commit comments

Comments
 (0)