github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/update/nginxistio/nginxistio_test.go (about)

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package nginxistio
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    11  	"github.com/verrazzano/verrazzano/pkg/nginxutil"
    12  	"reflect"
    13  	"strings"
    14  	"text/template"
    15  	"time"
    16  
    17  	"github.com/verrazzano/verrazzano/pkg/constants"
    18  	"github.com/verrazzano/verrazzano/pkg/k8s/resource"
    19  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    20  	vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    21  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    22  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework"
    23  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/update"
    24  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	"github.com/onsi/gomega"
    27  	corev1 "k8s.io/api/core/v1"
    28  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    29  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/util/intstr"
    31  	"sigs.k8s.io/yaml"
    32  )
    33  
    34  const (
    35  	nginxLabelKey            = "app.kubernetes.io/component"
    36  	nginxLabelValue          = "controller"
    37  	istioAppLabelKey         = "app"
    38  	istioIngressLabelValue   = "istio-ingressgateway"
    39  	istioEgressLabelValue    = "istio-egressgateway"
    40  	nginxIngressServiceName  = "ingress-controller-ingress-nginx-controller"
    41  	istioIngressServiceName  = "istio-ingressgateway"
    42  	waitTimeout              = 10 * time.Minute
    43  	pollingInterval          = 10 * time.Second
    44  	ociLBShapeAnnotation     = "service.beta.kubernetes.io/oci-load-balancer-shape"
    45  	nginxTestAnnotationName  = "name-n"
    46  	nginxTestAnnotationValue = "value-n"
    47  	istioTestAnnotationName  = "name-i"
    48  	istioTestAnnotationValue = "value-i"
    49  	newReplicas              = 3
    50  	nginxLBShapeValue        = "flexible"
    51  	istioLBShapeValue        = "flexible"
    52  )
    53  
    54  var ingressNGINXNamespace string
    55  
    56  var testNginxIngressPorts = []corev1.ServicePort{
    57  	{
    58  		Name:     "https",
    59  		Protocol: "TCP",
    60  		Port:     443,
    61  		NodePort: 31443,
    62  		TargetPort: intstr.IntOrString{
    63  			Type:   intstr.String,
    64  			StrVal: "https",
    65  		},
    66  	},
    67  }
    68  
    69  var testIstioIngressPorts = []corev1.ServicePort{
    70  	{
    71  		Name:       "https",
    72  		Protocol:   "TCP",
    73  		Port:       443,
    74  		NodePort:   32443,
    75  		TargetPort: intstr.FromInt(8443),
    76  	},
    77  }
    78  
    79  type externalLBsTemplateData struct {
    80  	ServerList string
    81  }
    82  
    83  type NginxAutoscalingIstioRelicasAffintyModifier struct {
    84  	nginxReplicas        uint32
    85  	istioIngressReplicas uint32
    86  	istioEgressReplicas  uint32
    87  	NginxIstioIngressServiceAnnotationModifier
    88  }
    89  
    90  type NginxIstioNodePortModifier struct {
    91  	systemExternalLBIP      string
    92  	applicationExternalLBIP string
    93  	NginxAutoscalingIstioRelicasAffintyModifier
    94  }
    95  
    96  type NginxIstioLoadBalancerModifier struct {
    97  	NginxIstioNodePortModifier
    98  }
    99  
   100  type NginxIstioIngressServiceAnnotationModifier struct {
   101  	nginxLBShape string
   102  	istioLBShape string
   103  }
   104  
   105  func (m NginxAutoscalingIstioRelicasAffintyModifier) ModifyCR(cr *vzapi.Verrazzano) {
   106  	if cr.Spec.Components.Ingress == nil {
   107  		cr.Spec.Components.Ingress = &vzapi.IngressNginxComponent{}
   108  	}
   109  	if cr.Spec.Components.Istio == nil {
   110  		cr.Spec.Components.Istio = &vzapi.IstioComponent{}
   111  	}
   112  	// update nginx
   113  	nginxYaml := fmt.Sprintf(`controller:
   114    autoscaling:
   115      enabled: true
   116      minReplicas: %v
   117    service:
   118      annotations:
   119        service.beta.kubernetes.io/oci-load-balancer-shape: %s
   120        name-n: value-n`, m.nginxReplicas, m.nginxLBShape)
   121  	cr.Spec.Components.Ingress.ValueOverrides = createOverridesOrDie(nginxYaml)
   122  
   123  	// update Istio
   124  	istioYaml := fmt.Sprintf(`apiVersion: install.istio.io/v1alpha1
   125  kind: IstioOperator
   126  spec:
   127    components:
   128      egressGateways:
   129        - enabled: true
   130          k8s:
   131            replicaCount: %v
   132          name: istio-egressgateway
   133      ingressGateways:
   134        - enabled: true
   135          k8s:
   136            replicaCount: %v
   137            service:
   138              type: LoadBalancer
   139          name: istio-ingressgateway
   140    values:
   141      gateways:
   142        istio-ingressgateway:
   143          serviceAnnotations:
   144            name-i: value-i
   145            service.beta.kubernetes.io/oci-load-balancer-shape: %s`, m.istioEgressReplicas, m.istioIngressReplicas, m.istioLBShape)
   146  	cr.Spec.Components.Istio.ValueOverrides = createOverridesOrDie(istioYaml)
   147  	// istio 1.11.4 has a bug handling this particular Affinity
   148  	// it works fine if istio is installed with it
   149  	// but it fails updating istio with it even though running pods has met replicaCount, istio is trying to schedule more
   150  	// which results in pending pods
   151  	//
   152  	//if cr.Spec.Components.Istio.Ingress.Kubernetes.Affinity == nil {
   153  	//	cr.Spec.Components.Istio.Ingress.Kubernetes.Affinity = &corev1.Affinity{}
   154  	//}
   155  	//if cr.Spec.Components.Istio.Ingress.Kubernetes.Affinity.PodAntiAffinity == nil {
   156  	//	cr.Spec.Components.Istio.Ingress.Kubernetes.Affinity.PodAntiAffinity = &corev1.PodAntiAffinity{}
   157  	//}
   158  	//requiredIngressAntiAffinity := cr.Spec.Components.Istio.Ingress.Kubernetes.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution
   159  	//requiredIngressAntiAffinity = append(requiredIngressAntiAffinity, corev1.PodAffinityTerm{
   160  	//	LabelSelector: &metav1.LabelSelector{
   161  	//		MatchLabels: nil,
   162  	//		MatchExpressions: []metav1.LabelSelectorRequirement{
   163  	//			{
   164  	//				Key:      istioAppLabelKey,
   165  	//				Operator: "In",
   166  	//				Values: []string{
   167  	//					istioIngressLabelValue,
   168  	//				},
   169  	//			},
   170  	//		},
   171  	//	},
   172  	//	TopologyKey: "kubernetes.io/hostname",
   173  	//})
   174  	//cr.Spec.Components.Istio.Ingress.Kubernetes.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = requiredIngressAntiAffinity
   175  	// istio 1.11.4 has a bug handling this particular Affinity
   176  	// it works fine if istio is installed with it
   177  	// but it fails updating istio with it even though running pods has met replicaCount, istio is trying to schedule more
   178  	// which results in pending pods
   179  	//if cr.Spec.Components.Istio.Egress.Kubernetes.Affinity == nil {
   180  	//	cr.Spec.Components.Istio.Egress.Kubernetes.Affinity = &corev1.Affinity{}
   181  	//}
   182  	//if cr.Spec.Components.Istio.Egress.Kubernetes.Affinity.PodAntiAffinity == nil {
   183  	//	cr.Spec.Components.Istio.Egress.Kubernetes.Affinity.PodAntiAffinity = &corev1.PodAntiAffinity{}
   184  	//}
   185  	//requiredEgressAntiAffinity := cr.Spec.Components.Istio.Egress.Kubernetes.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution
   186  	//requiredEgressAntiAffinity = append(requiredEgressAntiAffinity, corev1.PodAffinityTerm{
   187  	//	LabelSelector: &metav1.LabelSelector{
   188  	//		MatchLabels: nil,
   189  	//		MatchExpressions: []metav1.LabelSelectorRequirement{
   190  	//			{
   191  	//				Key:      istioAppLabelKey,
   192  	//				Operator: "In",
   193  	//				Values: []string{
   194  	//					istioEgressLabelValue,
   195  	//				},
   196  	//			},
   197  	//		},
   198  	//	},
   199  	//	TopologyKey: "kubernetes.io/hostname",
   200  	//})
   201  	//cr.Spec.Components.Istio.Egress.Kubernetes.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = requiredEgressAntiAffinity
   202  }
   203  
   204  func (u NginxIstioNodePortModifier) ModifyCR(cr *vzapi.Verrazzano) {
   205  	if cr.Spec.Components.Ingress == nil {
   206  		cr.Spec.Components.Ingress = &vzapi.IngressNginxComponent{}
   207  	}
   208  	if cr.Spec.Components.Istio == nil {
   209  		cr.Spec.Components.Istio = &vzapi.IstioComponent{}
   210  	}
   211  	cr.Spec.Components.Ingress.Ports = testNginxIngressPorts
   212  	cr.Spec.Components.Ingress.Type = vzapi.NodePort
   213  	cr.Spec.Components.Ingress.NGINXInstallArgs = append(cr.Spec.Components.Ingress.NGINXInstallArgs, vzapi.InstallArgs{
   214  		Name:      "controller.service.externalIPs",
   215  		ValueList: []string{u.systemExternalLBIP},
   216  	})
   217  	// update nginx
   218  	nginxYaml := fmt.Sprintf(`controller:
   219    autoscaling:
   220      enabled: true
   221      minReplicas: %v
   222      annotations:
   223        service.beta.kubernetes.io/oci-load-balancer-shape: %s
   224        name-n: value-n`, u.nginxReplicas, u.nginxLBShape)
   225  	cr.Spec.Components.Ingress.ValueOverrides = createOverridesOrDie(nginxYaml)
   226  
   227  	istio := cr.Spec.Components.Istio
   228  	istio.IstioInstallArgs = []vzapi.InstallArgs{
   229  		{
   230  			Name:      "gateways.istio-ingressgateway.externalIPs",
   231  			ValueList: []string{u.applicationExternalLBIP},
   232  		},
   233  	}
   234  	istio.Ingress = &vzapi.IstioIngressSection{
   235  		Type:  vzapi.NodePort,
   236  		Ports: testIstioIngressPorts,
   237  		Kubernetes: &vzapi.IstioKubernetesSection{
   238  			CommonKubernetesSpec: vzapi.CommonKubernetesSpec{
   239  				Replicas: newReplicas,
   240  			},
   241  		},
   242  	}
   243  	istio.Egress = &vzapi.IstioEgressSection{
   244  		Kubernetes: &vzapi.IstioKubernetesSection{
   245  			CommonKubernetesSpec: vzapi.CommonKubernetesSpec{
   246  				Replicas: newReplicas,
   247  			},
   248  		},
   249  	}
   250  	// update Istio
   251  	istioYaml := fmt.Sprintf(`apiVersion: install.istio.io/v1alpha1
   252  kind: IstioOperator
   253  spec:
   254    values:
   255      gateways:
   256        istio-ingressgateway:
   257          serviceAnnotations:
   258            name-i: value-i
   259            service.beta.kubernetes.io/oci-load-balancer-shape: %s`, u.istioLBShape)
   260  	cr.Spec.Components.Istio.ValueOverrides = createOverridesOrDie(istioYaml)
   261  }
   262  
   263  func (u NginxIstioLoadBalancerModifier) ModifyCR(cr *vzapi.Verrazzano) {
   264  	if cr.Spec.Components.Ingress == nil {
   265  		cr.Spec.Components.Ingress = &vzapi.IngressNginxComponent{}
   266  	}
   267  	cr.Spec.Components.Ingress.Type = vzapi.LoadBalancer
   268  	if cr.Spec.Components.Istio == nil {
   269  		cr.Spec.Components.Istio = &vzapi.IstioComponent{}
   270  	}
   271  
   272  	// update nginx
   273  	nginxYaml := fmt.Sprintf(`controller:
   274    autoscaling:
   275      enabled: true
   276      minReplicas: %v
   277    service:
   278      annotations:
   279        service.beta.kubernetes.io/oci-load-balancer-shape: %s
   280        name-n: value-n`, u.nginxReplicas, u.nginxLBShape)
   281  	cr.Spec.Components.Ingress.ValueOverrides = createOverridesOrDie(nginxYaml)
   282  
   283  	// update Istio
   284  	istioYaml := fmt.Sprintf(`apiVersion: install.istio.io/v1alpha1
   285  kind: IstioOperator
   286  spec:
   287    components:
   288      egressGateways:
   289        - enabled: true
   290          k8s:
   291            replicaCount: %v
   292          name: istio-egressgateway
   293      ingressGateways:
   294        - enabled: true
   295          k8s:
   296            replicaCount: %v
   297            service:
   298              type: LoadBalancer
   299          name: istio-ingressgateway
   300    values:
   301      gateways:
   302        istio-ingressgateway:
   303          serviceAnnotations:
   304            name-i: value-i
   305            service.beta.kubernetes.io/oci-load-balancer-shape: %s`, u.istioEgressReplicas, u.istioIngressReplicas, u.istioLBShape)
   306  	cr.Spec.Components.Istio.ValueOverrides = createOverridesOrDie(istioYaml)
   307  }
   308  
   309  func (u NginxIstioIngressServiceAnnotationModifier) ModifyCR(cr *vzapi.Verrazzano) {
   310  	if cr.Spec.Components.Ingress == nil {
   311  		cr.Spec.Components.Ingress = &vzapi.IngressNginxComponent{}
   312  	}
   313  	ingress := cr.Spec.Components.Ingress
   314  	ingress.Type = vzapi.LoadBalancer
   315  	nginxYaml := fmt.Sprintf(`controller:
   316    service:
   317      annotations:
   318        service.beta.kubernetes.io/oci-load-balancer-shape: %s
   319        name-n: value-n`, u.nginxLBShape)
   320  	ingress.ValueOverrides = createOverridesOrDie(nginxYaml)
   321  	if cr.Spec.Components.Istio == nil {
   322  		cr.Spec.Components.Istio = &vzapi.IstioComponent{}
   323  	}
   324  	istio := cr.Spec.Components.Istio
   325  	istioYaml := fmt.Sprintf(`apiVersion: install.istio.io/v1alpha1
   326  kind: IstioOperator
   327  spec:
   328    components:
   329      egressGateways:
   330        - enabled: true
   331          name: istio-egressgateway
   332      ingressGateways:
   333        - enabled: true
   334          k8s:
   335            service:
   336              type: LoadBalancer
   337          name: istio-ingressgateway
   338    values:
   339      gateways:
   340        istio-ingressgateway:
   341          serviceAnnotations:
   342            name-i: value-i
   343            service.beta.kubernetes.io/oci-load-balancer-shape: %s`, u.istioLBShape)
   344  	istio.ValueOverrides = createOverridesOrDie(istioYaml)
   345  }
   346  
   347  func createOverridesOrDie(yamlString string) []vzapi.Overrides {
   348  	data, err := yaml.YAMLToJSON([]byte(yamlString))
   349  	if err != nil {
   350  		t.Logs.Errorf("Failed to convert yaml to JSON: %s", yamlString)
   351  		panic(err)
   352  	}
   353  	return []vzapi.Overrides{
   354  		{
   355  			ConfigMapRef: nil,
   356  			SecretRef:    nil,
   357  			Values: &apiextensionsv1.JSON{
   358  				Raw: data,
   359  			},
   360  		},
   361  	}
   362  }
   363  
   364  var t = framework.NewTestFramework("update nginx-istio")
   365  
   366  var systemExternalIP, applicationExternalIP string
   367  
   368  var beforeSuite = t.BeforeSuiteFunc(func() {
   369  	var err error
   370  	systemExternalIP, applicationExternalIP, err = deployExternalLBs()
   371  	if err != nil {
   372  		Fail(err.Error())
   373  	}
   374  	ingressNGINXNamespace, err = nginxutil.DetermineNamespaceForIngressNGINX(vzlog.DefaultLogger())
   375  	if err != nil {
   376  		Fail(err.Error())
   377  	}
   378  })
   379  
   380  var _ = BeforeSuite(beforeSuite)
   381  
   382  var _ = t.Describe("Update nginx-istio", Serial, Ordered, Label("f:platform-lcm.update"), func() {
   383  	t.Describe("verrazzano-nginx-istio verify", Label("f:platform-lcm.nginx-istio-verify"), func() {
   384  		t.It("nginx-istio default replicas", func() {
   385  			cr := update.GetCR()
   386  
   387  			expectedIstioRunning := uint32(1)
   388  			expectedNGINXRunning := uint32(1)
   389  			if cr.Spec.Profile == "prod" || cr.Spec.Profile == "" {
   390  				expectedIstioRunning = 2
   391  				expectedNGINXRunning = 2
   392  			}
   393  			update.ValidatePods(nginxLabelValue, nginxLabelKey, ingressNGINXNamespace, expectedNGINXRunning, false)
   394  			update.ValidatePods(istioIngressLabelValue, istioAppLabelKey, constants.IstioSystemNamespace, expectedIstioRunning, false)
   395  			update.ValidatePods(istioEgressLabelValue, istioAppLabelKey, constants.IstioSystemNamespace, expectedIstioRunning, false)
   396  		})
   397  	})
   398  
   399  	t.Describe("verrazzano-nginx-istio update ingress service annotations", Label("f:platform-lcm.nginx-istio-update-annotations"), func() {
   400  		t.It("nginx-istio update ingress service annotations", func() {
   401  			m := NginxIstioIngressServiceAnnotationModifier{nginxLBShape: nginxLBShapeValue, istioLBShape: istioLBShapeValue}
   402  			update.UpdateCRWithRetries(m, pollingInterval, waitTimeout)
   403  
   404  			validateServiceAnnotations(m)
   405  		})
   406  	})
   407  
   408  	t.Describe("verrazzano-nginx-istio update replicas", Label("f:platform-lcm.nginx-istio-update-replicas"), func() {
   409  		t.It("nginx-istio update replicas", func() {
   410  			m := NginxAutoscalingIstioRelicasAffintyModifier{
   411  				nginxReplicas:        newReplicas,
   412  				istioIngressReplicas: newReplicas,
   413  				istioEgressReplicas:  newReplicas,
   414  				NginxIstioIngressServiceAnnotationModifier: NginxIstioIngressServiceAnnotationModifier{
   415  					nginxLBShape: nginxLBShapeValue,
   416  					istioLBShape: istioLBShapeValue,
   417  				},
   418  			}
   419  			update.UpdateCRWithRetries(m, pollingInterval, waitTimeout)
   420  
   421  			update.ValidatePods(nginxLabelValue, nginxLabelKey, ingressNGINXNamespace, newReplicas, false)
   422  			update.ValidatePods(istioIngressLabelValue, istioAppLabelKey, constants.IstioSystemNamespace, newReplicas, false)
   423  			update.ValidatePods(istioEgressLabelValue, istioAppLabelKey, constants.IstioSystemNamespace, newReplicas, false)
   424  		})
   425  	})
   426  
   427  	t.Describe("verrazzano-nginx-istio update nodeport", Label("f:platform-lcm.nginx-istio-update-nodeport"), func() {
   428  		t.It("nginx-istio update ingress type to nodeport", func() {
   429  			t.Logs.Infof("Update nginx/istio ingresses to use NodePort type with external load balancers: %s and %s", systemExternalIP, applicationExternalIP)
   430  			m := NginxIstioNodePortModifier{
   431  				systemExternalLBIP:      systemExternalIP,
   432  				applicationExternalLBIP: applicationExternalIP,
   433  				NginxAutoscalingIstioRelicasAffintyModifier: NginxAutoscalingIstioRelicasAffintyModifier{
   434  					nginxReplicas:        newReplicas,
   435  					istioIngressReplicas: newReplicas,
   436  					istioEgressReplicas:  newReplicas,
   437  					NginxIstioIngressServiceAnnotationModifier: NginxIstioIngressServiceAnnotationModifier{
   438  						nginxLBShape: nginxLBShapeValue,
   439  						istioLBShape: istioLBShapeValue,
   440  					},
   441  				},
   442  			}
   443  			update.UpdateCRWithRetries(m, pollingInterval, waitTimeout)
   444  
   445  			t.Logs.Info("Validate nginx/istio ingresses for NodePort type and externalIPs")
   446  			validateServiceNodePortAndExternalIP(systemExternalIP, applicationExternalIP)
   447  		})
   448  	})
   449  
   450  	t.Describe("verrazzano-nginx-istio update loadbalancer", Label("f:platform-lcm.nginx-istio-update-loadbalancer"), func() {
   451  		t.It("nginx-istio update ingress type to loadbalancer", func() {
   452  			t.Logs.Infof("Update nginx/istio ingresses to use LoadBalancer type")
   453  			m := NginxIstioLoadBalancerModifier{
   454  				NginxIstioNodePortModifier{
   455  					systemExternalLBIP:      systemExternalIP,
   456  					applicationExternalLBIP: applicationExternalIP,
   457  					NginxAutoscalingIstioRelicasAffintyModifier: NginxAutoscalingIstioRelicasAffintyModifier{
   458  						nginxReplicas:        newReplicas,
   459  						istioIngressReplicas: newReplicas,
   460  						istioEgressReplicas:  newReplicas,
   461  						NginxIstioIngressServiceAnnotationModifier: NginxIstioIngressServiceAnnotationModifier{
   462  							nginxLBShape: nginxLBShapeValue,
   463  							istioLBShape: istioLBShapeValue,
   464  						},
   465  					},
   466  				},
   467  			}
   468  			update.UpdateCRWithRetries(m, pollingInterval, waitTimeout)
   469  
   470  			t.Logs.Info("Validate nginx/istio ingresses for LoadBalancer type and loadBalancer IP")
   471  			validateServiceLoadBalancer()
   472  		})
   473  	})
   474  
   475  	t.Describe("verrazzano-nginx-istio update nodeport 2", Label("f:platform-lcm.nginx-istio-update-nodeport-2"), func() {
   476  		t.It("nginx-istio update ingress type to nodeport 2", func() {
   477  			t.Logs.Infof("Update nginx/istio ingresses to use NodePort type with external load balancers: %s and %s", systemExternalIP, applicationExternalIP)
   478  			m := NginxIstioNodePortModifier{
   479  				systemExternalLBIP:      systemExternalIP,
   480  				applicationExternalLBIP: applicationExternalIP,
   481  				NginxAutoscalingIstioRelicasAffintyModifier: NginxAutoscalingIstioRelicasAffintyModifier{
   482  					nginxReplicas:        newReplicas,
   483  					istioIngressReplicas: newReplicas,
   484  					istioEgressReplicas:  newReplicas,
   485  					NginxIstioIngressServiceAnnotationModifier: NginxIstioIngressServiceAnnotationModifier{
   486  						nginxLBShape: nginxLBShapeValue,
   487  						istioLBShape: istioLBShapeValue,
   488  					},
   489  				},
   490  			}
   491  			update.UpdateCRWithRetries(m, pollingInterval, waitTimeout)
   492  
   493  			t.Logs.Info("Validate nginx/istio ingresses for NodePort type and externalIPs")
   494  			validateServiceNodePortAndExternalIP(systemExternalIP, applicationExternalIP)
   495  		})
   496  	})
   497  })
   498  
   499  func deployExternalLBs() (string, string, error) {
   500  	_, err := pkg.CreateNamespaceWithAnnotations("external-lb", map[string]string{}, map[string]string{})
   501  	if err != nil {
   502  		return "", "", err
   503  	}
   504  
   505  	systemServerList, applicationServerList, err := buildServerLists()
   506  	if err != nil {
   507  		return "", "", err
   508  	}
   509  
   510  	applyResource("testdata/external-lb/system-external-lb-cm.yaml", &externalLBsTemplateData{ServerList: systemServerList})
   511  	applyResource("testdata/external-lb/system-external-lb.yaml", &externalLBsTemplateData{})
   512  	applyResource("testdata/external-lb/system-external-lb-svc.yaml", &externalLBsTemplateData{})
   513  	applyResource("testdata/external-lb/application-external-lb-cm.yaml", &externalLBsTemplateData{ServerList: applicationServerList})
   514  	applyResource("testdata/external-lb/application-external-lb.yaml", &externalLBsTemplateData{})
   515  	applyResource("testdata/external-lb/application-external-lb-svc.yaml", &externalLBsTemplateData{})
   516  
   517  	sysIP, err := getServiceLoadBalancerIP("external-lb", "system-external-lb-svc")
   518  	if err != nil {
   519  		return "", "", err
   520  	}
   521  
   522  	appIP, err := getServiceLoadBalancerIP("external-lb", "application-external-lb-svc")
   523  	if err != nil {
   524  		return "", "", err
   525  	}
   526  
   527  	return sysIP, appIP, nil
   528  }
   529  
   530  func buildServerLists() (string, string, error) {
   531  	nodes, err := pkg.ListNodes()
   532  	if err != nil {
   533  		return "", "", err
   534  	}
   535  	if len(nodes.Items) < 1 {
   536  		return "", "", fmt.Errorf("can not find node in the cluster")
   537  	}
   538  	var serverListNginx, serverListIstio string
   539  	for _, node := range nodes.Items {
   540  		if len(node.Status.Addresses) < 1 {
   541  			return "", "", fmt.Errorf("can not find address in the node")
   542  		}
   543  		serverListNginx = serverListNginx + fmt.Sprintf("           server %s:31443;\n", node.Status.Addresses[0].Address)
   544  		serverListIstio = serverListIstio + fmt.Sprintf("           server %s:32443;\n", node.Status.Addresses[0].Address)
   545  	}
   546  	return serverListNginx, serverListIstio, nil
   547  }
   548  
   549  func applyResource(resourceFile string, templateData *externalLBsTemplateData) {
   550  	file, err := pkg.FindTestDataFile(resourceFile)
   551  	if err != nil {
   552  		Fail(err.Error())
   553  	}
   554  	fileTemplate, err := template.ParseFiles(file)
   555  	if err != nil {
   556  		Fail(err.Error())
   557  	}
   558  	var buff bytes.Buffer
   559  	err = fileTemplate.Execute(&buff, templateData)
   560  	if err != nil {
   561  		Fail(err.Error())
   562  	}
   563  
   564  	err = resource.CreateOrUpdateResourceFromBytes(buff.Bytes(), t.Logs)
   565  	if err != nil {
   566  		Fail(err.Error())
   567  	}
   568  }
   569  
   570  func getServiceLoadBalancerIP(ns, svcName string) (string, error) {
   571  	gomega.Eventually(func() error {
   572  		svc, err := pkg.GetService(ns, svcName)
   573  		if err != nil {
   574  			return err
   575  		}
   576  		if len(svc.Status.LoadBalancer.Ingress) == 0 {
   577  			return fmt.Errorf("loadBalancer for service %s/%s is not ready yet", ns, svcName)
   578  		}
   579  		return nil
   580  	}, waitTimeout, pollingInterval).Should(gomega.BeNil(), fmt.Sprintf("Expected to get a loadBalancer for service %s/%s", ns, svcName))
   581  
   582  	// Get the CR
   583  	svc, err := pkg.GetService(ns, svcName)
   584  	if err != nil {
   585  		return "", fmt.Errorf("can not get IP for service %s/%s due to error: %v", ns, svcName, err.Error())
   586  	}
   587  	if len(svc.Status.LoadBalancer.Ingress) > 0 {
   588  		return svc.Status.LoadBalancer.Ingress[0].IP, nil
   589  	}
   590  
   591  	return "", fmt.Errorf("no IP is found for service %s/%s", ns, svcName)
   592  }
   593  
   594  func validateServiceAnnotations(m NginxIstioIngressServiceAnnotationModifier) {
   595  	gomega.Eventually(func() error {
   596  		var err error
   597  		nginxIngress, err := pkg.GetService(ingressNGINXNamespace, nginxIngressServiceName)
   598  		if err != nil {
   599  			return err
   600  		}
   601  		if nginxIngress.Annotations[nginxTestAnnotationName] != nginxTestAnnotationValue {
   602  			return fmt.Errorf("expect nginx ingress annotation %v with %v, but got %v", nginxTestAnnotationName, nginxTestAnnotationValue, nginxIngress.Annotations[nginxTestAnnotationName])
   603  		}
   604  		if nginxIngress.Annotations[ociLBShapeAnnotation] != m.nginxLBShape {
   605  			return fmt.Errorf("expect nginx ingress annotation %v with value %v, but got %v", ociLBShapeAnnotation, m.nginxLBShape, nginxIngress.Annotations[ociLBShapeAnnotation])
   606  		}
   607  		istioIngress, err := pkg.GetService(constants.IstioSystemNamespace, istioIngressServiceName)
   608  		if err != nil {
   609  			return err
   610  		}
   611  		if istioIngress.Annotations[istioTestAnnotationName] != istioTestAnnotationValue {
   612  			return fmt.Errorf("expect istio ingress annotation %v with %v, but got %v", istioTestAnnotationName, istioTestAnnotationValue, istioIngress.Annotations[istioTestAnnotationName])
   613  		}
   614  		if istioIngress.Annotations[ociLBShapeAnnotation] != m.istioLBShape {
   615  			return fmt.Errorf("expect istio ingress annotation %v with value %v, but got %v", ociLBShapeAnnotation, m.istioLBShape, istioIngress.Annotations[ociLBShapeAnnotation])
   616  		}
   617  		return nil
   618  	}, waitTimeout, pollingInterval).Should(gomega.BeNil(), "expect to get correct ports setting from nginx and istio services")
   619  }
   620  
   621  func validateServiceNodePortAndExternalIP(expectedSystemExternalIP, expectedApplicationExternalIP string) {
   622  	gomega.Eventually(func() error {
   623  		// validate Nginx Ingress service
   624  		var err error
   625  		nginxIngress, err := pkg.GetService(ingressNGINXNamespace, nginxIngressServiceName)
   626  		if err != nil {
   627  			return err
   628  		}
   629  		if nginxIngress.Spec.Type != corev1.ServiceTypeNodePort {
   630  			return fmt.Errorf("expect nginx ingress with type NodePort, but got %v", nginxIngress.Spec.Type)
   631  		}
   632  		if !reflect.DeepEqual(testNginxIngressPorts, nginxIngress.Spec.Ports) {
   633  			return fmt.Errorf("expect nginx ingress with ports %v, but got %v", testNginxIngressPorts, nginxIngress.Spec.Ports)
   634  		}
   635  		expectedSysIPs := []string{expectedSystemExternalIP}
   636  		if !reflect.DeepEqual(expectedSysIPs, nginxIngress.Spec.ExternalIPs) {
   637  			return fmt.Errorf("expect nginx ingress with externalIPs %v, but got %v", expectedSysIPs, nginxIngress.Spec.ExternalIPs)
   638  		}
   639  
   640  		// validate Istio Ingress Service
   641  		istioIngress, err := pkg.GetService(constants.IstioSystemNamespace, istioIngressServiceName)
   642  		if err != nil {
   643  			return err
   644  		}
   645  		if istioIngress.Spec.Type != corev1.ServiceTypeNodePort {
   646  			return fmt.Errorf("expect istio ingress with type NodePort, but got %v", istioIngress.Spec.Type)
   647  		}
   648  		if !reflect.DeepEqual(testIstioIngressPorts, istioIngress.Spec.Ports) {
   649  			return fmt.Errorf("expect istio ingress with ports %v, but got %v", testNginxIngressPorts, istioIngress.Spec.Ports)
   650  		}
   651  		expectedAppIPs := []string{expectedApplicationExternalIP}
   652  		if !reflect.DeepEqual(expectedAppIPs, istioIngress.Spec.ExternalIPs) {
   653  			return fmt.Errorf("expect istio ingress with externalIPs %v, but got %v", expectedAppIPs, istioIngress.Spec.ExternalIPs)
   654  		}
   655  
   656  		// validate Ingress Host
   657  		err = validateIngressHost(expectedSystemExternalIP, "keycloak", "keycloak")
   658  		if err != nil {
   659  			return err
   660  		}
   661  		err = validateIngressHost(expectedSystemExternalIP, "verrazzano-ingress", "verrazzano-system")
   662  		if err != nil {
   663  			return err
   664  		}
   665  
   666  		// validate application Host
   667  		err = validateApplicationHost(expectedApplicationExternalIP)
   668  		if err != nil {
   669  			return err
   670  		}
   671  
   672  		return nil
   673  	}, waitTimeout, pollingInterval).Should(gomega.BeNil(), "expect to get NodePort type and externalIPs from nginx and istio services")
   674  }
   675  
   676  func validateServiceLoadBalancer() {
   677  	gomega.Eventually(func() error {
   678  		// validate Nginx Ingress service
   679  		var err error
   680  		nginxIngress, err := pkg.GetService(ingressNGINXNamespace, nginxIngressServiceName)
   681  		if err != nil {
   682  			return err
   683  		}
   684  		if nginxIngress.Spec.Type != corev1.ServiceTypeLoadBalancer {
   685  			return fmt.Errorf("expect nginx ingress with type LoadBalancer, but got %v", nginxIngress.Spec.Type)
   686  		}
   687  		nginxLBIP, err := getServiceLoadBalancerIP(ingressNGINXNamespace, nginxIngressServiceName)
   688  		if err != nil {
   689  			return err
   690  		}
   691  		if len(nginxLBIP) == 0 {
   692  			return fmt.Errorf("invalid loadBalancer IP %s for nginx", nginxLBIP)
   693  		}
   694  
   695  		// validate Istio Ingress Service
   696  		istioIngress, err := pkg.GetService(constants.IstioSystemNamespace, istioIngressServiceName)
   697  		if err != nil {
   698  			return err
   699  		}
   700  		if istioIngress.Spec.Type != corev1.ServiceTypeLoadBalancer {
   701  			return fmt.Errorf("expect istio ingress with type LoadBalancer, but got %v", istioIngress.Spec.Type)
   702  		}
   703  		istioLBIP, err := getServiceLoadBalancerIP(constants.IstioSystemNamespace, istioIngressServiceName)
   704  		if err != nil {
   705  			return err
   706  		}
   707  		if len(istioLBIP) == 0 {
   708  			return fmt.Errorf("invalid loadBalancer IP %s for istio", istioLBIP)
   709  		}
   710  
   711  		// validate Ingress Host
   712  		err = validateIngressHost(nginxLBIP, "keycloak", "keycloak")
   713  		if err != nil {
   714  			return err
   715  		}
   716  		err = validateIngressHost(nginxLBIP, "verrazzano-ingress", "verrazzano-system")
   717  		if err != nil {
   718  			return err
   719  		}
   720  
   721  		// validate application Host
   722  		err = validateApplicationHost(istioLBIP)
   723  		if err != nil {
   724  			return err
   725  		}
   726  
   727  		return nil
   728  	}, waitTimeout, pollingInterval).Should(gomega.BeNil(), "expect to get LoadBalancer type and loadBalancer IP from nginx and istio services")
   729  }
   730  
   731  func validateIngressHost(expectedIP, ingressName, ns string) error {
   732  	kubeConfigPath, err := k8sutil.GetKubeConfigLocation()
   733  	if err != nil {
   734  		return err
   735  	}
   736  	clientset, err := pkg.GetKubernetesClientsetForCluster(kubeConfigPath)
   737  	if err != nil {
   738  		return err
   739  	}
   740  	ingress, err := clientset.NetworkingV1().Ingresses(ns).Get(context.TODO(), ingressName, v1.GetOptions{})
   741  	if err != nil {
   742  		return err
   743  	}
   744  	if len(ingress.Spec.Rules) == 0 {
   745  		return fmt.Errorf("expect Ingress %s/%s to have at least one host", ns, ingressName)
   746  	}
   747  	host := ingress.Spec.Rules[0].Host
   748  	if !strings.Contains(host, expectedIP) {
   749  		return fmt.Errorf("expect Ingress %s/%s Host %s to contain IP %s", ns, ingressName, host, expectedIP)
   750  	}
   751  	return nil
   752  }
   753  
   754  func validateApplicationHost(expectedIP string) error {
   755  	host, err := k8sutil.GetHostnameFromGateway("hello-helidon", "")
   756  	if err != nil {
   757  		return err
   758  	}
   759  	if !strings.Contains(host, expectedIP) {
   760  		return fmt.Errorf("expect hello-helidon HOST %s to contain IP %s", host, expectedIP)
   761  	}
   762  	return nil
   763  }