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 }