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