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