-
Notifications
You must be signed in to change notification settings - Fork 223
NE-1273: Add a watch to the ingress operator so it will recreate the gwapi crds #1106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,17 +80,24 @@ func TestGatewayAPI(t *testing.T) { | |
| // testGatewayAPIResources tests that Gateway API Custom Resource Definitions are available. | ||
| // It specifically verifies that when the GatewayAPI feature gate is enabled, that the Gateway API | ||
| // CRDs are created. | ||
| // It also deletes and ensure the CRDs are recreated. | ||
| func testGatewayAPIResources(t *testing.T) { | ||
| t.Helper() | ||
| // Make sure all the *.gateway.networking.k8s.io CRDs are available since the FeatureGate is enabled. | ||
| ensureCRDs(t) | ||
|
|
||
| // Deleting CRDs to ensure they gets recreated again | ||
| deleteCRDs(t) | ||
|
|
||
| // Make sure all the *.gateway.networking.k8s.io CRDs are available since they should be recreated after manual deletion. | ||
| ensureCRDs(t) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for taking on the PR review! Since they were two different efforts/tasks, would you mind breaking apart the E2E fixes from the Candace's PR code review and your new additional E2E changes for NE-1273. It will help with clarity in documentation and traceability, and future reverts (if ever needed). And in your commit message of the E2E fixes commit, mind putting a link in for #1023 to reference the PR where the code review changes are being addressed from? |
||
| } | ||
|
|
||
| // testGatewayAPIIstioInstallation tests that once the Gateway API Custom Resource GatewayClass is created, that | ||
| // the following installation operations complete automatically and successfully: | ||
| // - the required Subscription and CatalogSource are created. | ||
| // - the OSSM Istio operator is installed successfully and has status Running and Ready. e.g. istio-operator-9f5c88857-2xfrr -n openshift-operators | ||
| // - the Istiod proxy is installed successfully and has status Running and Ready. e.g istiod-openshift-gateway-867bb8d5c7-4z6mp -n openshift-ingress | ||
| // - Istiod is installed successfully and has status Running and Ready. e.g istiod-openshift-gateway-867bb8d5c7-4z6mp -n openshift-ingress | ||
| // - the SMCP is created successfully (OSSM 2.x). | ||
| func testGatewayAPIIstioInstallation(t *testing.T) { | ||
| t.Helper() | ||
|
|
@@ -99,13 +106,13 @@ func testGatewayAPIIstioInstallation(t *testing.T) { | |
| t.Fatalf("failed to find expected Subscription %s: %v", expectedSubscriptionName, err) | ||
| } | ||
| if err := assertCatalogSource(t, expectedCatalogSourceNamespace, expectedCatalogSourceName); err != nil { | ||
| t.Fatalf("failed to find expected CatalogSource %s", expectedCatalogSourceName) | ||
| t.Fatalf("failed to find expected CatalogSource %s: %v", expectedCatalogSourceName, err) | ||
| } | ||
| if err := assertOSSMOperator(t); err != nil { | ||
| t.Fatal("failed to find expected Istio operator") | ||
| t.Fatalf("failed to find expected Istio operator: %v", err) | ||
| } | ||
| if err := assertIstiodControlPlane(t); err != nil { | ||
| t.Fatal("failed to find expected Istiod control plane") | ||
| t.Fatalf("failed to find expected Istiod control plane: %v", err) | ||
| } | ||
| // TODO - In OSSM 3.x the configuration object to check will be different. | ||
| if err := assertSMCP(t); err != nil { | ||
|
|
@@ -146,6 +153,17 @@ func ensureCRDs(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| // deleteCRDs deletes Gateway API custom resource definitions. | ||
| func deleteCRDs(t *testing.T) { | ||
| t.Helper() | ||
| for _, crdName := range crdNames { | ||
| err := deleteExistingCRD(t, crdName) | ||
| if err != nil { | ||
| t.Errorf("failed to delete crd %s: %v", crdName, err) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // ensureGatewayObjectCreation tests that gateway class, gateway, and http route objects can be created. | ||
| func ensureGatewayObjectCreation(ns *corev1.Namespace) error { | ||
| var domain string | ||
|
|
@@ -163,7 +181,7 @@ func ensureGatewayObjectCreation(ns *corev1.Namespace) error { | |
| if err != nil { | ||
| return fmt.Errorf("feature gate was enabled, but gateway object could not be created: %v", err) | ||
| } | ||
| // The gateway is cleaned up in testGatewayAPIObjects. | ||
| // The gateway is cleaned up in TestGatewayAPI. | ||
|
|
||
| hostname := names.SimpleNameGenerator.GenerateName("test-hostname-") | ||
| defaultRoutename = hostname + "." + domain | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,7 +39,7 @@ const ( | |
| openshiftOperatorsNamespace = "openshift-operators" | ||
| // openshiftIstioOperatorDeploymentName holds the expected istio-operator deployment name. | ||
| openshiftIstioOperatorDeploymentName = "istio-operator" | ||
| // openshiftIstiodDeploymentName holds the expected istiod proxy deployment name | ||
| // openshiftIstiodDeploymentName holds the expected istiod deployment name | ||
| openshiftIstiodDeploymentName = "istiod-openshift-gateway" | ||
| // openshiftSMCPName holds the expected OSSM ServiceMeshControlPlane name | ||
| openshiftSMCPName = "openshift-gateway" | ||
|
|
@@ -103,19 +103,63 @@ func assertCrdExists(t *testing.T, crdname string) (string, error) { | |
| return crdVersion, err | ||
| } | ||
|
|
||
| // deleteExistingCRD deletes if the CRD of the given name exists and returns an error if not. | ||
| func deleteExistingCRD(t *testing.T, crdName string) error { | ||
| t.Helper() | ||
| crd := &apiextensionsv1.CustomResourceDefinition{} | ||
| newCRD := &apiextensionsv1.CustomResourceDefinition{} | ||
| name := types.NamespacedName{Namespace: "", Name: crdName} | ||
|
|
||
| err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 30*time.Second, false, func(context context.Context) (bool, error) { | ||
| if err := kclient.Get(context, name, crd); err != nil { | ||
| t.Logf("failed to get crd %s: %v", name, err) | ||
| return false, nil | ||
| } | ||
| return true, nil | ||
| }) | ||
| if err != nil { | ||
| t.Errorf("failed to get crd %s: %v", name, err) | ||
| return err | ||
| } | ||
| // deleting CRD. | ||
| err = kclient.Delete(context.Background(), crd) | ||
| if err != nil { | ||
| t.Errorf("failed to delete crd %s: %v", name, err) | ||
| return err | ||
| } | ||
|
Comment on lines
+125
to
+129
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this will mark the CRD for deletion, not-block, and continue on. Deletion can take a couple of seconds. A potential issue is that the CRD is still available, but being deleted, and the To be safe, I'd consider blocking here. One example of blocking or waiting for deletion is deleteIngressController.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gcs278 I have added the poll because the IngressController has multiple operands, which means it takes some time to delete. However, CRDs are deleted quite quickly. When I receive my first poll CRD, it gives me a new CRD with a different UUID, and it ends up stuck in a loop searching for a new CRD.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sorry, I might be confused, isn't this a good thing? Not sure why it'd get stuck in this loop. Looks like the test passed, are you saying it's not working correctly still or did you figure it out?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem here I am trying to address is that if I remove the code section for The problem will be the reconcile loop is creating the CRD again pretty quickly and when I am trying get the CRD in the poll section it is already been recreated, in that case the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yes, that sounds very reasonable, better to be precise to avoid race conditions. |
||
| err = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 1*time.Minute, false, func(ctx context.Context) (bool, error) { | ||
| if err := kclient.Get(ctx, name, newCRD); err != nil { | ||
| if kerrors.IsNotFound(err) { | ||
| return true, nil | ||
| } | ||
| t.Logf("failed to delete gatewayAPI CRD %s: %v", crdName, err) | ||
| return false, nil | ||
| } | ||
| // if new CRD got recreated while the poll ensures the CRD is deleted. | ||
| if newCRD != nil && newCRD.UID != crd.UID { | ||
| return true, nil | ||
| } | ||
| t.Logf("crd %s still exists", crdName) | ||
| return false, nil | ||
| }) | ||
| if err != nil { | ||
| return fmt.Errorf("timed out waiting for gatewayAPI CRD %s to be deleted: %v", crdName, err) | ||
| } | ||
| t.Logf("deleted crd %s", crdName) | ||
| return nil | ||
| } | ||
|
|
||
| // createHttpRoute checks if the HTTPRoute can be created. | ||
| // If it can't an error is returned. | ||
| func createHttpRoute(namespace, routename, parentnamespace, hostname, backendrefname string, gateway *gwapi.Gateway) (*gwapi.HTTPRoute, error) { | ||
| // Just in case gateway creation failed, supply a fake gateway name. | ||
| name := "NONE" | ||
| if gateway != nil { | ||
| name = gateway.Name | ||
| func createHttpRoute(namespace, routeName, parentNamespace, hostname, backendRefname string, gateway *gwapi.Gateway) (*gwapi.HTTPRoute, error) { | ||
| if gateway == nil { | ||
| return nil, errors.New("unable to create httpRoute, no gateway available") | ||
| } | ||
|
|
||
| // Create the backend (service and pod) needed for the route to have resolvedRefs=true. | ||
| // The http route, service, and pod are cleaned up when the namespace is automatically deleted. | ||
| // buildEchoPod builds a pod that listens on port 8080. | ||
| echoPod := buildEchoPod(backendrefname, namespace) | ||
| echoPod := buildEchoPod(backendRefname, namespace) | ||
| if err := kclient.Create(context.TODO(), echoPod); err != nil { | ||
| return nil, fmt.Errorf("failed to create pod %s/%s: %v", namespace, echoPod.Name, err) | ||
| } | ||
|
|
@@ -125,10 +169,10 @@ func createHttpRoute(namespace, routename, parentnamespace, hostname, backendref | |
| return nil, fmt.Errorf("failed to create service %s/%s: %v", echoService.Namespace, echoService.Name, err) | ||
| } | ||
|
|
||
| httpRoute := buildHTTPRoute(routename, namespace, name, parentnamespace, hostname, backendrefname) | ||
| httpRoute := buildHTTPRoute(routeName, namespace, gateway.Name, parentNamespace, hostname, backendRefname) | ||
| if err := kclient.Create(context.TODO(), httpRoute); err != nil { | ||
| if kerrors.IsAlreadyExists(err) { | ||
| name := types.NamespacedName{Namespace: namespace, Name: routename} | ||
| name := types.NamespacedName{Namespace: namespace, Name: routeName} | ||
| if err = kclient.Get(context.TODO(), name, httpRoute); err == nil { | ||
| return httpRoute, nil | ||
| } else { | ||
|
|
@@ -203,23 +247,23 @@ func buildGateway(name, namespace, gcname, fromNs, domain string) *gwapi.Gateway | |
| } | ||
|
|
||
| // buildHTTPRoute initializes the HTTPRoute and returns its address. | ||
| func buildHTTPRoute(routename, namespace, parentgateway, parentnamespace, hostname, backendrefname string) *gwapi.HTTPRoute { | ||
| parentns := gwapi.Namespace(parentnamespace) | ||
| func buildHTTPRoute(routeName, namespace, parentgateway, parentNamespace, hostname, backendRefname string) *gwapi.HTTPRoute { | ||
| parentns := gwapi.Namespace(parentNamespace) | ||
| parent := gwapi.ParentReference{Name: gwapi.ObjectName(parentgateway), Namespace: &parentns} | ||
| port := gwapi.PortNumber(defaultPortNumber) | ||
| rule := gwapi.HTTPRouteRule{ | ||
| BackendRefs: []gwapi.HTTPBackendRef{{ | ||
| BackendRef: gwapi.BackendRef{ | ||
| BackendObjectReference: gwapi.BackendObjectReference{ | ||
| Name: gwapi.ObjectName(backendrefname), | ||
| Name: gwapi.ObjectName(backendRefname), | ||
| Port: &port, | ||
| }, | ||
| }, | ||
| }}, | ||
| } | ||
|
|
||
| return &gwapi.HTTPRoute{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: routename, Namespace: namespace}, | ||
| ObjectMeta: metav1.ObjectMeta{Name: routeName, Namespace: namespace}, | ||
| Spec: gwapi.HTTPRouteSpec{ | ||
| CommonRouteSpec: gwapi.CommonRouteSpec{ParentRefs: []gwapi.ParentReference{parent}}, | ||
| Hostnames: []gwapi.Hostname{gwapi.Hostname(hostname)}, | ||
|
|
@@ -310,7 +354,7 @@ func assertIstiodControlPlane(t *testing.T) error { | |
| } | ||
| pod := podlist.Items[0] | ||
| if pod.Status.Phase != corev1.PodRunning { | ||
| return fmt.Errorf("Istiod proxy failure: pod %s is not running, it is %v", pod.Name, pod.Status.Phase) | ||
| return fmt.Errorf("Istiod failure: pod %s is not running, it is %v", pod.Name, pod.Status.Phase) | ||
| } | ||
|
|
||
| t.Logf("found istiod pod %s/%s to be %s", pod.Namespace, pod.Name, pod.Status.Phase) | ||
|
|
@@ -333,7 +377,7 @@ func assertGatewayClassSuccessful(t *testing.T, name string) (*gwapi.GatewayClas | |
| return false, nil | ||
| } | ||
| for _, condition := range gwc.Status.Conditions { | ||
| if condition.Type == string(gwapi.GatewayClassReasonAccepted) { | ||
| if condition.Type == string(gwapi.GatewayClassConditionStatusAccepted) { | ||
| recordedConditionMsg = condition.Message | ||
| if condition.Status == metav1.ConditionTrue { | ||
| return true, nil | ||
|
|
@@ -344,7 +388,7 @@ func assertGatewayClassSuccessful(t *testing.T, name string) (*gwapi.GatewayClas | |
| return false, nil | ||
| }) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("gateway class %s not %v, last recorded status message: %s", name, gwapi.GatewayClassReasonAccepted, recordedConditionMsg) | ||
| return nil, fmt.Errorf("gateway class %s not %v, last recorded status message: %s", name, gwapi.GatewayClassConditionStatusAccepted, recordedConditionMsg) | ||
| } | ||
|
|
||
| t.Logf("gateway class %s successful", name) | ||
|
|
@@ -366,7 +410,7 @@ func assertGatewaySuccessful(t *testing.T, namespace, name string) (*gwapi.Gatew | |
| return false, nil | ||
| } | ||
| for _, condition := range gw.Status.Conditions { | ||
| if condition.Type == string(gwapi.GatewayClassReasonAccepted) { // there is no GatewayReasonAccepted! | ||
| if condition.Type == string(gwapi.GatewayClassConditionStatusAccepted) { // TODO: Use GatewayConditionAccepted when updating to v1. | ||
| recordedConditionMsg = condition.Message | ||
| if condition.Status == metav1.ConditionTrue { | ||
| t.Logf("found gateway %s/%s as Accepted", namespace, name) | ||
|
|
@@ -377,7 +421,7 @@ func assertGatewaySuccessful(t *testing.T, namespace, name string) (*gwapi.Gatew | |
| return false, nil | ||
| }) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("gateway %s not %v, last recorded status message: %s", name, gwapi.GatewayClassReasonAccepted, recordedConditionMsg) | ||
| return nil, fmt.Errorf("gateway %s not %v, last recorded status message: %s", name, gwapi.GatewayClassConditionStatusAccepted, recordedConditionMsg) | ||
| } | ||
|
|
||
| return gw, nil | ||
|
|
@@ -601,10 +645,8 @@ func assertDNSRecord(t *testing.T, recordName types.NamespacedName) error { | |
| } | ||
| } | ||
| } | ||
| } else { | ||
| t.Logf("found DNSRecord %s/%s but could not determine its readiness. Retrying...", recordName.Namespace, recordName.Name) | ||
| return false, nil | ||
| } | ||
| t.Logf("found DNSRecord %s/%s but could not determine its readiness. Retrying...", recordName.Namespace, recordName.Name) | ||
| return false, nil | ||
| }) | ||
| return err | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.