Skip to content

Commit

Permalink
Add call to reconcile VirtualService
Browse files Browse the repository at this point in the history
  • Loading branch information
mcruzdev committed Sep 14, 2024
1 parent bc4e445 commit 6764331
Show file tree
Hide file tree
Showing 4 changed files with 16,699 additions and 1 deletion.
37 changes: 37 additions & 0 deletions workspaces/controller/KIND.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Running Notebooks Workspace with Kind


## Create KinD cluster

```shell
kind create cluster
```

## Install CertManager

```shell
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.3/cert-manager.yaml
```

Check if CertManager pods are running

```shell
kubectl get pods --namespace cert-manager
```

The output should looks something like it:

```shell
NAME READY STATUS RESTARTS AGE
cert-manager-7fbbc65b49-x62l8 1/1 Running 0 4m49s
cert-manager-cainjector-6664fc84f6-rckz5 1/1 Running 0 4m49s
cert-manager-webhook-59598898fd-sq87w 1/1 Running 0 4m49s
```

## Create jupyterlab namespace

Create a namespace called `jupyterlab`

```shell
kubectl create namespace jupyterlab
```
2 changes: 1 addition & 1 deletion workspaces/controller/internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var _ = BeforeSuite(func() {

By("bootstrapping test environment")
testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases"), filepath.Join("..", "..", "test", "crd")},
ErrorIfCRDPathMissing: true,

// The BinaryAssetsDirectory is only required if you want to run the tests directly without call the makefile target test.
Expand Down
136 changes: 136 additions & 0 deletions workspaces/controller/internal/controller/workspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package controller
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"os"
"reflect"
"strings"

Expand Down Expand Up @@ -346,6 +348,19 @@ func (r *WorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
// TODO: reconcile the Istio VirtualService to expose the Workspace
// and implement the `spec.podTemplate.httpProxy` options
//
virtualService, err := GenerateIstioVirtualService(workspace, workspaceKind, currentImageConfig, serviceName)
if err != nil {
log.Error(err, "unable to generate Istio Virtual Service")
}
log.Info(fmt.Sprintf("VirtualService %s", virtualService))

if err := ctrl.SetControllerReference(workspace, virtualService, r.Scheme); err != nil {
return ctrl.Result{}, err
}

if err := ReconcileVirtualService(ctx, r.Client, virtualService.GetName(), virtualService.GetNamespace(), virtualService, log); err != nil {
return ctrl.Result{}, err
}

// fetch Pod
// NOTE: the first StatefulSet Pod is always called "{statefulSetName}-0"
Expand Down Expand Up @@ -1003,3 +1018,124 @@ func (r *WorkspaceReconciler) generateWorkspaceStatus(ctx context.Context, log l
status.StateMessage = stateMsgUnknown
return status, nil
}

const istioApiVersion = "networking.istio.io/v1"
const virtualServiceKind = "VirtualService"

func GenerateIstioVirtualService(workspace *kubefloworgv1beta1.Workspace, workspaceKind *kubefloworgv1beta1.WorkspaceKind, imageConfig *kubefloworgv1beta1.ImageConfigValue, serviceName string) (*unstructured.Unstructured, error) {

virtualService := &unstructured.Unstructured{}
virtualService.SetAPIVersion(istioApiVersion)
virtualService.SetKind(virtualServiceKind)

prefix := generateNamePrefix(workspace.Name, maxServiceNameLength)
virtualService.SetName(removeTrailingDash(prefix))
virtualService.SetNamespace(workspace.Namespace)

// .spec.gateways
istioGateway := getEnvOrDefault("ISTIO_GATEWAY", "kubeflow/kubeflow-gateway")
if err := unstructured.SetNestedStringSlice(virtualService.Object, []string{istioGateway},
"spec", "gateways"); err != nil {
return nil, fmt.Errorf("set .spec.gateways error: %v", err)
}

istioHost := getEnvOrDefault("ISTIO_HOST", "*")
if err := unstructured.SetNestedStringSlice(virtualService.Object, []string{istioHost},
"spec", "gateways"); err != nil {
return nil, fmt.Errorf("set .spec.hosts error: %v", err)
}

var prefixes []string
for _, imagePort := range imageConfig.Spec.Ports {
prefix := fmt.Sprintf("/workspace/%s/%s/%s", workspace.Namespace, workspace.Name, imagePort.Id)
prefixes = append(prefixes, prefix)
}

var httpRoutes []interface{}

_ = fmt.Sprintf("%s.%s.svc.%s", workspace.Name, workspace.Namespace, getEnvOrDefault("CLUSTER_DOMAIN", "cluster.local"))
for _, imagePort := range imageConfig.Spec.Ports {

httpRoute := map[string]interface{}{
"match": []map[string]interface{}{
{
"uri": map[string]interface{}{
"prefix": fmt.Sprintf("/workspace/%s/%s/%s", workspace.Namespace, workspace.Name, imagePort.Id),
},
},
},
"route": []map[string]interface{}{
{
"destination": map[string]interface{}{
"host": fmt.Sprintf("%s.%s.svc.%s", serviceName, workspace.Namespace, getEnvOrDefault("CLUSTER_DOMAIN", "cluster.local")),
"port": map[string]interface{}{
"number": imagePort.Port,
},
},
},
},
}

if *workspaceKind.Spec.PodTemplate.HTTPProxy.RemovePathPrefix {
httpRoute["rewrite"] = map[string]interface{}{"uri": "/"}
}

httpRoutes = append(httpRoutes, httpRoute)
}

virtualService.Object["spec"] = map[string]interface{}{
"gateways": []string{
istioGateway,
},
"hosts": []string{
istioHost,
},
"http": httpRoutes,
}

return virtualService, nil
}

func getEnvOrDefault(name, defaultValue string) string {
if lookupEnv, exists := os.LookupEnv(name); exists {
return lookupEnv
} else {
return defaultValue
}
}

func ReconcileVirtualService(ctx context.Context, r client.Client, virtualServiceName, namespace string, virtualService *unstructured.Unstructured, log logr.Logger) error {
foundVirtualService := &unstructured.Unstructured{}
foundVirtualService.SetAPIVersion(istioApiVersion)
foundVirtualService.SetKind(virtualServiceKind)
justCreated := false
if err := r.Get(ctx, types.NamespacedName{Name: virtualServiceName, Namespace: namespace}, foundVirtualService); err != nil {
if apierrors.IsNotFound(err) {
log.Info("Creating virtual service", "namespace", namespace, "name", virtualServiceName)
if err := r.Create(ctx, virtualService); err != nil {
log.Error(err, "unable to create virtual service")
return err
}
justCreated = true
} else {
log.Error(err, "error getting virtual service")
return err
}
}
if !justCreated { // TODO: we need to evict unnecessary update
log.Info("Updating virtual service", "namespace", namespace, "name", virtualServiceName)
if err := r.Update(ctx, foundVirtualService); err != nil {
log.Error(err, "unable to update virtual service")
return err
}
}

return nil
}

func removeTrailingDash(s string) string {
if len(s) > 0 && s[len(s)-1] == '-' {
return s[:len(s)-1]
}
return s
}
Loading

0 comments on commit 6764331

Please sign in to comment.