This is an automated email from the ASF dual-hosted git repository.

liuxun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/submarine.git


The following commit(s) were added to refs/heads/master by this push:
     new f98c6822 SUBMARINE-976. Configure ingress routing for services across 
different namespaces (#985)
f98c6822 is described below

commit f98c6822137c05e09cf9378004fd2ac5d92567c8
Author: Josh Chuang <[email protected]>
AuthorDate: Tue Sep 13 18:53:19 2022 +0800

    SUBMARINE-976. Configure ingress routing for services across different 
namespaces (#985)
    
    ### What is this PR for?
    <!-- A few sentences describing the overall goals of the pull request's 
commits.
    First time? Check out the contributing guide - 
https://submarine.apache.org/contribution/contributions.html
    -->
    Configure Istio virtual service routing for submarine services across 
different namespaces.
    
    For example, a submarine workbench created by submitting `Submarine` custom 
resource to k8s namespace `submarine-user-test`, can now only be connected to 
with host `submarine-user-test.submarine` as the destination e.g. via URL 
`http://submarine-user-test.submarine`.
    
    To configure this, edit the newly added `spec.virtualservice.hosts` field 
of the submarine CR and `spec.host` of the virtual service will be set 
accordingly. For consistency, `spec.gateways` of the virtual service can now 
also be set through editing the submarine CR.
    
    ### What type of PR is it?
    Feature
    
    ### Todos
    * [x] - update operator source file and artifact file
    * [x] - add e2e test cases
    * [x] - add new development doc
    
    ### What is the Jira issue?
    <!-- * Open an issue on Jira 
https://issues.apache.org/jira/browse/SUBMARINE/
    * Put link here, and add [SUBMARINE-*Jira number*] in PR title, eg. 
`SUBMARINE-23. PR title`
    -->
    https://issues.apache.org/jira/browse/SUBMARINE-976
    
    ### How should this be tested?
    <!--
    * First time? Setup Travis CI as described on 
https://submarine.apache.org/contribution/contributions.html#continuous-integration
    * Strongly recommended: add automated unit tests for any new or changed 
behavior
    * Outline any manual steps to test the PR here.
    -->
    Follow submarine-cloud-v3/docs/developer-guide.md
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Do the license files need updating? No
    * Are there breaking changes for older versions? Yes
    * Does this need new documentation? Yes
---
 submarine-cloud-v3/api/v1alpha1/submarine_types.go |  20 +-
 .../api/v1alpha1/zz_generated.deepcopy.go          |  30 +++
 .../artifacts/submarine-virtualservice.yaml        |   2 -
 .../crd/bases/submarine.apache.org_submarines.yaml |  17 ++
 .../config/samples/_v1alpha1_submarine.yaml        |   5 +
 .../controllers/submarine_controller_test.go       | 202 ++++++++++++++++++---
 .../controllers/submarine_virtualservice.go        |  16 ++
 submarine-cloud-v3/docs/developer-guide.md         |  64 ++++++-
 8 files changed, 313 insertions(+), 43 deletions(-)

diff --git a/submarine-cloud-v3/api/v1alpha1/submarine_types.go 
b/submarine-cloud-v3/api/v1alpha1/submarine_types.go
index bac88cbd..5acc791f 100644
--- a/submarine-cloud-v3/api/v1alpha1/submarine_types.go
+++ b/submarine-cloud-v3/api/v1alpha1/submarine_types.go
@@ -46,6 +46,8 @@ type SubmarineSpec struct {
        Server *SubmarineServerSpec `json:"server"`
        // Database is the spec that defines the submarine database
        Database *SubmarineDatabaseSpec `json:"database"`
+       // Virtualservice is the spec that defines the submarine virtualservice
+       Virtualservice *SubmarineVirtualserviceSpec `json:"virtualservice"`
        // Tensorboard is the spec that defines the submarine tensorboard
        Tensorboard *SubmarineTensorboardSpec `json:"tensorboard"`
        // Mlflow is the spec that defines the submarine mlflow
@@ -54,7 +56,7 @@ type SubmarineSpec struct {
        Minio *SubmarineMinioSpec `json:"minio"`
 }
 
-// SubmarineServerSpec defins the desired submarine server
+// SubmarineServerSpec defines the desired submarine server
 type SubmarineServerSpec struct {
        // Image is the submarine server's docker image
        Image string `json:"image"`
@@ -63,7 +65,7 @@ type SubmarineServerSpec struct {
        Replicas *int32 `json:"replicas"`
 }
 
-// SubmarineServerSpec defins the desired submarine database
+// SubmarineServerSpec defines the desired submarine database
 type SubmarineDatabaseSpec struct {
        // Image is the submarine database's docker image
        Image string `json:"image"`
@@ -73,7 +75,15 @@ type SubmarineDatabaseSpec struct {
        MysqlRootPasswordSecret string `json:"mysqlRootPasswordSecret"`
 }
 
-// SubmarineServerSpec defins the desired submarine tensorboard
+// SubmarineVirtualserviceSpec defines the desired submarine virtualservice
+type SubmarineVirtualserviceSpec struct {
+       // Hosts is the submarine virtualservice's destination hosts
+       Hosts []string `json:"hosts,omitempty"`
+       // Hosts is the submarine virtualservice's gateways
+       Gateways []string `json:"gateways,omitempty"`
+}
+
+// SubmarineServerSpec defines the desired submarine tensorboard
 type SubmarineTensorboardSpec struct {
        // Enabled defines whether to enable tensorboard or not
        Enabled *bool `json:"enabled"`
@@ -81,7 +91,7 @@ type SubmarineTensorboardSpec struct {
        StorageSize string `json:"storageSize"`
 }
 
-// SubmarineServerSpec defins the desired submarine mlflow
+// SubmarineServerSpec defines the desired submarine mlflow
 type SubmarineMlflowSpec struct {
        // Enabled defines whether to enable mlflow or not
        Enabled *bool `json:"enabled"`
@@ -89,7 +99,7 @@ type SubmarineMlflowSpec struct {
        StorageSize string `json:"storageSize"`
 }
 
-// SubmarineServerSpec defins the desired submarine minio
+// SubmarineServerSpec defines the desired submarine minio
 type SubmarineMinioSpec struct {
        // Enabled defines whether to enable minio or not
        Enabled *bool `json:"enabled"`
diff --git a/submarine-cloud-v3/api/v1alpha1/zz_generated.deepcopy.go 
b/submarine-cloud-v3/api/v1alpha1/zz_generated.deepcopy.go
index 8bf981a5..bd02163c 100644
--- a/submarine-cloud-v3/api/v1alpha1/zz_generated.deepcopy.go
+++ b/submarine-cloud-v3/api/v1alpha1/zz_generated.deepcopy.go
@@ -172,6 +172,11 @@ func (in *SubmarineSpec) DeepCopyInto(out *SubmarineSpec) {
                *out = new(SubmarineDatabaseSpec)
                **out = **in
        }
+       if in.Virtualservice != nil {
+               in, out := &in.Virtualservice, &out.Virtualservice
+               *out = new(SubmarineVirtualserviceSpec)
+               (*in).DeepCopyInto(*out)
+       }
        if in.Tensorboard != nil {
                in, out := &in.Tensorboard, &out.Tensorboard
                *out = new(SubmarineTensorboardSpec)
@@ -249,3 +254,28 @@ func (in *SubmarineTensorboardSpec) DeepCopy() 
*SubmarineTensorboardSpec {
        in.DeepCopyInto(out)
        return out
 }
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *SubmarineVirtualserviceSpec) DeepCopyInto(out 
*SubmarineVirtualserviceSpec) {
+       *out = *in
+       if in.Hosts != nil {
+               in, out := &in.Hosts, &out.Hosts
+               *out = make([]string, len(*in))
+               copy(*out, *in)
+       }
+       if in.Gateways != nil {
+               in, out := &in.Gateways, &out.Gateways
+               *out = make([]string, len(*in))
+               copy(*out, *in)
+       }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new SubmarineVirtualserviceSpec.
+func (in *SubmarineVirtualserviceSpec) DeepCopy() *SubmarineVirtualserviceSpec 
{
+       if in == nil {
+               return nil
+       }
+       out := new(SubmarineVirtualserviceSpec)
+       in.DeepCopyInto(out)
+       return out
+}
diff --git a/submarine-cloud-v3/artifacts/submarine-virtualservice.yaml 
b/submarine-cloud-v3/artifacts/submarine-virtualservice.yaml
index 6405ddea..d9c7ad81 100644
--- a/submarine-cloud-v3/artifacts/submarine-virtualservice.yaml
+++ b/submarine-cloud-v3/artifacts/submarine-virtualservice.yaml
@@ -21,8 +21,6 @@ kind: VirtualService
 metadata:
   name: submarine-virtual-service
 spec:
-  hosts:
-    - "*"
   gateways:
     - submarine-cloud-v3-system/submarine-gateway
   http:
diff --git 
a/submarine-cloud-v3/config/crd/bases/submarine.apache.org_submarines.yaml 
b/submarine-cloud-v3/config/crd/bases/submarine.apache.org_submarines.yaml
index 3db3c6b5..0cc6cf9f 100644
--- a/submarine-cloud-v3/config/crd/bases/submarine.apache.org_submarines.yaml
+++ b/submarine-cloud-v3/config/crd/bases/submarine.apache.org_submarines.yaml
@@ -128,6 +128,22 @@ spec:
               version:
                 description: Version is the submarine docker image version
                 type: string
+              virtualservice:
+                description: Virtualservice is the spec that defines the 
submarine
+                  virtualservice
+                properties:
+                  gateways:
+                    description: Hosts is the submarine virtualservice's 
gateways
+                    items:
+                      type: string
+                    type: array
+                  hosts:
+                    description: Hosts is the submarine virtualservice's 
destination
+                      hosts
+                    items:
+                      type: string
+                    type: array
+                type: object
             required:
             - database
             - minio
@@ -135,6 +151,7 @@ spec:
             - server
             - tensorboard
             - version
+            - virtualservice
             type: object
           status:
             description: SubmarineStatus defines the observed state of 
Submarine
diff --git a/submarine-cloud-v3/config/samples/_v1alpha1_submarine.yaml 
b/submarine-cloud-v3/config/samples/_v1alpha1_submarine.yaml
index 8ae4c859..844f2afb 100644
--- a/submarine-cloud-v3/config/samples/_v1alpha1_submarine.yaml
+++ b/submarine-cloud-v3/config/samples/_v1alpha1_submarine.yaml
@@ -28,6 +28,11 @@ spec:
     image: "apache/submarine:database-0.8.0-SNAPSHOT" # overwrite the image 
when development
     storageSize: "1Gi"
     mysqlRootPasswordSecret: "root-pass-secret"
+  virtualservice:
+    hosts: 
+    #   - "yourHost" # configure when using custom hosts
+    gateways:
+    #   - "yourNamespace/submarine-gateway" # configure when installing Helm 
in a different namespace
   tensorboard:
     enabled: true
     storageSize: "10Gi"
diff --git a/submarine-cloud-v3/controllers/submarine_controller_test.go 
b/submarine-cloud-v3/controllers/submarine_controller_test.go
index 741e3e87..536bd98c 100644
--- a/submarine-cloud-v3/controllers/submarine_controller_test.go
+++ b/submarine-cloud-v3/controllers/submarine_controller_test.go
@@ -26,6 +26,7 @@ import (
        . "github.com/onsi/ginkgo"
        . "github.com/onsi/gomega"
        "github.com/pkg/errors"
+       istiov1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
        corev1 "k8s.io/api/core/v1"
        apierrors "k8s.io/apimachinery/pkg/api/errors"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -40,7 +41,11 @@ var _ = Describe("Submarine controller", func() {
 
        // Define utility constants and variables
        const (
-               submarineNamespace = "submarine-test-submit-custom-resource" // 
The namespace where the Submarine CR is created
+               // The namespaces where the Submarine CRs are created
+               submarineNamespaceDefaultCR = "submarine-test-submit-default-cr"
+               submarineNamespaceCustomCR  = "submarine-test-submit-custom-cr"
+               customHost                  = "submarine-custom-host"
+               customGateway               = "submarine-custom-gateway"
 
                createNsTimeout         = time.Second * 10
                createNsInterval        = time.Second * 2
@@ -54,19 +59,45 @@ var _ = Describe("Submarine controller", func() {
        var (
                // The name of Submarine is specified in the YAML file.
                // Storing name to call k8sClient.Get with NamespacedName
-               submarineName string
+               submarineNameDefaultCR  string
+               submarineNameCustomCR   string
+               submarineCustomHosts    []string
+               submarineCustomGateways []string
 
                ctx = context.Background()
        )
 
-       Context("Create a test namespace", func() {
-               It("Should create a test namespace", func() {
-                       msg := fmt.Sprintf("Creating the test namespace %s", 
submarineNamespace)
-                       By(msg)
+       Context("Create test namespaces", func() {
+               It(fmt.Sprintf("Should create namespace %s", 
submarineNamespaceDefaultCR), func() {
+                       By(fmt.Sprintf("Creating the test namespace %s", 
submarineNamespaceDefaultCR))
+                       ns := &corev1.Namespace{
+                               ObjectMeta: metav1.ObjectMeta{
+                                       Name: submarineNamespaceDefaultCR, // 
Namespace to test default CR
+                                       Labels: map[string]string{
+                                               "istio-injection": "enabled",
+                                       },
+                               },
+                       }
+                       Expect(k8sClient.Create(ctx, ns)).Should(Succeed())
 
+                       // We'll need to retry getting this newly created 
namespace, given that creation may not immediately happen.
+                       createdNs := &corev1.Namespace{} // stub
+                       Eventually(func() bool {
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNamespaceDefaultCR, Namespace: "default"}, 
createdNs)
+                               if err != nil {
+                                       return false
+                               }
+                               return true
+                       }, createNsTimeout, createNsInterval).Should(BeTrue())
+
+                       // The namespace should have Istio label
+                       
Expect(createdNs.Labels["istio-injection"]).To(Equal("enabled"))
+               })
+               It(fmt.Sprintf("Should create namespace %s", 
submarineNamespaceCustomCR), func() {
+                       By(fmt.Sprintf("Creating the test namespace %s", 
submarineNamespaceCustomCR))
                        ns := &corev1.Namespace{
                                ObjectMeta: metav1.ObjectMeta{
-                                       Name: submarineNamespace,
+                                       Name: submarineNamespaceCustomCR, // 
Namespace to test custom CR
                                        Labels: map[string]string{
                                                "istio-injection": "enabled",
                                        },
@@ -77,7 +108,7 @@ var _ = Describe("Submarine controller", func() {
                        // We'll need to retry getting this newly created 
namespace, given that creation may not immediately happen.
                        createdNs := &corev1.Namespace{} // stub
                        Eventually(func() bool {
-                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNamespace, Namespace: "default"}, createdNs)
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNamespaceCustomCR, Namespace: "default"}, 
createdNs)
                                if err != nil {
                                        return false
                                }
@@ -89,24 +120,74 @@ var _ = Describe("Submarine controller", func() {
                })
        })
 
-       Context("Create Submarine", func() {
-               It("Should create a Submarine and it should become RUNNING", 
func() {
-                       By("Creating a new Submarine")
+       Context("Create Submarines", func() {
+               It(fmt.Sprintf("Should create Submarine in %s and it should 
become RUNNING", submarineNamespaceDefaultCR), func() {
+                       By(fmt.Sprintf("Creating new Submarine in %s", 
submarineNamespaceDefaultCR))
+                       submarine, err := 
MakeSubmarineFromYaml("../config/samples/_v1alpha1_submarine.yaml")
+                       Expect(err).To(BeNil())
+
+                       // Leave Spec.Virtualservice.Host empty to test default 
value
+                       // Leave Spec.Virtualservice.Gateways empty to test 
default value
+
+                       // The name of Submarine is specified in the YAML file.
+                       // Storing name to call k8sClient.Get with 
NamespacedName
+                       submarineNameDefaultCR = submarine.Name
+
+                       // Create Submarines in our namespace
+                       submarine.Namespace = submarineNamespaceDefaultCR
+                       Expect(k8sClient.Create(ctx, 
submarine)).Should(Succeed())
+
+                       // We'll need to retry getting this newly created 
Submarine, given that creation may not immediately happen.
+                       createdSubmarine := 
&submarineapacheorgv1alpha1.Submarine{} // stub
+                       Eventually(func() bool {
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNameDefaultCR, Namespace: 
submarineNamespaceDefaultCR}, createdSubmarine)
+                               if err != nil {
+                                       return false
+                               }
+                               return true
+                       }, createNsTimeout, createNsInterval).Should(BeTrue())
+
+                       // Wait for Submarine to be in RUNNING state
+                       By(fmt.Sprintf("Waiting until Submarine %s/%s become 
RUNNING", submarineNameDefaultCR, submarineNamespaceDefaultCR))
+                       Eventually(func() bool {
+                               err = k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNameDefaultCR, Namespace: 
submarineNamespaceDefaultCR}, createdSubmarine)
+                               Expect(err).To(BeNil())
+
+                               state := 
createdSubmarine.Status.SubmarineState.State
+                               
Expect(state).ToNot(Equal(submarineapacheorgv1alpha1.FailedState))
+                               if createdSubmarine.Status.SubmarineState.State 
== submarineapacheorgv1alpha1.RunningState {
+                                       return true
+                               }
+                               return false
+                       }, createSubmarineTimeout, 
createSubmarineInterval).Should(BeTrue())
+               })
+               It(fmt.Sprintf("Should create Submarine in %s and it should 
become RUNNING", submarineNamespaceCustomCR), func() {
+                       By(fmt.Sprintf("Creating new Submarine in %s", 
submarineNamespaceCustomCR))
                        submarine, err := 
MakeSubmarineFromYaml("../config/samples/_v1alpha1_submarine.yaml")
                        Expect(err).To(BeNil())
 
+                       // Set Spec.Virtualservice.Hosts to 
[submarineCustomHosts] to test custom value
+                       submarineCustomHosts = make([]string, 1, 1)
+                       submarineCustomHosts[0] = customHost
+                       submarine.Spec.Virtualservice.Hosts = 
submarineCustomHosts
+
+                       // Set Spec.Virtualservice.Gateways to 
[submarineCustomGateway] to test custom value
+                       submarineCustomGateways = make([]string, 1, 1)
+                       submarineCustomGateways[0] = customGateway
+                       submarine.Spec.Virtualservice.Gateways = 
submarineCustomGateways
+
                        // The name of Submarine is specified in the YAML file.
                        // Storing name to call k8sClient.Get with 
NamespacedName
-                       submarineName = submarine.Name
+                       submarineNameCustomCR = submarine.Name
 
-                       // Create Submarine in our namespace
-                       submarine.Namespace = submarineNamespace
+                       // Create Submarines in our namespace
+                       submarine.Namespace = submarineNamespaceCustomCR
                        Expect(k8sClient.Create(ctx, 
submarine)).Should(Succeed())
 
                        // We'll need to retry getting this newly created 
Submarine, given that creation may not immediately happen.
                        createdSubmarine := 
&submarineapacheorgv1alpha1.Submarine{} // stub
                        Eventually(func() bool {
-                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineName, Namespace: submarineNamespace}, 
createdSubmarine)
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNameCustomCR, Namespace: 
submarineNamespaceCustomCR}, createdSubmarine)
                                if err != nil {
                                        return false
                                }
@@ -114,10 +195,9 @@ var _ = Describe("Submarine controller", func() {
                        }, createNsTimeout, createNsInterval).Should(BeTrue())
 
                        // Wait for Submarine to be in RUNNING state
-                       msg := fmt.Sprintf("Waiting until Submarine %s/%s 
become RUNNING", submarineName, submarineNamespace)
-                       By(msg)
+                       By(fmt.Sprintf("Waiting until Submarine %s/%s become 
RUNNING", submarineNameCustomCR, submarineNamespaceCustomCR))
                        Eventually(func() bool {
-                               err = k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineName, Namespace: submarineNamespace}, 
createdSubmarine)
+                               err = k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNameCustomCR, Namespace: 
submarineNamespaceCustomCR}, createdSubmarine)
                                Expect(err).To(BeNil())
 
                                state := 
createdSubmarine.Status.SubmarineState.State
@@ -130,15 +210,62 @@ var _ = Describe("Submarine controller", func() {
                })
        })
 
+       Context("Verify Virtual Service Spec", func() {
+               It(fmt.Sprintf("Hosts and Gateways should have default values 
In %s", submarineNamespaceDefaultCR), func() {
+                       By(fmt.Sprintf("Getting Virtual Service In %s", 
submarineNamespaceDefaultCR))
+                       createdVirtualService := 
&istiov1alpha3.VirtualService{} // stub
+                       err := k8sClient.Get(ctx, types.NamespacedName{Name: 
virtualServiceName, Namespace: submarineNamespaceDefaultCR}, 
createdVirtualService)
+                       Expect(err).To(BeNil())
+
+                       // The default value for host is <submarine 
namespace>.submarine
+                       
Expect(createdVirtualService.Spec.Hosts[0]).To(Equal(submarineNamespaceDefaultCR
 + ".submarine"))
+                       // The default value for gateway is 
submarine-cloud-v3-system/submarine-gateway
+                       
Expect(createdVirtualService.Spec.Gateways[0]).To(Equal("submarine-cloud-v3-system/submarine-gateway"))
+               })
+               It(fmt.Sprintf("Hosts and Gateways should have custom values In 
%s", submarineNamespaceCustomCR), func() {
+                       By(fmt.Sprintf("Getting Virtual Service In %s", 
submarineNamespaceCustomCR))
+                       createdVirtualService := 
&istiov1alpha3.VirtualService{} // stub
+                       err := k8sClient.Get(ctx, types.NamespacedName{Name: 
virtualServiceName, Namespace: submarineNamespaceCustomCR}, 
createdVirtualService)
+                       Expect(err).To(BeNil())
+
+                       // The custom value for hosts matches the submarine CR
+                       
Expect(createdVirtualService.Spec.Hosts).To(Equal(submarineCustomHosts))
+                       // The custom value for gateways matches the submarine 
CR
+                       
Expect(createdVirtualService.Spec.Gateways).To(Equal(submarineCustomGateways))
+               })
+       })
+
        Context("Delete Submarine", func() {
-               It("Should delete the Submarine", func() {
-                       Expect(submarineName).ToNot(BeNil())
+               It(fmt.Sprintf("Should delete the Submarine In %s", 
submarineNamespaceDefaultCR), func() {
+                       Expect(submarineNameDefaultCR).ToNot(BeNil())
+
+                       By(fmt.Sprintf("Deleting Submarine %s/%s", 
submarineNameDefaultCR, submarineNamespaceDefaultCR))
+                       createdSubmarine := 
&submarineapacheorgv1alpha1.Submarine{} // stub
+                       err := k8sClient.Get(ctx, types.NamespacedName{Name: 
submarineNameDefaultCR, Namespace: submarineNamespaceDefaultCR}, 
createdSubmarine)
+                       Expect(err).To(BeNil())
+
+                       foreground := metav1.DeletePropagationForeground
+                       err = k8sClient.Delete(ctx, createdSubmarine, 
&client.DeleteOptions{
+                               PropagationPolicy: &foreground,
+                       })
+                       Expect(err).To(BeNil())
 
-                       msg := fmt.Sprintf("Deleting Submarine %s/%s", 
submarineName, submarineNamespace)
-                       By(msg)
+                       // Wait for Submarine to be deleted entirely
+                       Eventually(func() bool {
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNameDefaultCR, Namespace: 
submarineNamespaceDefaultCR}, createdSubmarine)
+                               if apierrors.IsNotFound(err) {
+                                       return true
+                               }
+                               Expect(err).To(BeNil())
+                               return false
+                       }, deleteSubmarineTimeout, 
deleteSubmarineInterval).Should(BeTrue())
+               })
+               It(fmt.Sprintf("Should delete the Submarine In %s", 
submarineNamespaceCustomCR), func() {
+                       Expect(submarineNameCustomCR).ToNot(BeNil())
 
+                       By(fmt.Sprintf("Deleting Submarine %s/%s", 
submarineNameCustomCR, submarineNamespaceCustomCR))
                        createdSubmarine := 
&submarineapacheorgv1alpha1.Submarine{} // stub
-                       err := k8sClient.Get(ctx, types.NamespacedName{Name: 
submarineName, Namespace: submarineNamespace}, createdSubmarine)
+                       err := k8sClient.Get(ctx, types.NamespacedName{Name: 
submarineNameCustomCR, Namespace: submarineNamespaceCustomCR}, createdSubmarine)
                        Expect(err).To(BeNil())
 
                        foreground := metav1.DeletePropagationForeground
@@ -149,7 +276,7 @@ var _ = Describe("Submarine controller", func() {
 
                        // Wait for Submarine to be deleted entirely
                        Eventually(func() bool {
-                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineName, Namespace: submarineNamespace}, 
createdSubmarine)
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNameCustomCR, Namespace: 
submarineNamespaceCustomCR}, createdSubmarine)
                                if apierrors.IsNotFound(err) {
                                        return true
                                }
@@ -160,18 +287,35 @@ var _ = Describe("Submarine controller", func() {
        })
 
        Context("Delete the test namespace", func() {
-               It("Should delete the test namespace", func() {
-                       msg := fmt.Sprintf("Deleting the test namespace %s", 
submarineNamespace)
-                       By(msg)
+               It(fmt.Sprintf("Should delete namespace %s", 
submarineNamespaceDefaultCR), func() {
+                       By(fmt.Sprintf("Deleting the test namespace %s", 
submarineNamespaceDefaultCR))
+
+                       createdNs := &corev1.Namespace{} // stub
+                       Expect(k8sClient.Get(ctx, types.NamespacedName{Name: 
submarineNamespaceDefaultCR, Namespace: "default"}, 
createdNs)).Should(Succeed())
+                       Expect(k8sClient.Delete(ctx, 
createdNs)).Should(Succeed())
+
+                       // Wait for submarine to be deleted entirely
+
+                       Eventually(func() bool {
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNamespaceDefaultCR, Namespace: "default"}, 
createdNs)
+                               if apierrors.IsNotFound(err) {
+                                       return true
+                               }
+                               Expect(err).To(BeNil())
+                               return false
+                       }, deleteNsTimeout, deleteNsInterval).Should(BeTrue())
+               })
+               It(fmt.Sprintf("Should delete namespace %s", 
submarineNamespaceCustomCR), func() {
+                       By(fmt.Sprintf("Deleting the test namespace %s", 
submarineNamespaceCustomCR))
 
                        createdNs := &corev1.Namespace{} // stub
-                       Expect(k8sClient.Get(ctx, types.NamespacedName{Name: 
submarineNamespace, Namespace: "default"}, createdNs)).Should(Succeed())
+                       Expect(k8sClient.Get(ctx, types.NamespacedName{Name: 
submarineNamespaceCustomCR, Namespace: "default"}, createdNs)).Should(Succeed())
                        Expect(k8sClient.Delete(ctx, 
createdNs)).Should(Succeed())
 
                        // Wait for submarine to be deleted entirely
 
                        Eventually(func() bool {
-                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNamespace, Namespace: "default"}, createdNs)
+                               err := k8sClient.Get(ctx, 
types.NamespacedName{Name: submarineNamespaceCustomCR, Namespace: "default"}, 
createdNs)
                                if apierrors.IsNotFound(err) {
                                        return true
                                }
diff --git a/submarine-cloud-v3/controllers/submarine_virtualservice.go 
b/submarine-cloud-v3/controllers/submarine_virtualservice.go
index 2158817e..b15a0d44 100644
--- a/submarine-cloud-v3/controllers/submarine_virtualservice.go
+++ b/submarine-cloud-v3/controllers/submarine_virtualservice.go
@@ -39,6 +39,22 @@ func (r *SubmarineReconciler) newSubmarineVirtualService(ctx 
context.Context, su
                r.Log.Error(err, "ParseVirtualService")
        }
        virtualService.Namespace = submarine.Namespace
+
+       virtualserviceHosts := submarine.Spec.Virtualservice.Hosts
+       if virtualserviceHosts != nil {
+               // Use `Hosts` defined in submarine spec
+               virtualService.Spec.Hosts = virtualserviceHosts
+       } else {
+               // Use default `<namespace>.submarine`
+               virtualService.Spec.Hosts = append(virtualService.Spec.Hosts, 
submarine.Namespace+".submarine")
+       }
+
+       virtualserviceGateways := submarine.Spec.Virtualservice.Gateways
+       if virtualserviceGateways != nil {
+               // Use `Gateways` defined in submarine spec
+               virtualService.Spec.Gateways = virtualserviceGateways
+       }
+
        err = controllerutil.SetControllerReference(submarine, virtualService, 
r.Scheme)
        if err != nil {
                r.Log.Error(err, "Set VirtualService ControllerReference")
diff --git a/submarine-cloud-v3/docs/developer-guide.md 
b/submarine-cloud-v3/docs/developer-guide.md
index 75badc02..9789824e 100644
--- a/submarine-cloud-v3/docs/developer-guide.md
+++ b/submarine-cloud-v3/docs/developer-guide.md
@@ -47,7 +47,7 @@ Before running submarine operator, install submarine 
dependencies with helm. `--
 helm install --set dev=true submarine ../helm-charts/submarine/ -n 
submarine-cloud-v3-system
 ```
 
-Now we run the submarine operator.
+Run the submarine operator.
 
 ```bash
 # Step1: Apply the submarine CRD.
@@ -62,17 +62,63 @@ make run
 kubectl apply -n submarine-user-test -f config/samples/_v1alpha1_submarine.yaml
 ```
 
-If you follow the above steps, you can view the submarine workbench via the 
same approach specified in the 
[QuickStart](https://submarine.apache.org/docs/next/gettingStarted/quickstart) 
section on the submarine website.
+Ensure that submarine is ready.
 
+```bash
+$ kubectl get pods -n submarine-cloud-v3-system
+NAME                                                     READY   STATUS    
RESTARTS   AGE
+notebook-controller-deployment-5b489cf59d-52lff          1/1     Running   2   
       22h
+submarine-cloud-v3-controller-manager-7b5787d8bc-hvpbk   2/2     Running   5   
       22h
+training-operator-6dcd5b9c64-v7k7k                       1/1     Running   1   
       22h
+
+$ kubectl get pods -n submarine-user-test
+NAME                                     READY   STATUS    RESTARTS   AGE
+submarine-database-0                     2/2     Running   0          24m
+submarine-minio-6d757cc97c-qwft5         2/2     Running   0          24m
+submarine-mlflow-657f5f8f6-9zxt6         2/2     Running   0          24m
+submarine-server-6c787c69b5-pv2dr        2/2     Running   0          24m
+submarine-tensorboard-7b447d94dd-d6kkm   2/2     Running   0          24m
+
+$ kubectl get virtualservices -n submarine-user-test
+NAME                        GATEWAYS                                          
HOSTS                               AGE
+submarine-virtual-service   ["submarine-cloud-v3-system/submarine-gateway"]   
["submarine-user-test.submarine"]   24m
+```
+
+The Istio virtual service now accepts traffic sent to destination `<submarine 
namespace>.submarine`. In our case it's `submarine-user-test.submarine`. Expose 
the service by forwarding traffic from local port 80 to the Istio ingress 
gateway.
+
+```bash
+# You may need to grant kubectl the capacity to bind to previleged ports 
without root.
+# sudo setcap CAP_NET_BIND_SERVICE=+ep <full path to kubectl binary>
+
+# Step4: Expose the service using kubectl port-forward
+kubectl port-forward --address 127.0.0.1 -n istio-system 
service/istio-ingressgateway 80:80
+
+# Alternatively, use minikube tunnel, which asks for sudo and does not require 
setcap.
+# It may provide an external IP address other than 127.0.0.1.
+# minikube tunnel
+# kubectl get service/istio-ingressgateway -n istio-system
+```
+
+For local development, resolve `submarine-user-test.submarine` to IP address 
`127.0.0.1` by adding the following line to the file `~/etc/hosts`.
 
 ```bash
-# Step4: Cleanup submarine.
+127.0.0.1 submarine-user-test.submarine # For submarine local development
+```
+
+Now we can connect to the submarine workbench at 
`http://submarine-user-test.submarine`.
+
+
+```bash
+# Step5: Stop exposing the service
+# Just close the running process at Step4.
+
+# Step6: Cleanup submarine.
 kubectl delete -n submarine-user-test submarine example-submarine
 
-# Step5: Cleanup operator.
+# Step7: Cleanup operator.
 # Just close the running process at Step2.
 
-# Step6: Delete the submarine CRD.
+# Step8: Delete the submarine CRD.
 make uninstall
 ```
 
@@ -107,7 +153,7 @@ kubectl get deployment -n submarine-cloud-v3-system
 # Step5: Deploy a submarine.
 kubectl apply -n submarine-user-test -f config/samples/_v1alpha1_submarine.yaml
 
-# You can now view the submarine workbench
+# We can now expose the service like before and connect to the submarine 
workbench
 
 # Step6: Cleanup submarine.
 kubectl delete -n submarine-user-test submarine example-submarine
@@ -122,7 +168,11 @@ make undeploy
 
 ### Installing Helm in a different namespace
 
-By default, the Istio virtual service created by the operator binds to the 
Istio gateway `submarine-cloud-v3-system/submarine-gateway`, which should be 
installed in the `submarine-cloud-v3-system` namespace via Helm. If `helm 
install` is run with `-n <your_namespace>`, edit the spec `spec.gateways` of 
the virtual service to be `<your_namespace>/submarine-gateway` manually once 
it's created by the operator.
+By default, the Istio virtual service created by the operator binds to the 
Istio gateway `submarine-cloud-v3-system/submarine-gateway`, which is installed 
in the `submarine-cloud-v3-system` namespace via Helm. If `helm install` is run 
with `-n <your_namespace>`, set `spec.virtualserice.gateways` of your submarine 
CRs to `<your_namespace>/submarine-gateway`. Note that the data type is an 
array of strings.
+
+### Use custom hosts
+
+You can set the desitination hosts of the Istio virtual service by setting 
`spec.virtualservice.hosts` of the submarine CR. Note that the data type is an 
array of strings. If that field is omitted, hosts defaults to 
`["<namespace>.submarine"]` e.g. `["submarine-user-test.submarine"]`.
 
 ### Rebuild Operator Image
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to