@@ -19,6 +19,8 @@ package controller
19
19
import (
20
20
"context"
21
21
"fmt"
22
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23
+ "os"
22
24
"reflect"
23
25
"strings"
24
26
@@ -346,6 +348,19 @@ func (r *WorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
346
348
// TODO: reconcile the Istio VirtualService to expose the Workspace
347
349
// and implement the `spec.podTemplate.httpProxy` options
348
350
//
351
+ virtualService , err := GenerateIstioVirtualService (workspace , workspaceKind , currentImageConfig , serviceName )
352
+ if err != nil {
353
+ log .Error (err , "unable to generate Istio Virtual Service" )
354
+ }
355
+ log .Info (fmt .Sprintf ("VirtualService %s" , virtualService ))
356
+
357
+ if err := ctrl .SetControllerReference (workspace , virtualService , r .Scheme ); err != nil {
358
+ return ctrl.Result {}, err
359
+ }
360
+
361
+ if err := ReconcileVirtualService (ctx , r .Client , virtualService .GetName (), virtualService .GetNamespace (), virtualService , log ); err != nil {
362
+ return ctrl.Result {}, err
363
+ }
349
364
350
365
// fetch Pod
351
366
// NOTE: the first StatefulSet Pod is always called "{statefulSetName}-0"
@@ -1003,3 +1018,124 @@ func (r *WorkspaceReconciler) generateWorkspaceStatus(ctx context.Context, log l
1003
1018
status .StateMessage = stateMsgUnknown
1004
1019
return status , nil
1005
1020
}
1021
+
1022
+ const istioApiVersion = "networking.istio.io/v1"
1023
+ const virtualServiceKind = "VirtualService"
1024
+
1025
+ func GenerateIstioVirtualService (workspace * kubefloworgv1beta1.Workspace , workspaceKind * kubefloworgv1beta1.WorkspaceKind , imageConfig * kubefloworgv1beta1.ImageConfigValue , serviceName string ) (* unstructured.Unstructured , error ) {
1026
+
1027
+ virtualService := & unstructured.Unstructured {}
1028
+ virtualService .SetAPIVersion (istioApiVersion )
1029
+ virtualService .SetKind (virtualServiceKind )
1030
+
1031
+ prefix := generateNamePrefix (workspace .Name , maxServiceNameLength )
1032
+ virtualService .SetName (removeTrailingDash (prefix ))
1033
+ virtualService .SetNamespace (workspace .Namespace )
1034
+
1035
+ // .spec.gateways
1036
+ istioGateway := getEnvOrDefault ("ISTIO_GATEWAY" , "kubeflow/kubeflow-gateway" )
1037
+ if err := unstructured .SetNestedStringSlice (virtualService .Object , []string {istioGateway },
1038
+ "spec" , "gateways" ); err != nil {
1039
+ return nil , fmt .Errorf ("set .spec.gateways error: %v" , err )
1040
+ }
1041
+
1042
+ istioHost := getEnvOrDefault ("ISTIO_HOST" , "*" )
1043
+ if err := unstructured .SetNestedStringSlice (virtualService .Object , []string {istioHost },
1044
+ "spec" , "gateways" ); err != nil {
1045
+ return nil , fmt .Errorf ("set .spec.hosts error: %v" , err )
1046
+ }
1047
+
1048
+ var prefixes []string
1049
+ for _ , imagePort := range imageConfig .Spec .Ports {
1050
+ prefix := fmt .Sprintf ("/workspace/%s/%s/%s" , workspace .Namespace , workspace .Name , imagePort .Id )
1051
+ prefixes = append (prefixes , prefix )
1052
+ }
1053
+
1054
+ var httpRoutes []interface {}
1055
+
1056
+ _ = fmt .Sprintf ("%s.%s.svc.%s" , workspace .Name , workspace .Namespace , getEnvOrDefault ("CLUSTER_DOMAIN" , "cluster.local" ))
1057
+ for _ , imagePort := range imageConfig .Spec .Ports {
1058
+
1059
+ httpRoute := map [string ]interface {}{
1060
+ "match" : []map [string ]interface {}{
1061
+ {
1062
+ "uri" : map [string ]interface {}{
1063
+ "prefix" : fmt .Sprintf ("/workspace/%s/%s/%s" , workspace .Namespace , workspace .Name , imagePort .Id ),
1064
+ },
1065
+ },
1066
+ },
1067
+ "route" : []map [string ]interface {}{
1068
+ {
1069
+ "destination" : map [string ]interface {}{
1070
+ "host" : fmt .Sprintf ("%s.%s.svc.%s" , serviceName , workspace .Namespace , getEnvOrDefault ("CLUSTER_DOMAIN" , "cluster.local" )),
1071
+ "port" : map [string ]interface {}{
1072
+ "number" : imagePort .Port ,
1073
+ },
1074
+ },
1075
+ },
1076
+ },
1077
+ }
1078
+
1079
+ if * workspaceKind .Spec .PodTemplate .HTTPProxy .RemovePathPrefix {
1080
+ httpRoute ["rewrite" ] = map [string ]interface {}{"uri" : "/" }
1081
+ }
1082
+
1083
+ httpRoutes = append (httpRoutes , httpRoute )
1084
+ }
1085
+
1086
+ virtualService .Object ["spec" ] = map [string ]interface {}{
1087
+ "gateways" : []string {
1088
+ istioGateway ,
1089
+ },
1090
+ "hosts" : []string {
1091
+ istioHost ,
1092
+ },
1093
+ "http" : httpRoutes ,
1094
+ }
1095
+
1096
+ return virtualService , nil
1097
+ }
1098
+
1099
+ func getEnvOrDefault (name , defaultValue string ) string {
1100
+ if lookupEnv , exists := os .LookupEnv (name ); exists {
1101
+ return lookupEnv
1102
+ } else {
1103
+ return defaultValue
1104
+ }
1105
+ }
1106
+
1107
+ func ReconcileVirtualService (ctx context.Context , r client.Client , virtualServiceName , namespace string , virtualService * unstructured.Unstructured , log logr.Logger ) error {
1108
+ foundVirtualService := & unstructured.Unstructured {}
1109
+ foundVirtualService .SetAPIVersion (istioApiVersion )
1110
+ foundVirtualService .SetKind (virtualServiceKind )
1111
+ justCreated := false
1112
+ if err := r .Get (ctx , types.NamespacedName {Name : virtualServiceName , Namespace : namespace }, foundVirtualService ); err != nil {
1113
+ if apierrors .IsNotFound (err ) {
1114
+ log .Info ("Creating virtual service" , "namespace" , namespace , "name" , virtualServiceName )
1115
+ if err := r .Create (ctx , virtualService ); err != nil {
1116
+ log .Error (err , "unable to create virtual service" )
1117
+ return err
1118
+ }
1119
+ justCreated = true
1120
+ } else {
1121
+ log .Error (err , "error getting virtual service" )
1122
+ return err
1123
+ }
1124
+ }
1125
+ if ! justCreated { // TODO: we need to evict unnecessary update
1126
+ log .Info ("Updating virtual service" , "namespace" , namespace , "name" , virtualServiceName )
1127
+ if err := r .Update (ctx , foundVirtualService ); err != nil {
1128
+ log .Error (err , "unable to update virtual service" )
1129
+ return err
1130
+ }
1131
+ }
1132
+
1133
+ return nil
1134
+ }
1135
+
1136
+ func removeTrailingDash (s string ) string {
1137
+ if len (s ) > 0 && s [len (s )- 1 ] == '-' {
1138
+ return s [:len (s )- 1 ]
1139
+ }
1140
+ return s
1141
+ }
0 commit comments