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