k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/framework/ingress/ingress_utils.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package ingress
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/rand"
    23  	"crypto/rsa"
    24  	"crypto/tls"
    25  	"crypto/x509"
    26  	"crypto/x509/pkix"
    27  	"encoding/pem"
    28  	"fmt"
    29  	"io"
    30  	"math/big"
    31  	"net"
    32  	"net/http"
    33  	"os"
    34  	"path/filepath"
    35  	"regexp"
    36  	"strconv"
    37  	"strings"
    38  	"time"
    39  
    40  	netutils "k8s.io/utils/net"
    41  
    42  	appsv1 "k8s.io/api/apps/v1"
    43  	v1 "k8s.io/api/core/v1"
    44  	networkingv1 "k8s.io/api/networking/v1"
    45  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    46  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    47  	"k8s.io/apimachinery/pkg/fields"
    48  	"k8s.io/apimachinery/pkg/labels"
    49  	"k8s.io/apimachinery/pkg/runtime"
    50  	"k8s.io/apimachinery/pkg/runtime/schema"
    51  	"k8s.io/apimachinery/pkg/util/intstr"
    52  	utilnet "k8s.io/apimachinery/pkg/util/net"
    53  	"k8s.io/apimachinery/pkg/util/sets"
    54  	"k8s.io/apimachinery/pkg/util/wait"
    55  	utilyaml "k8s.io/apimachinery/pkg/util/yaml"
    56  	clientset "k8s.io/client-go/kubernetes"
    57  	"k8s.io/client-go/kubernetes/scheme"
    58  	"k8s.io/kubernetes/test/e2e/framework"
    59  	e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
    60  	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
    61  	e2eservice "k8s.io/kubernetes/test/e2e/framework/service"
    62  	e2etestfiles "k8s.io/kubernetes/test/e2e/framework/testfiles"
    63  	testutils "k8s.io/kubernetes/test/utils"
    64  	imageutils "k8s.io/kubernetes/test/utils/image"
    65  
    66  	"github.com/onsi/ginkgo/v2"
    67  )
    68  
    69  const (
    70  	rsaBits  = 2048
    71  	validFor = 365 * 24 * time.Hour
    72  
    73  	// IngressClassKey is ingress class annotation defined in ingress repository.
    74  	// TODO: All these annotations should be reused from
    75  	// ingress-gce/pkg/annotations instead of duplicating them here.
    76  	IngressClassKey = "kubernetes.io/ingress.class"
    77  
    78  	// MulticlusterIngressClassValue is ingress class annotation value for multi cluster ingress.
    79  	MulticlusterIngressClassValue = "gce-multi-cluster"
    80  
    81  	// IngressStaticIPKey is static IP annotation defined in ingress repository.
    82  	IngressStaticIPKey = "kubernetes.io/ingress.global-static-ip-name"
    83  
    84  	// IngressAllowHTTPKey is Allow HTTP annotation defined in ingress repository.
    85  	IngressAllowHTTPKey = "kubernetes.io/ingress.allow-http"
    86  
    87  	// IngressPreSharedCertKey is Pre-shared-cert annotation defined in ingress repository.
    88  	IngressPreSharedCertKey = "ingress.gcp.kubernetes.io/pre-shared-cert"
    89  
    90  	// ServiceApplicationProtocolKey annotation defined in ingress repository.
    91  	ServiceApplicationProtocolKey = "service.alpha.kubernetes.io/app-protocols"
    92  
    93  	// Name of the default http backend service
    94  	defaultBackendName = "default-http-backend"
    95  
    96  	// IngressManifestPath is the parent path to yaml test manifests.
    97  	IngressManifestPath = "test/e2e/testing-manifests/ingress"
    98  
    99  	// GCEIngressManifestPath is the parent path to GCE-specific yaml test manifests.
   100  	GCEIngressManifestPath = IngressManifestPath + "/gce"
   101  
   102  	// IngressReqTimeout is the timeout on a single http request.
   103  	IngressReqTimeout = 10 * time.Second
   104  
   105  	// NEGAnnotation is NEG annotation.
   106  	NEGAnnotation = "cloud.google.com/neg"
   107  
   108  	// NEGStatusAnnotation is NEG status annotation.
   109  	NEGStatusAnnotation = "cloud.google.com/neg-status"
   110  
   111  	// StatusPrefix is prefix for annotation keys used by the ingress controller to specify the
   112  	// names of GCP resources such as forwarding rules, url maps, target proxies, etc
   113  	// that it created for the corresponding ingress.
   114  	StatusPrefix = "ingress.kubernetes.io"
   115  
   116  	// poll is how often to Poll pods, nodes and claims.
   117  	poll = 2 * time.Second
   118  )
   119  
   120  // TestLogger is an interface for log.
   121  type TestLogger interface {
   122  	Infof(format string, args ...interface{})
   123  	Errorf(format string, args ...interface{})
   124  }
   125  
   126  // E2ELogger is test logger.
   127  type E2ELogger struct{}
   128  
   129  // Infof outputs log.
   130  func (l *E2ELogger) Infof(format string, args ...interface{}) {
   131  	framework.Logf(format, args...)
   132  }
   133  
   134  // Errorf outputs log.
   135  func (l *E2ELogger) Errorf(format string, args ...interface{}) {
   136  	framework.Logf(format, args...)
   137  }
   138  
   139  // ConformanceTests contains a closure with an entry and exit log line.
   140  type ConformanceTests struct {
   141  	EntryLog string
   142  	Execute  func()
   143  	ExitLog  string
   144  }
   145  
   146  // NegStatus contains name and zone of the Network Endpoint Group
   147  // resources associated with this service.
   148  // Needs to be consistent with the NEG internal structs in ingress-gce.
   149  type NegStatus struct {
   150  	// NetworkEndpointGroups returns the mapping between service port and NEG
   151  	// resource. key is service port, value is the name of the NEG resource.
   152  	NetworkEndpointGroups map[int32]string `json:"network_endpoint_groups,omitempty"`
   153  	Zones                 []string         `json:"zones,omitempty"`
   154  }
   155  
   156  // SimpleGET executes a get on the given url, returns error if non-200 returned.
   157  func SimpleGET(ctx context.Context, c *http.Client, url, host string) (string, error) {
   158  	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
   159  	if err != nil {
   160  		return "", err
   161  	}
   162  	req.Host = host
   163  	res, err := c.Do(req)
   164  	if err != nil {
   165  		return "", err
   166  	}
   167  	defer res.Body.Close()
   168  	rawBody, err := io.ReadAll(res.Body)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	body := string(rawBody)
   173  	if res.StatusCode != http.StatusOK {
   174  		err = fmt.Errorf(
   175  			"GET returned http error %v", res.StatusCode)
   176  	}
   177  	return body, err
   178  }
   179  
   180  // PollURL polls till the url responds with a healthy http code. If
   181  // expectUnreachable is true, it breaks on first non-healthy http code instead.
   182  func PollURL(ctx context.Context, route, host string, timeout time.Duration, interval time.Duration, httpClient *http.Client, expectUnreachable bool) error {
   183  	var lastBody string
   184  	pollErr := wait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (bool, error) {
   185  		var err error
   186  		lastBody, err = SimpleGET(ctx, httpClient, route, host)
   187  		if err != nil {
   188  			framework.Logf("host %v path %v: %v unreachable", host, route, err)
   189  			return expectUnreachable, nil
   190  		}
   191  		framework.Logf("host %v path %v: reached", host, route)
   192  		return !expectUnreachable, nil
   193  	})
   194  	if pollErr != nil {
   195  		return fmt.Errorf("Failed to execute a successful GET within %v, Last response body for %v, host %v:\n%v\n\n%v",
   196  			timeout, route, host, lastBody, pollErr)
   197  	}
   198  	return nil
   199  }
   200  
   201  // CreateIngressComformanceTests generates an slice of sequential test cases:
   202  // a simple http ingress, ingress with HTTPS, ingress HTTPS with a modified hostname,
   203  // ingress https with a modified URLMap
   204  func CreateIngressComformanceTests(ctx context.Context, jig *TestJig, ns string, annotations map[string]string) []ConformanceTests {
   205  	manifestPath := filepath.Join(IngressManifestPath, "http")
   206  	// These constants match the manifests used in IngressManifestPath
   207  	tlsHost := "foo.bar.com"
   208  	tlsSecretName := "foo"
   209  	updatedTLSHost := "foobar.com"
   210  	updateURLMapHost := "bar.baz.com"
   211  	updateURLMapPath := "/testurl"
   212  	prefixPathType := networkingv1.PathTypeImplementationSpecific
   213  	// Platform agnostic list of tests that must be satisfied by all controllers
   214  	tests := []ConformanceTests{
   215  		{
   216  			fmt.Sprintf("should create a basic HTTP ingress"),
   217  			func() { jig.CreateIngress(ctx, manifestPath, ns, annotations, annotations) },
   218  			fmt.Sprintf("waiting for urls on basic HTTP ingress"),
   219  		},
   220  		{
   221  			fmt.Sprintf("should terminate TLS for host %v", tlsHost),
   222  			func() { jig.SetHTTPS(ctx, tlsSecretName, tlsHost) },
   223  			fmt.Sprintf("waiting for HTTPS updates to reflect in ingress"),
   224  		},
   225  		{
   226  			fmt.Sprintf("should update url map for host %v to expose a single url: %v", updateURLMapHost, updateURLMapPath),
   227  			func() {
   228  				var pathToFail string
   229  				jig.Update(ctx, func(ing *networkingv1.Ingress) {
   230  					newRules := []networkingv1.IngressRule{}
   231  					for _, rule := range ing.Spec.Rules {
   232  						if rule.Host != updateURLMapHost {
   233  							newRules = append(newRules, rule)
   234  							continue
   235  						}
   236  						existingPath := rule.IngressRuleValue.HTTP.Paths[0]
   237  						pathToFail = existingPath.Path
   238  						newRules = append(newRules, networkingv1.IngressRule{
   239  							Host: updateURLMapHost,
   240  							IngressRuleValue: networkingv1.IngressRuleValue{
   241  								HTTP: &networkingv1.HTTPIngressRuleValue{
   242  									Paths: []networkingv1.HTTPIngressPath{
   243  										{
   244  											Path:     updateURLMapPath,
   245  											PathType: &prefixPathType,
   246  											Backend:  existingPath.Backend,
   247  										},
   248  									},
   249  								},
   250  							},
   251  						})
   252  					}
   253  					ing.Spec.Rules = newRules
   254  				})
   255  				ginkgo.By("Checking that " + pathToFail + " is not exposed by polling for failure")
   256  				route := fmt.Sprintf("http://%v%v", jig.Address, pathToFail)
   257  				framework.ExpectNoError(PollURL(ctx, route, updateURLMapHost, e2eservice.LoadBalancerCleanupTimeout, jig.PollInterval, &http.Client{Timeout: IngressReqTimeout}, true))
   258  			},
   259  			fmt.Sprintf("Waiting for path updates to reflect in L7"),
   260  		},
   261  	}
   262  	// Skip the Update TLS cert test for kubemci: https://github.com/GoogleCloudPlatform/k8s-multicluster-ingress/issues/141.
   263  	if jig.Class != MulticlusterIngressClassValue {
   264  		tests = append(tests, ConformanceTests{
   265  			fmt.Sprintf("should update SSL certificate with modified hostname %v", updatedTLSHost),
   266  			func() {
   267  				jig.Update(ctx, func(ing *networkingv1.Ingress) {
   268  					newRules := []networkingv1.IngressRule{}
   269  					for _, rule := range ing.Spec.Rules {
   270  						if rule.Host != tlsHost {
   271  							newRules = append(newRules, rule)
   272  							continue
   273  						}
   274  						newRules = append(newRules, networkingv1.IngressRule{
   275  							Host:             updatedTLSHost,
   276  							IngressRuleValue: rule.IngressRuleValue,
   277  						})
   278  					}
   279  					ing.Spec.Rules = newRules
   280  				})
   281  				jig.SetHTTPS(ctx, tlsSecretName, updatedTLSHost)
   282  			},
   283  			fmt.Sprintf("Waiting for updated certificates to accept requests for host %v", updatedTLSHost),
   284  		})
   285  	}
   286  	return tests
   287  }
   288  
   289  // GenerateRSACerts generates a basic self signed certificate using a key length
   290  // of rsaBits, valid for validFor time.
   291  func GenerateRSACerts(host string, isCA bool) ([]byte, []byte, error) {
   292  	if len(host) == 0 {
   293  		return nil, nil, fmt.Errorf("Require a non-empty host for client hello")
   294  	}
   295  	priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
   296  	if err != nil {
   297  		return nil, nil, fmt.Errorf("Failed to generate key: %w", err)
   298  	}
   299  	notBefore := time.Now()
   300  	notAfter := notBefore.Add(validFor)
   301  
   302  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   303  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
   304  
   305  	if err != nil {
   306  		return nil, nil, fmt.Errorf("failed to generate serial number: %w", err)
   307  	}
   308  	template := x509.Certificate{
   309  		SerialNumber: serialNumber,
   310  		Subject: pkix.Name{
   311  			CommonName:   "default",
   312  			Organization: []string{"Acme Co"},
   313  		},
   314  		NotBefore: notBefore,
   315  		NotAfter:  notAfter,
   316  
   317  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   318  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   319  		BasicConstraintsValid: true,
   320  	}
   321  
   322  	hosts := strings.Split(host, ",")
   323  	for _, h := range hosts {
   324  		if ip := netutils.ParseIPSloppy(h); ip != nil {
   325  			template.IPAddresses = append(template.IPAddresses, ip)
   326  		} else {
   327  			template.DNSNames = append(template.DNSNames, h)
   328  		}
   329  	}
   330  
   331  	if isCA {
   332  		template.IsCA = true
   333  		template.KeyUsage |= x509.KeyUsageCertSign
   334  	}
   335  
   336  	var keyOut, certOut bytes.Buffer
   337  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
   338  	if err != nil {
   339  		return nil, nil, fmt.Errorf("Failed to create certificate: %w", err)
   340  	}
   341  	if err := pem.Encode(&certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
   342  		return nil, nil, fmt.Errorf("Failed creating cert: %w", err)
   343  	}
   344  	if err := pem.Encode(&keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
   345  		return nil, nil, fmt.Errorf("Failed creating key: %w", err)
   346  	}
   347  	return certOut.Bytes(), keyOut.Bytes(), nil
   348  }
   349  
   350  // buildTransportWithCA creates a transport for use in executing HTTPS requests with
   351  // the given certs. Note that the given rootCA must be configured with isCA=true.
   352  func buildTransportWithCA(serverName string, rootCA []byte) (*http.Transport, error) {
   353  	pool := x509.NewCertPool()
   354  	ok := pool.AppendCertsFromPEM(rootCA)
   355  	if !ok {
   356  		return nil, fmt.Errorf("Unable to load serverCA")
   357  	}
   358  	return utilnet.SetTransportDefaults(&http.Transport{
   359  		TLSClientConfig: &tls.Config{
   360  			InsecureSkipVerify: false,
   361  			ServerName:         serverName,
   362  			RootCAs:            pool,
   363  		},
   364  	}), nil
   365  }
   366  
   367  // BuildInsecureClient returns an insecure http client. Can be used for "curl -k".
   368  func BuildInsecureClient(timeout time.Duration) *http.Client {
   369  	t := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
   370  	return &http.Client{Timeout: timeout, Transport: utilnet.SetTransportDefaults(t)}
   371  }
   372  
   373  // createTLSSecret creates a secret containing TLS certificates.
   374  // If a secret with the same name already pathExists in the namespace of the
   375  // Ingress, it's updated.
   376  func createTLSSecret(ctx context.Context, kubeClient clientset.Interface, namespace, secretName string, hosts ...string) (host string, rootCA, privKey []byte, err error) {
   377  	host = strings.Join(hosts, ",")
   378  	framework.Logf("Generating RSA cert for host %v", host)
   379  	cert, key, err := GenerateRSACerts(host, true)
   380  	if err != nil {
   381  		return
   382  	}
   383  	secret := &v1.Secret{
   384  		ObjectMeta: metav1.ObjectMeta{
   385  			Name: secretName,
   386  		},
   387  		Data: map[string][]byte{
   388  			v1.TLSCertKey:       cert,
   389  			v1.TLSPrivateKeyKey: key,
   390  		},
   391  	}
   392  	var s *v1.Secret
   393  	if s, err = kubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}); err == nil {
   394  		framework.Logf("Updating secret %v in ns %v with hosts %v", secret.Name, namespace, host)
   395  		s.Data = secret.Data
   396  		_, err = kubeClient.CoreV1().Secrets(namespace).Update(ctx, s, metav1.UpdateOptions{})
   397  	} else {
   398  		framework.Logf("Creating secret %v in ns %v with hosts %v", secret.Name, namespace, host)
   399  		_, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{})
   400  	}
   401  	return host, cert, key, err
   402  }
   403  
   404  // TestJig holds the relevant state and parameters of the ingress test.
   405  type TestJig struct {
   406  	Client clientset.Interface
   407  	Logger TestLogger
   408  
   409  	RootCAs map[string][]byte
   410  	Address string
   411  	Ingress *networkingv1.Ingress
   412  	// class was the value of the annotation keyed under `kubernetes.io/ingress.class`.
   413  	// A new ingressClassName field has been added that is used to reference the IngressClass.
   414  	// It's added to all ingresses created by this jig.
   415  	Class string
   416  
   417  	// The interval used to poll urls
   418  	PollInterval time.Duration
   419  }
   420  
   421  // NewIngressTestJig instantiates struct with client
   422  func NewIngressTestJig(c clientset.Interface) *TestJig {
   423  	return &TestJig{
   424  		Client:       c,
   425  		RootCAs:      map[string][]byte{},
   426  		PollInterval: e2eservice.LoadBalancerPollInterval,
   427  		Logger:       &E2ELogger{},
   428  	}
   429  }
   430  
   431  // CreateIngress creates the Ingress and associated service/rc.
   432  // Required: ing.yaml, rc.yaml, svc.yaml must exist in manifestPath
   433  // Optional: secret.yaml, ingAnnotations
   434  // If ingAnnotations is specified it will overwrite any annotations in ing.yaml
   435  // If svcAnnotations is specified it will overwrite any annotations in svc.yaml
   436  func (j *TestJig) CreateIngress(ctx context.Context, manifestPath, ns string, ingAnnotations map[string]string, svcAnnotations map[string]string) {
   437  	var err error
   438  	read := func(file string) string {
   439  		data, err := e2etestfiles.Read(filepath.Join(manifestPath, file))
   440  		if err != nil {
   441  			framework.Fail(err.Error())
   442  		}
   443  		return string(data)
   444  	}
   445  	exists := func(file string) bool {
   446  		found, err := e2etestfiles.Exists(filepath.Join(manifestPath, file))
   447  		if err != nil {
   448  			framework.Fail(fmt.Sprintf("fatal error looking for test file %s: %s", file, err))
   449  		}
   450  		return found
   451  	}
   452  
   453  	j.Logger.Infof("creating replication controller")
   454  	e2ekubectl.RunKubectlOrDieInput(ns, read("rc.yaml"), "create", "-f", "-")
   455  
   456  	j.Logger.Infof("creating service")
   457  	e2ekubectl.RunKubectlOrDieInput(ns, read("svc.yaml"), "create", "-f", "-")
   458  	if len(svcAnnotations) > 0 {
   459  		svcList, err := j.Client.CoreV1().Services(ns).List(ctx, metav1.ListOptions{})
   460  		framework.ExpectNoError(err)
   461  		for _, svc := range svcList.Items {
   462  			svc.Annotations = svcAnnotations
   463  			_, err = j.Client.CoreV1().Services(ns).Update(ctx, &svc, metav1.UpdateOptions{})
   464  			framework.ExpectNoError(err)
   465  		}
   466  	}
   467  
   468  	if exists("secret.yaml") {
   469  		j.Logger.Infof("creating secret")
   470  		e2ekubectl.RunKubectlOrDieInput(ns, read("secret.yaml"), "create", "-f", "-")
   471  	}
   472  	j.Logger.Infof("Parsing ingress from %v", filepath.Join(manifestPath, "ing.yaml"))
   473  
   474  	j.Ingress, err = ingressFromManifest(filepath.Join(manifestPath, "ing.yaml"))
   475  	framework.ExpectNoError(err)
   476  	j.Ingress.Namespace = ns
   477  	if j.Class != "" {
   478  		j.Ingress.Spec.IngressClassName = &j.Class
   479  	}
   480  	j.Logger.Infof("creating %v ingress", j.Ingress.Name)
   481  	j.Ingress, err = j.runCreate(ctx, j.Ingress)
   482  	framework.ExpectNoError(err)
   483  }
   484  
   485  // marshalToYaml marshals an object into YAML for a given GroupVersion.
   486  // The object must be known in SupportedMediaTypes() for the Codecs under "client-go/kubernetes/scheme".
   487  func marshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
   488  	mediaType := "application/yaml"
   489  	info, ok := runtime.SerializerInfoForMediaType(scheme.Codecs.SupportedMediaTypes(), mediaType)
   490  	if !ok {
   491  		return []byte{}, fmt.Errorf("unsupported media type %q", mediaType)
   492  	}
   493  	encoder := scheme.Codecs.EncoderForVersion(info.Serializer, gv)
   494  	return runtime.Encode(encoder, obj)
   495  }
   496  
   497  // ingressFromManifest reads a .json/yaml file and returns the ingress in it.
   498  func ingressFromManifest(fileName string) (*networkingv1.Ingress, error) {
   499  	var ing networkingv1.Ingress
   500  	data, err := e2etestfiles.Read(fileName)
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  
   505  	json, err := utilyaml.ToJSON(data)
   506  	if err != nil {
   507  		return nil, err
   508  	}
   509  	if err := runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), json, &ing); err != nil {
   510  		return nil, err
   511  	}
   512  	return &ing, nil
   513  }
   514  
   515  // ingressToManifest generates a yaml file in the given path with the given ingress.
   516  // Assumes that a directory exists at the given path.
   517  func ingressToManifest(ing *networkingv1.Ingress, path string) error {
   518  	serialized, err := marshalToYaml(ing, networkingv1.SchemeGroupVersion)
   519  	if err != nil {
   520  		return fmt.Errorf("failed to marshal ingress %v to YAML: %w", ing, err)
   521  	}
   522  
   523  	if err := os.WriteFile(path, serialized, 0600); err != nil {
   524  		return fmt.Errorf("error in writing ingress to file: %w", err)
   525  	}
   526  	return nil
   527  }
   528  
   529  // runCreate runs the required command to create the given ingress.
   530  func (j *TestJig) runCreate(ctx context.Context, ing *networkingv1.Ingress) (*networkingv1.Ingress, error) {
   531  	if j.Class != MulticlusterIngressClassValue {
   532  		return j.Client.NetworkingV1().Ingresses(ing.Namespace).Create(ctx, ing, metav1.CreateOptions{})
   533  	}
   534  	// Use kubemci to create a multicluster ingress.
   535  	filePath := framework.TestContext.OutputDir + "/mci.yaml"
   536  	if err := ingressToManifest(ing, filePath); err != nil {
   537  		return nil, err
   538  	}
   539  	_, err := e2ekubectl.RunKubemciWithKubeconfig("create", ing.Name, fmt.Sprintf("--ingress=%s", filePath))
   540  	return ing, err
   541  }
   542  
   543  // runUpdate runs the required command to update the given ingress.
   544  func (j *TestJig) runUpdate(ctx context.Context, ing *networkingv1.Ingress) (*networkingv1.Ingress, error) {
   545  	if j.Class != MulticlusterIngressClassValue {
   546  		return j.Client.NetworkingV1().Ingresses(ing.Namespace).Update(ctx, ing, metav1.UpdateOptions{})
   547  	}
   548  	// Use kubemci to update a multicluster ingress.
   549  	// kubemci does not have an update command. We use "create --force" to update an existing ingress.
   550  	filePath := framework.TestContext.OutputDir + "/mci.yaml"
   551  	if err := ingressToManifest(ing, filePath); err != nil {
   552  		return nil, err
   553  	}
   554  	_, err := e2ekubectl.RunKubemciWithKubeconfig("create", ing.Name, fmt.Sprintf("--ingress=%s", filePath), "--force")
   555  	return ing, err
   556  }
   557  
   558  // DescribeIng describes information of ingress by running kubectl describe ing.
   559  func DescribeIng(ns string) {
   560  	framework.Logf("\nOutput of kubectl describe ing:\n")
   561  	desc, _ := e2ekubectl.RunKubectl(
   562  		ns, "describe", "ing")
   563  	framework.Logf(desc)
   564  }
   565  
   566  // Update retrieves the ingress, performs the passed function, and then updates it.
   567  func (j *TestJig) Update(ctx context.Context, update func(ing *networkingv1.Ingress)) {
   568  	var err error
   569  	ns, name := j.Ingress.Namespace, j.Ingress.Name
   570  	for i := 0; i < 3; i++ {
   571  		j.Ingress, err = j.Client.NetworkingV1().Ingresses(ns).Get(ctx, name, metav1.GetOptions{})
   572  		if err != nil {
   573  			framework.Failf("failed to get ingress %s/%s: %v", ns, name, err)
   574  		}
   575  		update(j.Ingress)
   576  		j.Ingress, err = j.runUpdate(ctx, j.Ingress)
   577  		if err == nil {
   578  			DescribeIng(j.Ingress.Namespace)
   579  			return
   580  		}
   581  		if !apierrors.IsConflict(err) && !apierrors.IsServerTimeout(err) {
   582  			framework.Failf("failed to update ingress %s/%s: %v", ns, name, err)
   583  		}
   584  	}
   585  	framework.Failf("too many retries updating ingress %s/%s", ns, name)
   586  }
   587  
   588  // AddHTTPS updates the ingress to add this secret for these hosts.
   589  func (j *TestJig) AddHTTPS(ctx context.Context, secretName string, hosts ...string) {
   590  	// TODO: Just create the secret in GetRootCAs once we're watching secrets in
   591  	// the ingress controller.
   592  	_, cert, _, err := createTLSSecret(ctx, j.Client, j.Ingress.Namespace, secretName, hosts...)
   593  	framework.ExpectNoError(err)
   594  	j.Logger.Infof("Updating ingress %v to also use secret %v for TLS termination", j.Ingress.Name, secretName)
   595  	j.Update(ctx, func(ing *networkingv1.Ingress) {
   596  		ing.Spec.TLS = append(ing.Spec.TLS, networkingv1.IngressTLS{Hosts: hosts, SecretName: secretName})
   597  	})
   598  	j.RootCAs[secretName] = cert
   599  }
   600  
   601  // SetHTTPS updates the ingress to use only this secret for these hosts.
   602  func (j *TestJig) SetHTTPS(ctx context.Context, secretName string, hosts ...string) {
   603  	_, cert, _, err := createTLSSecret(ctx, j.Client, j.Ingress.Namespace, secretName, hosts...)
   604  	framework.ExpectNoError(err)
   605  	j.Logger.Infof("Updating ingress %v to only use secret %v for TLS termination", j.Ingress.Name, secretName)
   606  	j.Update(ctx, func(ing *networkingv1.Ingress) {
   607  		ing.Spec.TLS = []networkingv1.IngressTLS{{Hosts: hosts, SecretName: secretName}}
   608  	})
   609  	j.RootCAs = map[string][]byte{secretName: cert}
   610  }
   611  
   612  // RemoveHTTPS updates the ingress to not use this secret for TLS.
   613  // Note: Does not delete the secret.
   614  func (j *TestJig) RemoveHTTPS(ctx context.Context, secretName string) {
   615  	newTLS := []networkingv1.IngressTLS{}
   616  	for _, ingressTLS := range j.Ingress.Spec.TLS {
   617  		if secretName != ingressTLS.SecretName {
   618  			newTLS = append(newTLS, ingressTLS)
   619  		}
   620  	}
   621  	j.Logger.Infof("Updating ingress %v to not use secret %v for TLS termination", j.Ingress.Name, secretName)
   622  	j.Update(ctx, func(ing *networkingv1.Ingress) {
   623  		ing.Spec.TLS = newTLS
   624  	})
   625  	delete(j.RootCAs, secretName)
   626  }
   627  
   628  // PrepareTLSSecret creates a TLS secret and caches the cert.
   629  func (j *TestJig) PrepareTLSSecret(ctx context.Context, namespace, secretName string, hosts ...string) error {
   630  	_, cert, _, err := createTLSSecret(ctx, j.Client, namespace, secretName, hosts...)
   631  	if err != nil {
   632  		return err
   633  	}
   634  	j.RootCAs[secretName] = cert
   635  	return nil
   636  }
   637  
   638  // GetRootCA returns a rootCA from the ingress test jig.
   639  func (j *TestJig) GetRootCA(secretName string) (rootCA []byte) {
   640  	var ok bool
   641  	rootCA, ok = j.RootCAs[secretName]
   642  	if !ok {
   643  		framework.Failf("Failed to retrieve rootCAs, no recorded secret by name %v", secretName)
   644  	}
   645  	return
   646  }
   647  
   648  // TryDeleteIngress attempts to delete the ingress resource and logs errors if they occur.
   649  func (j *TestJig) TryDeleteIngress(ctx context.Context) {
   650  	j.tryDeleteGivenIngress(ctx, j.Ingress)
   651  }
   652  
   653  func (j *TestJig) tryDeleteGivenIngress(ctx context.Context, ing *networkingv1.Ingress) {
   654  	if err := j.runDelete(ctx, ing); err != nil {
   655  		j.Logger.Infof("Error while deleting the ingress %v/%v with class %s: %v", ing.Namespace, ing.Name, j.Class, err)
   656  	}
   657  }
   658  
   659  // runDelete runs the required command to delete the given ingress.
   660  func (j *TestJig) runDelete(ctx context.Context, ing *networkingv1.Ingress) error {
   661  	if j.Class != MulticlusterIngressClassValue {
   662  		return j.Client.NetworkingV1().Ingresses(ing.Namespace).Delete(ctx, ing.Name, metav1.DeleteOptions{})
   663  	}
   664  	// Use kubemci to delete a multicluster ingress.
   665  	filePath := framework.TestContext.OutputDir + "/mci.yaml"
   666  	if err := ingressToManifest(ing, filePath); err != nil {
   667  		return err
   668  	}
   669  	_, err := e2ekubectl.RunKubemciWithKubeconfig("delete", ing.Name, fmt.Sprintf("--ingress=%s", filePath))
   670  	return err
   671  }
   672  
   673  // getIngressAddressFromKubemci returns the IP address of the given multicluster ingress using kubemci.
   674  // TODO(nikhiljindal): Update this to be able to return hostname as well.
   675  func getIngressAddressFromKubemci(name string) ([]string, error) {
   676  	var addresses []string
   677  	out, err := e2ekubectl.RunKubemciCmd("get-status", name)
   678  	if err != nil {
   679  		return addresses, err
   680  	}
   681  	ip := findIPv4(out)
   682  	if ip != "" {
   683  		addresses = append(addresses, ip)
   684  	}
   685  	return addresses, err
   686  }
   687  
   688  // findIPv4 returns the first IPv4 address found in the given string.
   689  func findIPv4(input string) string {
   690  	numBlock := "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
   691  	regexPattern := numBlock + "\\." + numBlock + "\\." + numBlock + "\\." + numBlock
   692  
   693  	regEx := regexp.MustCompile(regexPattern)
   694  	return regEx.FindString(input)
   695  }
   696  
   697  // getIngressAddress returns the ips/hostnames associated with the Ingress.
   698  func getIngressAddress(ctx context.Context, client clientset.Interface, ns, name, class string) ([]string, error) {
   699  	if class == MulticlusterIngressClassValue {
   700  		return getIngressAddressFromKubemci(name)
   701  	}
   702  	ing, err := client.NetworkingV1().Ingresses(ns).Get(ctx, name, metav1.GetOptions{})
   703  	if err != nil {
   704  		return nil, err
   705  	}
   706  	var addresses []string
   707  	for _, a := range ing.Status.LoadBalancer.Ingress {
   708  		if a.IP != "" {
   709  			addresses = append(addresses, a.IP)
   710  		}
   711  		if a.Hostname != "" {
   712  			addresses = append(addresses, a.Hostname)
   713  		}
   714  	}
   715  	return addresses, nil
   716  }
   717  
   718  // WaitForIngressAddress waits for the Ingress to acquire an address.
   719  func (j *TestJig) WaitForIngressAddress(ctx context.Context, c clientset.Interface, ns, ingName string, timeout time.Duration) (string, error) {
   720  	var address string
   721  	err := wait.PollUntilContextTimeout(ctx, 10*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
   722  		ipOrNameList, err := getIngressAddress(ctx, c, ns, ingName, j.Class)
   723  		if err != nil || len(ipOrNameList) == 0 {
   724  			j.Logger.Errorf("Waiting for Ingress %s/%s to acquire IP, error: %v, ipOrNameList: %v", ns, ingName, err, ipOrNameList)
   725  			return false, err
   726  		}
   727  		address = ipOrNameList[0]
   728  		j.Logger.Infof("Found address %s for ingress %s/%s", address, ns, ingName)
   729  		return true, nil
   730  	})
   731  	return address, err
   732  }
   733  
   734  func (j *TestJig) pollIngressWithCert(ctx context.Context, ing *networkingv1.Ingress, address string, knownHosts []string, cert []byte, waitForNodePort bool, timeout time.Duration) error {
   735  	// Check that all rules respond to a simple GET.
   736  	knownHostsSet := sets.NewString(knownHosts...)
   737  	for _, rules := range ing.Spec.Rules {
   738  		timeoutClient := &http.Client{Timeout: IngressReqTimeout}
   739  		proto := "http"
   740  		if knownHostsSet.Has(rules.Host) {
   741  			var err error
   742  			// Create transport with cert to verify if the server uses the correct one.
   743  			timeoutClient.Transport, err = buildTransportWithCA(rules.Host, cert)
   744  			if err != nil {
   745  				return err
   746  			}
   747  			proto = "https"
   748  		}
   749  		for _, p := range rules.IngressRuleValue.HTTP.Paths {
   750  			if waitForNodePort {
   751  				nodePort := int(p.Backend.Service.Port.Number)
   752  				if err := j.pollServiceNodePort(ctx, ing.Namespace, p.Backend.Service.Name, nodePort); err != nil {
   753  					j.Logger.Infof("Error in waiting for nodeport %d on service %v/%v: %s", nodePort, ing.Namespace, p.Backend.Service.Name, err)
   754  					return err
   755  				}
   756  			}
   757  			route := fmt.Sprintf("%v://%v%v", proto, address, p.Path)
   758  			j.Logger.Infof("Testing route %v host %v with simple GET", route, rules.Host)
   759  			if err := PollURL(ctx, route, rules.Host, timeout, j.PollInterval, timeoutClient, false); err != nil {
   760  				return err
   761  			}
   762  		}
   763  	}
   764  	j.Logger.Infof("Finished polling on all rules on ingress %q", ing.Name)
   765  	return nil
   766  }
   767  
   768  // WaitForIngress waits for the Ingress to get an address.
   769  // WaitForIngress returns when it gets the first 200 response
   770  func (j *TestJig) WaitForIngress(ctx context.Context, waitForNodePort bool) {
   771  	if err := j.WaitForGivenIngressWithTimeout(ctx, j.Ingress, waitForNodePort, e2eservice.GetServiceLoadBalancerPropagationTimeout(ctx, j.Client)); err != nil {
   772  		framework.Failf("error in waiting for ingress to get an address: %s", err)
   773  	}
   774  }
   775  
   776  // WaitForIngressToStable waits for the LB return 100 consecutive 200 responses.
   777  func (j *TestJig) WaitForIngressToStable(ctx context.Context) {
   778  	if err := wait.PollWithContext(ctx, 10*time.Second, e2eservice.GetServiceLoadBalancerPropagationTimeout(ctx, j.Client), func(ctx context.Context) (bool, error) {
   779  		_, err := j.GetDistinctResponseFromIngress(ctx)
   780  		if err != nil {
   781  			return false, nil
   782  		}
   783  		return true, nil
   784  	}); err != nil {
   785  		framework.Failf("error in waiting for ingress to stabilize: %v", err)
   786  	}
   787  }
   788  
   789  // WaitForGivenIngressWithTimeout waits till the ingress acquires an IP,
   790  // then waits for its hosts/urls to respond to a protocol check (either
   791  // http or https). If waitForNodePort is true, the NodePort of the Service
   792  // is verified before verifying the Ingress. NodePort is currently a
   793  // requirement for cloudprovider Ingress.
   794  func (j *TestJig) WaitForGivenIngressWithTimeout(ctx context.Context, ing *networkingv1.Ingress, waitForNodePort bool, timeout time.Duration) error {
   795  	// Wait for the loadbalancer IP.
   796  	address, err := j.WaitForIngressAddress(ctx, j.Client, ing.Namespace, ing.Name, timeout)
   797  	if err != nil {
   798  		return fmt.Errorf("Ingress failed to acquire an IP address within %v", timeout)
   799  	}
   800  
   801  	var knownHosts []string
   802  	var cert []byte
   803  	if len(ing.Spec.TLS) > 0 {
   804  		knownHosts = ing.Spec.TLS[0].Hosts
   805  		cert = j.GetRootCA(ing.Spec.TLS[0].SecretName)
   806  	}
   807  	return j.pollIngressWithCert(ctx, ing, address, knownHosts, cert, waitForNodePort, timeout)
   808  }
   809  
   810  // WaitForIngressWithCert waits till the ingress acquires an IP, then waits for its
   811  // hosts/urls to respond to a protocol check (either http or https). If
   812  // waitForNodePort is true, the NodePort of the Service is verified before
   813  // verifying the Ingress. NodePort is currently a requirement for cloudprovider
   814  // Ingress. Hostnames and certificate need to be explicitly passed in.
   815  func (j *TestJig) WaitForIngressWithCert(ctx context.Context, waitForNodePort bool, knownHosts []string, cert []byte) error {
   816  	// Wait for the loadbalancer IP.
   817  	propagationTimeout := e2eservice.GetServiceLoadBalancerPropagationTimeout(ctx, j.Client)
   818  	address, err := j.WaitForIngressAddress(ctx, j.Client, j.Ingress.Namespace, j.Ingress.Name, propagationTimeout)
   819  	if err != nil {
   820  		return fmt.Errorf("Ingress failed to acquire an IP address within %v", propagationTimeout)
   821  	}
   822  
   823  	return j.pollIngressWithCert(ctx, j.Ingress, address, knownHosts, cert, waitForNodePort, propagationTimeout)
   824  }
   825  
   826  // VerifyURL polls for the given iterations, in intervals, and fails if the
   827  // given url returns a non-healthy http code even once.
   828  func (j *TestJig) VerifyURL(ctx context.Context, route, host string, iterations int, interval time.Duration, httpClient *http.Client) error {
   829  	for i := 0; i < iterations; i++ {
   830  		b, err := SimpleGET(ctx, httpClient, route, host)
   831  		if err != nil {
   832  			framework.Logf(b)
   833  			return err
   834  		}
   835  		j.Logger.Infof("Verified %v with host %v %d times, sleeping for %v", route, host, i, interval)
   836  		time.Sleep(interval)
   837  	}
   838  	return nil
   839  }
   840  
   841  func (j *TestJig) pollServiceNodePort(ctx context.Context, ns, name string, port int) error {
   842  	// TODO: Curl all nodes?
   843  	u, err := getPortURL(ctx, j.Client, ns, name, port)
   844  	if err != nil {
   845  		return err
   846  	}
   847  	return PollURL(ctx, u, "", 30*time.Second, j.PollInterval, &http.Client{Timeout: IngressReqTimeout}, false)
   848  }
   849  
   850  // getSvcNodePort returns the node port for the given service:port.
   851  func getSvcNodePort(ctx context.Context, client clientset.Interface, ns, name string, svcPort int) (int, error) {
   852  	svc, err := client.CoreV1().Services(ns).Get(ctx, name, metav1.GetOptions{})
   853  	if err != nil {
   854  		return 0, err
   855  	}
   856  	for _, p := range svc.Spec.Ports {
   857  		if p.Port == int32(svcPort) {
   858  			if p.NodePort != 0 {
   859  				return int(p.NodePort), nil
   860  			}
   861  		}
   862  	}
   863  	return 0, fmt.Errorf(
   864  		"no node port found for service %v, port %v", name, svcPort)
   865  }
   866  
   867  // getPortURL returns the url to a nodeport Service.
   868  func getPortURL(ctx context.Context, client clientset.Interface, ns, name string, svcPort int) (string, error) {
   869  	nodePort, err := getSvcNodePort(ctx, client, ns, name, svcPort)
   870  	if err != nil {
   871  		return "", err
   872  	}
   873  	// This list of nodes must not include the any control plane nodes, which are marked
   874  	// unschedulable, since control plane nodes don't run kube-proxy. Without
   875  	// kube-proxy NodePorts won't work.
   876  	var nodes *v1.NodeList
   877  	if wait.PollUntilContextTimeout(ctx, poll, framework.SingleCallTimeout, true, func(ctx context.Context) (bool, error) {
   878  		nodes, err = client.CoreV1().Nodes().List(ctx, metav1.ListOptions{FieldSelector: fields.Set{
   879  			"spec.unschedulable": "false",
   880  		}.AsSelector().String()})
   881  		if err != nil {
   882  			return false, err
   883  		}
   884  		return true, nil
   885  	}) != nil {
   886  		return "", err
   887  	}
   888  	if len(nodes.Items) == 0 {
   889  		return "", fmt.Errorf("Unable to list nodes in cluster")
   890  	}
   891  	for _, node := range nodes.Items {
   892  		for _, address := range node.Status.Addresses {
   893  			if address.Type == v1.NodeExternalIP {
   894  				if address.Address != "" {
   895  					host := net.JoinHostPort(address.Address, fmt.Sprint(nodePort))
   896  					return fmt.Sprintf("http://%s", host), nil
   897  				}
   898  			}
   899  		}
   900  	}
   901  	return "", fmt.Errorf("failed to find external address for service %v", name)
   902  }
   903  
   904  // GetIngressNodePorts returns related backend services' nodePorts.
   905  // Current GCE ingress controller allows traffic to the default HTTP backend
   906  // by default, so retrieve its nodePort if includeDefaultBackend is true.
   907  func (j *TestJig) GetIngressNodePorts(ctx context.Context, includeDefaultBackend bool) []string {
   908  	nodePorts := []string{}
   909  	svcPorts := j.GetServicePorts(ctx, includeDefaultBackend)
   910  	for _, svcPort := range svcPorts {
   911  		nodePorts = append(nodePorts, strconv.Itoa(int(svcPort.NodePort)))
   912  	}
   913  	return nodePorts
   914  }
   915  
   916  // GetServicePorts returns related backend services' svcPorts.
   917  // Current GCE ingress controller allows traffic to the default HTTP backend
   918  // by default, so retrieve its nodePort if includeDefaultBackend is true.
   919  func (j *TestJig) GetServicePorts(ctx context.Context, includeDefaultBackend bool) map[string]v1.ServicePort {
   920  	svcPorts := make(map[string]v1.ServicePort)
   921  	if includeDefaultBackend {
   922  		defaultSvc, err := j.Client.CoreV1().Services(metav1.NamespaceSystem).Get(ctx, defaultBackendName, metav1.GetOptions{})
   923  		framework.ExpectNoError(err)
   924  		svcPorts[defaultBackendName] = defaultSvc.Spec.Ports[0]
   925  	}
   926  
   927  	backendSvcs := []string{}
   928  	if j.Ingress.Spec.DefaultBackend != nil {
   929  		backendSvcs = append(backendSvcs, j.Ingress.Spec.DefaultBackend.Service.Name)
   930  	}
   931  	for _, rule := range j.Ingress.Spec.Rules {
   932  		for _, ingPath := range rule.HTTP.Paths {
   933  			backendSvcs = append(backendSvcs, ingPath.Backend.Service.Name)
   934  		}
   935  	}
   936  	for _, svcName := range backendSvcs {
   937  		svc, err := j.Client.CoreV1().Services(j.Ingress.Namespace).Get(ctx, svcName, metav1.GetOptions{})
   938  		framework.ExpectNoError(err)
   939  		svcPorts[svcName] = svc.Spec.Ports[0]
   940  	}
   941  	return svcPorts
   942  }
   943  
   944  // GetDistinctResponseFromIngress tries GET call to the ingress VIP and return all distinct responses.
   945  func (j *TestJig) GetDistinctResponseFromIngress(ctx context.Context) (sets.String, error) {
   946  	// Wait for the loadbalancer IP.
   947  	propagationTimeout := e2eservice.GetServiceLoadBalancerPropagationTimeout(ctx, j.Client)
   948  	address, err := j.WaitForIngressAddress(ctx, j.Client, j.Ingress.Namespace, j.Ingress.Name, propagationTimeout)
   949  	if err != nil {
   950  		framework.Failf("Ingress failed to acquire an IP address within %v", propagationTimeout)
   951  	}
   952  	responses := sets.NewString()
   953  	timeoutClient := &http.Client{Timeout: IngressReqTimeout}
   954  
   955  	for i := 0; i < 100; i++ {
   956  		url := fmt.Sprintf("http://%v", address)
   957  		res, err := SimpleGET(ctx, timeoutClient, url, "")
   958  		if err != nil {
   959  			j.Logger.Errorf("Failed to GET %q. Got responses: %q: %v", url, res, err)
   960  			return responses, err
   961  		}
   962  		responses.Insert(res)
   963  	}
   964  	return responses, nil
   965  }
   966  
   967  // NginxIngressController manages implementation details of Ingress on Nginx.
   968  type NginxIngressController struct {
   969  	Ns     string
   970  	rc     *v1.ReplicationController
   971  	pod    *v1.Pod
   972  	Client clientset.Interface
   973  	lbSvc  *v1.Service
   974  }
   975  
   976  // Init initializes the NginxIngressController
   977  func (cont *NginxIngressController) Init(ctx context.Context) {
   978  	// Set up a LoadBalancer service in front of nginx ingress controller and pass it via
   979  	// --publish-service flag (see <IngressManifestPath>/nginx/rc.yaml) to make it work in private
   980  	// clusters, i.e. clusters where nodes don't have public IPs.
   981  	framework.Logf("Creating load balancer service for nginx ingress controller")
   982  	serviceJig := e2eservice.NewTestJig(cont.Client, cont.Ns, "nginx-ingress-lb")
   983  	_, err := serviceJig.CreateTCPService(ctx, func(svc *v1.Service) {
   984  		svc.Spec.Type = v1.ServiceTypeLoadBalancer
   985  		svc.Spec.Selector = map[string]string{"k8s-app": "nginx-ingress-lb"}
   986  		svc.Spec.Ports = []v1.ServicePort{
   987  			{Name: "http", Port: 80},
   988  			{Name: "https", Port: 443},
   989  			{Name: "stats", Port: 18080}}
   990  	})
   991  	framework.ExpectNoError(err)
   992  	cont.lbSvc, err = serviceJig.WaitForLoadBalancer(ctx, e2eservice.GetServiceLoadBalancerCreationTimeout(ctx, cont.Client))
   993  	framework.ExpectNoError(err)
   994  
   995  	read := func(file string) string {
   996  		data, err := e2etestfiles.Read(filepath.Join(IngressManifestPath, "nginx", file))
   997  		if err != nil {
   998  			framework.Fail(err.Error())
   999  		}
  1000  		return string(data)
  1001  	}
  1002  
  1003  	framework.Logf("initializing nginx ingress controller")
  1004  	e2ekubectl.RunKubectlOrDieInput(cont.Ns, read("rc.yaml"), "create", "-f", "-")
  1005  
  1006  	rc, err := cont.Client.CoreV1().ReplicationControllers(cont.Ns).Get(ctx, "nginx-ingress-controller", metav1.GetOptions{})
  1007  	framework.ExpectNoError(err)
  1008  	cont.rc = rc
  1009  
  1010  	framework.Logf("waiting for pods with label %v", rc.Spec.Selector)
  1011  	sel := labels.SelectorFromSet(labels.Set(rc.Spec.Selector))
  1012  	framework.ExpectNoError(testutils.WaitForPodsWithLabelRunning(cont.Client, cont.Ns, sel))
  1013  	pods, err := cont.Client.CoreV1().Pods(cont.Ns).List(ctx, metav1.ListOptions{LabelSelector: sel.String()})
  1014  	framework.ExpectNoError(err)
  1015  	if len(pods.Items) == 0 {
  1016  		framework.Failf("Failed to find nginx ingress controller pods with selector %v", sel)
  1017  	}
  1018  	cont.pod = &pods.Items[0]
  1019  	framework.Logf("ingress controller running in pod %v", cont.pod.Name)
  1020  }
  1021  
  1022  // TearDown cleans up the NginxIngressController.
  1023  func (cont *NginxIngressController) TearDown(ctx context.Context) {
  1024  	if cont.lbSvc == nil {
  1025  		framework.Logf("No LoadBalancer service created, no cleanup necessary")
  1026  		return
  1027  	}
  1028  	e2eservice.WaitForServiceDeletedWithFinalizer(ctx, cont.Client, cont.Ns, cont.lbSvc.Name)
  1029  }
  1030  
  1031  func generateBacksideHTTPSIngressSpec(ns string) *networkingv1.Ingress {
  1032  	return &networkingv1.Ingress{
  1033  		ObjectMeta: metav1.ObjectMeta{
  1034  			Name:      "echoheaders-https",
  1035  			Namespace: ns,
  1036  		},
  1037  		Spec: networkingv1.IngressSpec{
  1038  			// Note kubemci requires a default backend.
  1039  			DefaultBackend: &networkingv1.IngressBackend{
  1040  				Service: &networkingv1.IngressServiceBackend{
  1041  					Name: "echoheaders-https",
  1042  					Port: networkingv1.ServiceBackendPort{
  1043  						Number: 443,
  1044  					},
  1045  				},
  1046  			},
  1047  		},
  1048  	}
  1049  }
  1050  
  1051  func generateBacksideHTTPSServiceSpec() *v1.Service {
  1052  	return &v1.Service{
  1053  		ObjectMeta: metav1.ObjectMeta{
  1054  			Name: "echoheaders-https",
  1055  			Annotations: map[string]string{
  1056  				ServiceApplicationProtocolKey: `{"my-https-port":"HTTPS"}`,
  1057  			},
  1058  		},
  1059  		Spec: v1.ServiceSpec{
  1060  			Ports: []v1.ServicePort{{
  1061  				Name:       "my-https-port",
  1062  				Protocol:   v1.ProtocolTCP,
  1063  				Port:       443,
  1064  				TargetPort: intstr.FromString("echo-443"),
  1065  			}},
  1066  			Selector: map[string]string{
  1067  				"app": "echoheaders-https",
  1068  			},
  1069  			Type: v1.ServiceTypeNodePort,
  1070  		},
  1071  	}
  1072  }
  1073  
  1074  func generateBacksideHTTPSDeploymentSpec() *appsv1.Deployment {
  1075  	labels := map[string]string{"app": "echoheaders-https"}
  1076  	d := e2edeployment.NewDeployment("echoheaders-https", 0, labels, "echoheaders-https", imageutils.GetE2EImage(imageutils.Agnhost), appsv1.RollingUpdateDeploymentStrategyType)
  1077  	d.Spec.Replicas = nil
  1078  	d.Spec.Template.Spec.Containers[0].Command = []string{
  1079  		"/agnhost",
  1080  		"netexec",
  1081  		"--http-port=8443",
  1082  		"--tls-cert-file=/localhost.crt",
  1083  		"--tls-private-key-file=/localhost.key",
  1084  	}
  1085  	d.Spec.Template.Spec.Containers[0].Ports = []v1.ContainerPort{{
  1086  		ContainerPort: 8443,
  1087  		Name:          "echo-443",
  1088  	}}
  1089  	return d
  1090  }
  1091  
  1092  // SetUpBacksideHTTPSIngress sets up deployment, service and ingress with backside HTTPS configured.
  1093  func (j *TestJig) SetUpBacksideHTTPSIngress(ctx context.Context, cs clientset.Interface, namespace string, staticIPName string) (*appsv1.Deployment, *v1.Service, *networkingv1.Ingress, error) {
  1094  	deployCreated, err := cs.AppsV1().Deployments(namespace).Create(ctx, generateBacksideHTTPSDeploymentSpec(), metav1.CreateOptions{})
  1095  	if err != nil {
  1096  		return nil, nil, nil, err
  1097  	}
  1098  	svcCreated, err := cs.CoreV1().Services(namespace).Create(ctx, generateBacksideHTTPSServiceSpec(), metav1.CreateOptions{})
  1099  	if err != nil {
  1100  		return nil, nil, nil, err
  1101  	}
  1102  	ingToCreate := generateBacksideHTTPSIngressSpec(namespace)
  1103  	if staticIPName != "" {
  1104  		if ingToCreate.Annotations == nil {
  1105  			ingToCreate.Annotations = map[string]string{}
  1106  		}
  1107  		ingToCreate.Annotations[IngressStaticIPKey] = staticIPName
  1108  	}
  1109  	ingCreated, err := j.runCreate(ctx, ingToCreate)
  1110  	if err != nil {
  1111  		return nil, nil, nil, err
  1112  	}
  1113  	return deployCreated, svcCreated, ingCreated, nil
  1114  }
  1115  
  1116  // DeleteTestResource deletes given deployment, service and ingress.
  1117  func (j *TestJig) DeleteTestResource(ctx context.Context, cs clientset.Interface, deploy *appsv1.Deployment, svc *v1.Service, ing *networkingv1.Ingress) []error {
  1118  	var errs []error
  1119  	if ing != nil {
  1120  		if err := j.runDelete(ctx, ing); err != nil {
  1121  			errs = append(errs, fmt.Errorf("error while deleting ingress %s/%s: %w", ing.Namespace, ing.Name, err))
  1122  		}
  1123  	}
  1124  	if svc != nil {
  1125  		if err := cs.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}); err != nil {
  1126  			errs = append(errs, fmt.Errorf("error while deleting service %s/%s: %w", svc.Namespace, svc.Name, err))
  1127  		}
  1128  	}
  1129  	if deploy != nil {
  1130  		if err := cs.AppsV1().Deployments(deploy.Namespace).Delete(ctx, deploy.Name, metav1.DeleteOptions{}); err != nil {
  1131  			errs = append(errs, fmt.Errorf("error while deleting deployment %s/%s: %w", deploy.Namespace, deploy.Name, err))
  1132  		}
  1133  	}
  1134  	return errs
  1135  }