diff --git a/test/extended/cpu_partitioning/pods.go b/test/extended/cpu_partitioning/pods.go index 08a1b24e399f..8b8046c6d36e 100644 --- a/test/extended/cpu_partitioning/pods.go +++ b/test/extended/cpu_partitioning/pods.go @@ -54,6 +54,8 @@ var ( "cloud-ingress-operator": {"openshift-cloud-ingress-operator"}, "managed-velero-operator": {"openshift-velero"}, "velero": {"openshift-velero"}, + + "gateway": {"openshift-ingress"}, } excludedBestEffortDaemonSets = map[string][]string{ @@ -107,6 +109,13 @@ var _ = g.Describe("[sig-node][apigroup:config.openshift.io] CPU Partitioning cl o.Expect(err).NotTo(o.HaveOccurred()) for _, deployment := range deployments.Items { + if deployment.Namespace == "openshift-ingress" && strings.HasPrefix(deployment.Name, "gateway-") { + // The gateway deployment's name contains a hash, which + // must be removed in order to be able to define an + // exception. Remove this if block when the + // corresponding exception is removed. + deployment.Name = "gateway" + } // If we find a deployment that is to be excluded from resource checks, we skip looking for their pods. if isExcluded(excludedBestEffortDeployments, deployment.Namespace, deployment.Name) { framework.Logf("skipping resource check on deployment (%s/%s) due to presence in BestEffort exclude list", deployment.Namespace, deployment.Name) diff --git a/test/extended/pods/priorityclasses.go b/test/extended/pods/priorityclasses.go index 31bdddbc8399..221c21d97602 100644 --- a/test/extended/pods/priorityclasses.go +++ b/test/extended/pods/priorityclasses.go @@ -37,6 +37,13 @@ var excludedPriorityClassPods = map[string][]string{ "openshift-operators": { "servicemesh-operator3-", }, + + // Istio does not provide an option to set priority class on gateway + // pods. https://issues.redhat.com/browse/OCPBUGS-54652 tracks setting + // the annotation so that we can remove this exclusion. + "openshift-ingress": { + "gateway-", + }, } var _ = Describe("[sig-arch] Managed cluster should", func() { diff --git a/test/extended/router/gatewayapicontroller.go b/test/extended/router/gatewayapicontroller.go index 449dec9d837d..4d77832a0b6f 100644 --- a/test/extended/router/gatewayapicontroller.go +++ b/test/extended/router/gatewayapicontroller.go @@ -3,9 +3,12 @@ package router import ( "context" "crypto/tls" + "errors" "fmt" "net" "net/http" + "sort" + "strconv" "strings" "time" @@ -18,56 +21,107 @@ import ( exutil "github.com/openshift/origin/test/extended/util" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" e2e "k8s.io/kubernetes/test/e2e/framework" admissionapi "k8s.io/pod-security-admission/api" + utilnet "k8s.io/utils/net" "k8s.io/utils/pointer" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/storage/names" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" ) -var ( - requiredCapabilities = []configv1.ClusterVersionCapability{ - configv1.ClusterVersionCapabilityMarketplace, - configv1.ClusterVersionCapabilityOperatorLifecycleManager, - } -) - const ( // Max time duration for the DNS resolution dnsResolutionTimeout = 10 * time.Minute // Max time duration for the Load balancer address loadBalancerReadyTimeout = 10 * time.Minute + // ingressNamespace is the name of the "openshift-ingress" operand + // namespace. + ingressNamespace = "openshift-ingress" + // istioName is the name of the Istio CR. + istioName = "openshift-gateway" + istiodDeployment = "istiod-openshift-gateway" + // The name of the default gatewayclass, which is used to install OSSM. + gatewayClassName = "openshift-default" + + // The expected OSSM subscription name. + expectedSubscriptionName = "servicemeshoperator3" + // The expected OSSM operator name. + serviceMeshOperatorName = expectedSubscriptionName + ".openshift-operators" + // Expected Subscription Source + expectedSubscriptionSource = "redhat-operators" + // The expected OSSM operator namespace. + expectedSubscriptionNamespace = "openshift-operators" + + gatewayClassCRDsReadyConditionType = "CRDsReady" + gatewayClassControllerInstalledConditionType = "ControllerInstalled" + + gatewayClassConditions = "gatewayclass-conditions" + gatewayClassFinalizer = "gatewayclass-finalizer" + noOLMResourcesPresent = "no-olm-resources" + istioCRDsManagedbyCIO = "cio-manages-istio" + istiodLabel = "istiod-label" + + ossmAndOLMResourcesCreated = "ensure-resources-are-created" + defaultGatewayclassAccepted = "ensure-default-gatewayclass-is-accepted" + customGatewayclassAccepted = "ensure-custom-gatewayclass-is-accepted" + lbAndServiceAndDnsrecordAreCreated = "ensure-lb-and-service-and-dnsrecord-are-created" + httprouteObjectCreated = "ensure-httproute-object-is-created" + gieEnabled = "ensure-gie-is-enabled" +) + +var ( + olmCapabilities = []configv1.ClusterVersionCapability{ + configv1.ClusterVersionCapabilityMarketplace, + configv1.ClusterVersionCapabilityOperatorLifecycleManager, + } + // testNames is a list of names that are used to track when tests are + // done in order to check whether it is safe to clean up resources that + // these tests share, such as the gatewayclass and Istio CR. These + // names are embedded within annotation keys of the form test-%s-done. + // Because annotation keys are limited to 63 characters, each of these + // names must be no longer than 53 characters. + testNames = []string{ + gatewayClassConditions, + gatewayClassFinalizer, + noOLMResourcesPresent, + istioCRDsManagedbyCIO, + istiodLabel, + ossmAndOLMResourcesCreated, + defaultGatewayclassAccepted, + customGatewayclassAccepted, + lbAndServiceAndDnsrecordAreCreated, + httprouteObjectCreated, + gieEnabled, + } ) var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io]", g.Ordered, g.Serial, func() { defer g.GinkgoRecover() var ( - oc = exutil.NewCLIWithPodSecurityLevel("gatewayapi-controller", admissionapi.LevelBaseline) - csvName string - err error - gateways []string + oc = exutil.NewCLIWithPodSecurityLevel("gatewayapi-controller", admissionapi.LevelBaseline) + csvName string + err error + gateways []string + infPoolCRD = "https://raw.githubusercontent.com/kubernetes-sigs/gateway-api-inference-extension/main/config/crd/bases/inference.networking.k8s.io_inferencepools.yaml" + managedDNS bool + loadBalancerSupported bool ) const ( - // The expected OSSM subscription name. - expectedSubscriptionName = "servicemeshoperator3" - // Expected Subscription Source - expectedSubscriptionSource = "redhat-operators" - // The expected OSSM operator namespace. - expectedSubscriptionNamespace = "openshift-operators" - // The gatewayclass name used to create ossm and other gateway api resources. - gatewayClassName = "openshift-default" // gatewayClassControllerName is the name that must be used to create a supported gatewayClass. gatewayClassControllerName = "openshift.io/gateway-controller/v1" //OSSM Deployment Pod Name - deploymentOSSMName = "servicemesh-operator3" + deploymentOSSMName = "servicemesh-operator3" + openshiftOperatorsNamespace = "openshift-operators" ) - g.BeforeAll(func() { + g.BeforeEach(func() { isokd, err := isOKD(oc) if err != nil { e2e.Failf("Failed to get clusterversion to determine if release is OKD: %v", err) @@ -76,36 +130,192 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat g.Skip("Skipping on OKD cluster as OSSM is not available as a community operator") } - // skip non clould platforms since gateway needs LB service - skipGatewayIfNonCloudPlatform(oc) - - // GatewayAPIController relies on OSSM OLM operator. - // Skipping on clusters which don't have capabilities required - // to install an OLM operator. - exutil.SkipIfMissingCapabilities(oc, requiredCapabilities...) + // Check platform support and get capabilities (LoadBalancer, DNS) + loadBalancerSupported, managedDNS = checkPlatformSupportAndGetCapabilities(oc) + if !isNoOLMFeatureGateEnabled(oc) { + // GatewayAPIController without GatewayAPIWithoutOLM featuregate + // relies on OSSM OLM operator. + // Skipping on clusters which don't have capabilities required + // to install an OLM operator. + exutil.SkipIfMissingCapabilities(oc, olmCapabilities...) + } // create the default gatewayClass gatewayClass := buildGatewayClass(gatewayClassName, gatewayClassControllerName) _, err = oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Create(context.TODO(), gatewayClass, metav1.CreateOptions{}) if err != nil && !apierrors.IsAlreadyExists(err) { - e2e.Failf("Failed to create GatewayClass %q", gatewayClassName) + e2e.Failf("Failed to create GatewayClass %q: %v", gatewayClassName, err) } }) - g.AfterAll(func() { - g.By("Cleaning up the GatewayAPI Objects") - for _, name := range gateways { - err = oc.AdminGatewayApiClient().GatewayV1().Gateways("openshift-ingress").Delete(context.Background(), name, metav1.DeleteOptions{}) - o.Expect(err).NotTo(o.HaveOccurred(), "Gateway %s could not be deleted", name) + g.AfterEach(func() { + if !checkAllTestsDone(oc) { + e2e.Logf("Skipping cleanup while not all GatewayAPIController tests are done") + } else { + g.By("Deleting the gateways") + + for _, name := range gateways { + err = oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Delete(context.Background(), name, metav1.DeleteOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "Gateway %s could not be deleted", name) + } + + g.By("Deleting the GatewayClass") + + if err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Delete(context.Background(), gatewayClassName, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { + e2e.Failf("Failed to delete GatewayClass %q", gatewayClassName) + } + if isNoOLMFeatureGateEnabled(oc) { + g.By("Waiting for the istiod pod to be deleted") + waitForIstiodPodDeletion(oc) + } else { + g.By("Deleting the Istio CR") + + // Explicitly deleting the Istio CR should not strictly be + // necessary; the Istio CR has an owner reference on the + // gatewayclass, and so deleting the gatewayclass should cause + // the garbage collector to delete the Istio CR. However, it + // has been observed that the Istio CR sometimes does not get + // deleted, and so we have an explicit delete command here just + // in case. The --ignore-not-found option should prevent errors + // if garbage collection has already deleted the object. + o.Expect(oc.AsAdmin().WithoutNamespace().Run("delete").Args("--ignore-not-found=true", "istio", istioName).Execute()).Should(o.Succeed()) + + g.By("Waiting for the istiod pod to be deleted") + waitForIstiodPodDeletion(oc) + + g.By("Deleting the OSSM Operator resources") + + gvr := schema.GroupVersionResource{ + Group: "operators.coreos.com", + Version: "v1", + Resource: "operators", + } + operator, err := oc.KubeFramework().DynamicClient.Resource(gvr).Get(context.Background(), serviceMeshOperatorName, metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get Operator %q", serviceMeshOperatorName) + + refs, ok, err := unstructured.NestedSlice(operator.Object, "status", "components", "refs") + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(ok).To(o.BeTrue(), "Failed to find status.components.refs in Operator %q", serviceMeshOperatorName) + restmapper := oc.AsAdmin().RESTMapper() + for _, ref := range refs { + ref := extractObjectReference(ref.(map[string]any)) + mapping, err := restmapper.RESTMapping(ref.GroupVersionKind().GroupKind()) + o.Expect(err).NotTo(o.HaveOccurred()) + + e2e.Logf("Deleting %s %s/%s...", ref.Kind, ref.Namespace, ref.Name) + err = oc.KubeFramework().DynamicClient.Resource(mapping.Resource).Namespace(ref.Namespace).Delete(context.Background(), ref.Name, metav1.DeleteOptions{}) + o.Expect(err).Should(o.Or(o.Not(o.HaveOccurred()), o.MatchError(apierrors.IsNotFound, "IsNotFound")), "Failed to delete %s %q: %v", ref.GroupVersionKind().Kind, ref.Name, err) + } + + o.Expect(oc.AsAdmin().WithoutNamespace().Run("delete").Args("operators", serviceMeshOperatorName).Execute()).Should(o.Succeed()) + + } } }) + g.It("[OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains CIO management conditions after creation", func() { + defer markTestDone(oc, gatewayClassConditions) + + g.By("Check if default GatewayClass is accepted") + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) + + g.By("Check the GatewayClass conditions to confirm OSSM is provisioned by CIO") + errCheck = checkGatewayClassCondition(oc, gatewayClassName, gatewayClassControllerInstalledConditionType, metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q does not have the ControllerInstalled condition", gatewayClassName) + + errCheck = checkGatewayClassCondition(oc, gatewayClassName, gatewayClassCRDsReadyConditionType, metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q does not have the CRDsReady condition", gatewayClassName) + }) + + g.It("[OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains sail finalizer after creation", func() { + defer markTestDone(oc, gatewayClassFinalizer) + + g.By("Check if default GatewayClass is accepted") + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) + + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) + + g.By("Confirm that the GatewayClass has the correct finalizer") + errCheck = checkGatewayClassFinalizer(oc, gatewayClassName, "openshift.io/ingress-operator-sail-finalizer") + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q does not have the finalizer", gatewayClassName) + }) + + g.It("[OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Sail operator resources are not installed", func() { + defer markTestDone(oc, noOLMResourcesPresent) + + g.By("Check if default GatewayClass is accepted") + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) + + g.By("Confirm that the Sail Operator Subscription, CSV and Deployment do not exist") + ensureSailOperatorResourceDoesNotExist(oc, "subscription") + ensureSailOperatorResourceDoesNotExist(oc, "csv") + ensureSailOperatorResourceDoesNotExist(oc, "deployment") + + g.By("Confirm there is no Istio CR present") + _, err = oc.AsAdmin().Run("get").Args("istio", istioName).Output() + o.Expect(err).To(o.HaveOccurred(), "Istio CR %q should not exist", istioName) + }) + + g.It("[OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Istio CRDs are managed by CIO and istiod deployment exists", func() { + defer markTestDone(oc, istioCRDsManagedbyCIO) + + g.By("Check if default GatewayClass is accepted") + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) + + g.By("Ensure the istiod Deployment is present and managed by helm") + errCheck = checkIstiodExists(oc, ingressNamespace, istiodDeployment) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "istiod deployment %s does not exist", istiodDeployment) + + g.By("Check the corresponding Istio CRDs are managed by CIO") + err := assertIstioCRDsOwnedByCIO(oc) + o.Expect(err).NotTo((o.HaveOccurred())) + }) + + g.It("[OCPFeatureGate:GatewayAPIWithoutOLM] Ensure istiod Deployment contains the correct label", func() { + defer markTestDone(oc, istiodLabel) + + g.By("Check if default GatewayClass is accepted") + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) + + g.By("Confirm the istiod Deployment contains the correct managed label") + waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, false, func(context context.Context) (bool, error) { + istiod, err := oc.AdminKubeClient().AppsV1().Deployments(ingressNamespace).Get(context, istiodDeployment, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get istiod deployment %q: %v; retrying...", istiodDeployment, err) + return false, nil + } + labels := istiod.ObjectMeta.Labels + e2e.Logf("the labels are %s", labels) + if value, ok := labels["managed-by"]; ok && value == "sail-library" { + e2e.Logf("The istiod deployment %q is managed by the sail library and has no sail-operator dependencies", istiodDeployment) + return true, nil + } + e2e.Logf("The istiod deployment %q does not have the label, retrying...", istiodDeployment) + return false, nil + }) + o.Expect(waitErr).NotTo(o.HaveOccurred(), "Timed out looking for the label in deployment %q", istiodDeployment) + + }) + g.It("Ensure OSSM and OLM related resources are created after creating GatewayClass", func() { + defer markTestDone(oc, ossmAndOLMResourcesCreated) + // these will fail since no OLM Resources will be available + if isNoOLMFeatureGateEnabled(oc) { + g.Skip("Skip this test since it requires OLM resources") + } + //check the catalogSource g.By("Check OLM catalogSource, subscription, CSV and Pod") waitCatalogErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 20*time.Minute, false, func(context context.Context) (bool, error) { catalog, err := oc.AsAdmin().Run("get").Args("-n", "openshift-marketplace", "catalogsource", expectedSubscriptionSource, "-o=jsonpath={.status.connectionState.lastObservedState}").Output() - o.Expect(err).NotTo(o.HaveOccurred()) + if err != nil { + e2e.Logf("Failed to get CatalogSource %q: %v; retrying...", expectedSubscriptionSource, err) + return false, nil + } if catalog != "READY" { e2e.Logf("CatalogSource %q is not in ready state, retrying...", expectedSubscriptionSource) return false, nil @@ -118,7 +328,10 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat // check Subscription waitVersionErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 20*time.Minute, false, func(context context.Context) (bool, error) { csvName, err = oc.AsAdmin().Run("get").Args("-n", expectedSubscriptionNamespace, "subscription", expectedSubscriptionName, "-o=jsonpath={.status.installedCSV}").Output() - o.Expect(err).NotTo(o.HaveOccurred()) + if err != nil { + e2e.Logf("Failed to get Subscription %q: %v; retrying...", expectedSubscriptionName, err) + return false, nil + } if csvName == "" { e2e.Logf("Subscription %q doesn't have installed CSV, retrying...", expectedSubscriptionName) return false, nil @@ -130,7 +343,10 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat waitCSVErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 20*time.Minute, false, func(context context.Context) (bool, error) { csvStatus, err := oc.AsAdmin().Run("get").Args("-n", expectedSubscriptionNamespace, "clusterserviceversion", csvName, "-o=jsonpath={.status.phase}").Output() - o.Expect(err).NotTo(o.HaveOccurred()) + if err != nil { + e2e.Logf("Failed to get ClusterServiceVersion %q: %v; retrying...", csvName, err) + return false, nil + } if csvStatus != "Succeeded" { e2e.Logf("Cluster Service Version %q is not successful, retrying...", csvName) return false, nil @@ -144,7 +360,7 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 20*time.Minute, false, func(context context.Context) (bool, error) { deployOSSM, err := oc.AdminKubeClient().AppsV1().Deployments(expectedSubscriptionNamespace).Get(context, "servicemesh-operator3", metav1.GetOptions{}) if err != nil { - e2e.Logf("Failed to get OSSM operator deployment %q, retrying...", deploymentOSSMName) + e2e.Logf("Failed to get OSSM operator deployment %q: %v; retrying...", deploymentOSSMName, err) return false, nil } if deployOSSM.Status.ReadyReplicas < 1 { @@ -161,42 +377,70 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat }) g.It("Ensure default gatewayclass is accepted", func() { + defer markTestDone(oc, defaultGatewayclassAccepted) g.By("Check if default GatewayClass is accepted after OLM resources are successful") - errCheck := checkGatewayClass(oc, gatewayClassName) + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) }) g.It("Ensure custom gatewayclass can be accepted", func() { + defer markTestDone(oc, customGatewayclassAccepted) + customGatewayClassName := "custom-gatewayclass" g.By("Create Custom GatewayClass") gatewayClass := buildGatewayClass(customGatewayClassName, gatewayClassControllerName) gwc, err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Create(context.TODO(), gatewayClass, metav1.CreateOptions{}) if err != nil { - e2e.Logf("Gateway Class \"custom-gatewayclass\" already exists, or has failed to be created, checking its status") + e2e.Logf("Failed to create GatewayClass %q: %v; checking its status...", customGatewayClassName, err) } - errCheck := checkGatewayClass(oc, customGatewayClassName) + errCheck := checkGatewayClassCondition(oc, customGatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gwc.Name) + if isNoOLMFeatureGateEnabled(oc) { + g.By("Check the GatewayClass conditions") + errCheck = checkGatewayClassCondition(oc, customGatewayClassName, gatewayClassControllerInstalledConditionType, metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q does not have the ControllerInstalled condition", customGatewayClassName) + + errCheck = checkGatewayClassCondition(oc, customGatewayClassName, gatewayClassCRDsReadyConditionType, metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q does not have the CRDsReady condition", customGatewayClassName) + + g.By("Confirm that the GatewayClass has the finalizer") + errCheck = checkGatewayClassFinalizer(oc, customGatewayClassName, "openshift.io/ingress-operator-sail-finalizer") + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q does not have the finalizer", customGatewayClassName) + } + g.By("Deleting Custom GatewayClass and confirming that it is no longer there") err = oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Delete(context.Background(), customGatewayClassName, metav1.DeleteOptions{}) o.Expect(err).NotTo(o.HaveOccurred()) - _, err = oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Get(context.Background(), customGatewayClassName, metav1.GetOptions{}) - o.Expect(err).To(o.HaveOccurred(), "The custom gatewayClass \"custom-gatewayclass\" has been sucessfully deleted") + // Wait for the GatewayClass to be fully deleted (finalizers processed) + o.Eventually(func() bool { + _, err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Get(context.Background(), customGatewayClassName, metav1.GetOptions{}) + return apierrors.IsNotFound(err) + }).WithTimeout(1*time.Minute).WithPolling(2*time.Second).Should(o.BeTrue(), "custom-gatewayclass should be deleted") - g.By("check if default gatewayClass is accepted and ISTIO CR and pod are still available") - defaultCheck := checkGatewayClass(oc, gatewayClassName) + g.By("check if default gatewayClass is accepted") + defaultCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) o.Expect(defaultCheck).NotTo(o.HaveOccurred()) - g.By("Confirm that ISTIO CR is created and in healthy state") - waitForIstioHealthy(oc) + if !isNoOLMFeatureGateEnabled(oc) { + g.By("Confirm that ISTIO CR is created and in healthy state") + waitForIstioHealthy(oc) + } + + g.By("Confirm that the istiod deployment still exists") + errIstio := checkIstiodExists(oc, ingressNamespace, istiodDeployment) + o.Expect(errIstio).NotTo(o.HaveOccurred(), "istiod deployment %s does not exist", istiodDeployment) + }) g.It("Ensure LB, service, and dnsRecord are created for a Gateway object", func() { + defer markTestDone(oc, lbAndServiceAndDnsrecordAreCreated) + g.By("Ensure default GatewayClass is accepted") - errCheck := checkGatewayClass(oc, gatewayClassName) + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) g.By("Getting the default domain") @@ -207,19 +451,25 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat g.By("Create the default Gateway") gw := names.SimpleNameGenerator.GenerateName("gateway-") gateways = append(gateways, gw) - _, gwerr := createAndCheckGateway(oc, gw, gatewayClassName, defaultDomain) + _, gwerr := createAndCheckGateway(oc, gw, gatewayClassName, defaultDomain, loadBalancerSupported) o.Expect(gwerr).NotTo(o.HaveOccurred(), "failed to create Gateway") g.By("Verify the gateway's LoadBalancer service and DNSRecords") - assertGatewayLoadbalancerReady(oc, gw, gw+"-openshift-default") + if loadBalancerSupported { + assertGatewayLoadbalancerReady(oc, gw, gw+"-openshift-default") + } // check the dns record is created and status of the published dnsrecord of all zones are True - assertDNSRecordStatus(oc, gw) + if managedDNS { + assertDNSRecordStatus(oc, gw) + } }) g.It("Ensure HTTPRoute object is created", func() { + defer markTestDone(oc, httprouteObjectCreated) + g.By("Ensure default GatewayClass is accepted") - errCheck := checkGatewayClass(oc, gatewayClassName) + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) g.By("Getting the default domain") @@ -230,11 +480,13 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat g.By("Create a custom Gateway for the HTTPRoute") gw := names.SimpleNameGenerator.GenerateName("gateway-") gateways = append(gateways, gw) - _, gwerr := createAndCheckGateway(oc, gw, gatewayClassName, customDomain) + _, gwerr := createAndCheckGateway(oc, gw, gatewayClassName, customDomain, loadBalancerSupported) o.Expect(gwerr).NotTo(o.HaveOccurred(), "Failed to create Gateway") - // make sure the DNSRecord is ready to use. - assertDNSRecordStatus(oc, gw) + // make sure the DNSRecord is ready to use + if managedDNS { + assertDNSRecordStatus(oc, gw) + } g.By("Create the http route using the custom gateway") defaultRoutename := "test-hostname." + customDomain @@ -244,11 +496,146 @@ var _ = g.Describe("[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feat assertHttpRouteSuccessful(oc, gw, "test-httproute") g.By("Validating the http connectivity to the backend application") - assertHttpRouteConnection(defaultRoutename) + if loadBalancerSupported && managedDNS { + assertHttpRouteConnection(defaultRoutename) + } + }) + + g.It("Ensure GIE is enabled after creating an inferencePool CRD", func() { + defer markTestDone(oc, gieEnabled) + + errCheck := checkGatewayClassCondition(oc, gatewayClassName, string(gatewayapiv1.GatewayClassConditionStatusAccepted), metav1.ConditionTrue) + o.Expect(errCheck).NotTo(o.HaveOccurred(), "GatewayClass %q was not installed and accepted", gatewayClassName) + + g.By("Install the GIE CRD") + err := oc.AsAdmin().Run("create").Args("-f", infPoolCRD).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + + g.By("Confirm istiod deployment contains the env variable") + // check the istiod deployment so this test can be ran with and without gatewayAPIWithoutOLM featuregate + waitIstioErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, false, func(context context.Context) (bool, error) { + istiod, err := oc.AdminKubeClient().AppsV1().Deployments(ingressNamespace).Get(context, istiodDeployment, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get istiod deployment %q: %v; retrying...", istiodDeployment, err) + return false, nil + } + envVar := istiod.Spec.Template.Spec.Containers[0].Env + for _, env := range envVar { + if env.Name == "ENABLE_GATEWAY_API_INFERENCE_EXTENSION" { + if env.Value == "true" { + e2e.Logf("GIE has been enabled, and the env variable is present in Istiod deployment resource") + return true, nil + } + } + } + e2e.Logf("GIE env variable is not present, retrying...") + return false, nil + }) + o.Expect(waitIstioErr).NotTo(o.HaveOccurred(), "Timed out waiting for Istiod Deployment to have GIE env variable") + + g.By("Uninstall the GIE CRD and confirm the env variable is removed") + err = oc.AsAdmin().Run("delete").Args("-f", infPoolCRD).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + waitIstioErr = wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, false, func(context context.Context) (bool, error) { + istiod, err := oc.AdminKubeClient().AppsV1().Deployments(ingressNamespace).Get(context, istiodDeployment, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get istiod deployment %q: %v; retrying...", istiodDeployment, err) + return false, nil + } + envVar := istiod.Spec.Template.Spec.Containers[0].Env + for _, env := range envVar { + if env.Name == "ENABLE_GATEWAY_API_INFERENCE_EXTENSION" { + e2e.Logf("GIE env variable is still present in Istiod deployment resource, retrying...") + return false, nil + } + } + e2e.Logf("GIE env variable has been removed from the Istio resource") + return true, nil + }) + o.Expect(waitIstioErr).NotTo(o.HaveOccurred(), "Timed out waiting for Istiod to remove GIE env variable") + }) + + g.It("Ensure istiod deployment and the istio could be deleted and then get recreated [Serial]", func() { + // delete the istiod deployment and then checked if it is restored + g.By(fmt.Sprintf("Try to delete the istiod deployment in %s namespace", ingressNamespace)) + pollWaitDeploymentReady(oc, ingressNamespace, istiodDeployment) + deployment, err := oc.AdminKubeClient().AppsV1().Deployments(ingressNamespace).Get(context.Background(), istiodDeployment, metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + err = oc.AdminKubeClient().AppsV1().Deployments(ingressNamespace).Delete(context.Background(), istiodDeployment, metav1.DeleteOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + + g.By(fmt.Sprintf("Wait until the istiod deployment in %s namespace is automatically created successfully", ingressNamespace)) + pollWaitDeploymentCreated(oc, ingressNamespace, istiodDeployment, deployment.CreationTimestamp) + + if !isNoOLMFeatureGateEnabled(oc) { + // delete the istio and check if it is restored + g.By(fmt.Sprintf("Try to delete the istio %s", istioName)) + istioOriginalCreatedTimestamp, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("-n", ingressNamespace, "istio/"+istioName, `-o=jsonpath={.metadata.creationTimestamp}`).Output() + o.Expect(err).NotTo(o.HaveOccurred()) + _, err = oc.AsAdmin().WithoutNamespace().Run("delete").Args("-n", ingressNamespace, "istio/"+istioName).Output() + o.Expect(err).NotTo(o.HaveOccurred()) + + g.By(fmt.Sprintf("Wait until the the istiod %s is automatically created successfully", istioName)) + pollWaitIstioCreated(oc, ingressNamespace, istioName, istioOriginalCreatedTimestamp) + } else { + e2e.Logf("Not checking the Istio CR, due to NO OLM featuregate being enabled") + } + + }) + + g.It("Ensure gateway loadbalancer service and dnsrecords could be deleted and then get recreated [Serial]", func() { + if !loadBalancerSupported || !managedDNS { + g.Skip("Skipping LoadBalancer and DNS deletion test - platform does not support these features") + } + + g.By("Getting the default domain for creating a custom Gateway") + defaultIngressDomain, err := getDefaultIngressClusterDomainName(oc, time.Minute) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to find default domain name") + customDomain := strings.Replace(defaultIngressDomain, "apps.", "gw-custom.", 1) + + g.By("Create a custom Gateway") + gw := names.SimpleNameGenerator.GenerateName("gateway-") + gateways = append(gateways, gw) + _, gwerr := createAndCheckGateway(oc, gw, gatewayClassName, customDomain, loadBalancerSupported) + o.Expect(gwerr).NotTo(o.HaveOccurred(), "Failed to create Gateway") + + // verify the gateway's LoadBalancer service + assertGatewayLoadbalancerReady(oc, gw, gw+"-openshift-default") + gatewayLbService := gw + "-openshift-default" + + // make sure the DNSRecord is ready to use. + assertDNSRecordStatus(oc, gw) + + g.By(fmt.Sprintf("Try to delete the gateway lb service %s", gatewayLbService)) + lbService, err := oc.AdminKubeClient().CoreV1().Services(ingressNamespace).Get(context.Background(), gatewayLbService, metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + err = oc.AdminKubeClient().CoreV1().Services(ingressNamespace).Delete(context.Background(), gatewayLbService, metav1.DeleteOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + + g.By(fmt.Sprintf("Wait until the gateway lb service %s is automatically recreated successfully", gatewayLbService)) + pollWaitGWLBServiceRecreated(oc, ingressNamespace, gatewayLbService, lbService.ObjectMeta.CreationTimestamp) + + // make sure the DNSRecord is ready to use. + assertDNSRecordStatus(oc, gw) + + // delete the gateway dnsrecords then checked if it is restored + g.By(fmt.Sprintf("Get some info of the gateway dnsrecords in %s namespace, then try to delete it", ingressNamespace)) + dnsrecordList, err := oc.AdminIngressClient().IngressV1().DNSRecords(ingressNamespace).List(context.Background(), metav1.ListOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + + dnsrecord, err := getGWDNSRecords(dnsrecordList, gw) + o.Expect(err).NotTo(o.HaveOccurred()) + err = oc.AdminIngressClient().IngressV1().DNSRecords(ingressNamespace).Delete(context.Background(), dnsrecord.ObjectMeta.Name, metav1.DeleteOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + + g.By(fmt.Sprintf("Wait unitl the gateway dnsrecords in %s namespace is automatically created successfully", ingressNamespace)) + pollWaitGWDNSRecordsRecreated(oc, gw, ingressNamespace, getSortedString(dnsrecord.Spec.Targets), dnsrecord.ObjectMeta.CreationTimestamp) }) }) -func skipGatewayIfNonCloudPlatform(oc *exutil.CLI) { +// checkPlatformSupportAndGetCapabilities verifies the platform is supported and returns +// platform capabilities for LoadBalancer services and managed DNS. +func checkPlatformSupportAndGetCapabilities(oc *exutil.CLI) (loadBalancerSupported bool, managedDNS bool) { infra, err := oc.AdminConfigClient().ConfigV1().Infrastructures().Get(context.Background(), "cluster", metav1.GetOptions{}) o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(infra).NotTo(o.BeNil()) @@ -258,47 +645,79 @@ func skipGatewayIfNonCloudPlatform(oc *exutil.CLI) { o.Expect(platformType).NotTo(o.BeEmpty()) switch platformType { case configv1.AWSPlatformType, configv1.AzurePlatformType, configv1.GCPPlatformType, configv1.IBMCloudPlatformType: - // supported + // Cloud platforms with native LoadBalancer support + loadBalancerSupported = true + case configv1.VSpherePlatformType, configv1.BareMetalPlatformType, configv1.EquinixMetalPlatformType: + // Platforms without native LoadBalancer support (may have MetalLB or similar) + loadBalancerSupported = false default: - g.Skip(fmt.Sprintf("Skipping on non cloud platform type %q", platformType)) + g.Skip(fmt.Sprintf("Skipping on unsupported platform type %q", platformType)) + } + + // Check if DNS is managed (has public or private zones configured) + managedDNS = isDNSManaged(oc) + + // Skip Gateway API tests on IPv6 or dual-stack clusters (any platform) + if isIPv6OrDualStack(oc) { + g.Skip("Skipping Gateway API tests on IPv6/dual-stack cluster") } + + e2e.Logf("Platform: %s, LoadBalancer supported: %t, DNS managed: %t", platformType, loadBalancerSupported, managedDNS) + return loadBalancerSupported, managedDNS } -func waitForIstioHealthy(oc *exutil.CLI) { - resource := types.NamespacedName{Namespace: "openshift-ingress", Name: "openshift-gateway"} - err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) { - istioStatus, errIstio := oc.AsAdmin().Run("get").Args("-n", resource.Namespace, "istio", resource.Name, "-o=jsonpath={.status.state}").Output() - o.Expect(errIstio).NotTo(o.HaveOccurred()) - if istioStatus != "Healthy" { - e2e.Logf("Istio CR %q is not healthy, retrying...", resource.Name) - return false, nil +// isDNSManaged checks if the cluster has DNS zones configured (public or private). +// On platforms like vSphere without external DNS, DNS records cannot be managed. +func isDNSManaged(oc *exutil.CLI) bool { + dnsConfig, err := oc.AdminConfigClient().ConfigV1().DNSes().Get(context.Background(), "cluster", metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get DNS config") + return dnsConfig.Spec.PrivateZone != nil || dnsConfig.Spec.PublicZone != nil +} + +// isIPv6OrDualStack checks if the cluster is using IPv6 or dual-stack networking. +// Returns true if any ServiceNetwork CIDR is IPv6 (indicates IPv6-only or dual-stack). +func isIPv6OrDualStack(oc *exutil.CLI) bool { + networkConfig, err := oc.AdminOperatorClient().OperatorV1().Networks().Get(context.Background(), "cluster", metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get network config") + + for _, cidr := range networkConfig.Spec.ServiceNetwork { + if utilnet.IsIPv6CIDRString(cidr) { + return true } - e2e.Logf("Istio CR %q is healthy", resource.Name) - return true, nil - }) - o.Expect(err).NotTo(o.HaveOccurred(), "Istio CR %q did not reach healthy state in time", resource.Name) + } + return false } -func checkGatewayClass(oc *exutil.CLI, name string) error { - waitErr := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) { - gwc, err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Get(context, name, metav1.GetOptions{}) - if err != nil { - e2e.Logf("Failed to get gatewayclass %s, retrying...", name) +func isNoOLMFeatureGateEnabled(oc *exutil.CLI) bool { + fgs, err := oc.AdminConfigClient().ConfigV1().FeatureGates().Get(context.TODO(), "cluster", metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred(), "Error getting cluster FeatureGates.") + for _, fg := range fgs.Status.FeatureGates { + for _, enabledFG := range fg.Enabled { + if enabledFG.Name == "GatewayAPIWithoutOLM" { + e2e.Logf("GatewayAPIWithoutOLM featuregate is enabled") + return true + } + } + } + return false +} + +func waitForIstioHealthy(oc *exutil.CLI) { + timeout := 20 * time.Minute + err := wait.PollUntilContextTimeout(context.Background(), 10*time.Second, timeout, false, func(context context.Context) (bool, error) { + istioStatus, errIstio := oc.AsAdmin().Run("get").Args("istio", istioName, "-o=jsonpath={.status.state}").Output() + if errIstio != nil { + e2e.Logf("Failed to get Istio CR %q: %v; retrying...", istioName, errIstio) return false, nil } - for _, condition := range gwc.Status.Conditions { - if condition.Type == string(gatewayapiv1.GatewayClassConditionStatusAccepted) { - if condition.Status == metav1.ConditionTrue { - return true, nil - } - } + if istioStatus != "Healthy" { + e2e.Logf("Istio CR %q is not healthy, retrying...", istioName) + return false, nil } - e2e.Logf("Found gatewayclass %s but it is not accepted, retrying...", name) - return false, nil + e2e.Logf("Istio CR %q is healthy", istioName) + return true, nil }) - - o.Expect(waitErr).NotTo(o.HaveOccurred(), "Gatewayclass %s is not accepted", name) - return nil + o.Expect(err).NotTo(o.HaveOccurred(), "Istio CR %q did not reach healthy state within %v", istioName, timeout) } // buildGatewayClass initializes the GatewayClass and returns its address. @@ -312,46 +731,53 @@ func buildGatewayClass(name, controllerName string) *gatewayapiv1.GatewayClass { } // createAndCheckGateway build and creates the Gateway. -func createAndCheckGateway(oc *exutil.CLI, gwname, gwclassname, domain string) (*gatewayapiv1.Gateway, error) { - ingressNameSpace := "openshift-ingress" - +func createAndCheckGateway(oc *exutil.CLI, gwname, gwclassname, domain string, loadBalancerSupported bool) (*gatewayapiv1.Gateway, error) { // Build the gateway object - gatewaybuild := buildGateway(gwname, ingressNameSpace, gwclassname, "All", domain) + gatewaybuild := buildGateway(gwname, ingressNamespace, gwclassname, "All", domain) // Create the gateway object - _, errGwObj := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNameSpace).Create(context.TODO(), gatewaybuild, metav1.CreateOptions{}) + _, errGwObj := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Create(context.TODO(), gatewaybuild, metav1.CreateOptions{}) if errGwObj != nil { return nil, errGwObj } // Confirm the gateway is up and running - return checkGatewayStatus(oc, gwname, ingressNameSpace) + return checkGatewayStatus(oc, gwname, ingressNamespace, loadBalancerSupported) } -func checkGatewayStatus(oc *exutil.CLI, gwname, ingressNameSpace string) (*gatewayapiv1.Gateway, error) { +func checkGatewayStatus(oc *exutil.CLI, gwname, ingressNameSpace string, loadBalancerSupported bool) (*gatewayapiv1.Gateway, error) { + // Determine which condition to wait for based on platform capabilities + // Without LoadBalancer support, Gateway reaches Accepted but not Programmed (reason: AddressNotAssigned) + var expectedCondition gatewayapiv1.GatewayConditionType + if loadBalancerSupported { + expectedCondition = gatewayapiv1.GatewayConditionProgrammed + } else { + expectedCondition = gatewayapiv1.GatewayConditionAccepted + } + programmedGateway := &gatewayapiv1.Gateway{} - if err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) { + timeout := 20 * time.Minute + if err := wait.PollUntilContextTimeout(context.Background(), 10*time.Second, timeout, false, func(context context.Context) (bool, error) { gateway, err := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNameSpace).Get(context, gwname, metav1.GetOptions{}) if err != nil { e2e.Logf("Failed to get gateway %q: %v, retrying...", gwname, err) return false, nil } - // Checking the gateway controller status for _, condition := range gateway.Status.Conditions { - if condition.Type == string(gatewayapiv1.GatewayConditionProgrammed) { + if condition.Type == string(expectedCondition) { if condition.Status == metav1.ConditionTrue { - e2e.Logf("The gateway controller for gateway %q is programmed", gwname) + e2e.Logf("Gateway %q has condition %s=True", gwname, expectedCondition) programmedGateway = gateway return true, nil } } } - e2e.Logf("Found gateway %q but the controller is still not programmed, retrying...", gwname) + e2e.Logf("Found gateway %q but condition %s is not yet True, retrying...", gwname, expectedCondition) return false, nil }); err != nil { - return nil, fmt.Errorf("timed out waiting for gateway %q to become programmed: %w", gwname, err) + return nil, fmt.Errorf("timed out after %v waiting for gateway %q to have condition %s=True: %w", timeout, gwname, expectedCondition, err) } - e2e.Logf("Gateway %q successfully programmed!", gwname) + e2e.Logf("Gateway %q successfully has condition %s=True", gwname, expectedCondition) return programmedGateway, nil } @@ -377,11 +803,15 @@ func assertGatewayLoadbalancerReady(oc *exutil.CLI, gwName, gwServiceName string // check gateway LB service, note that External-IP might be hostname (AWS) or IP (Azure/GCP) var lbAddress string err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, loadBalancerReadyTimeout, false, func(context context.Context) (bool, error) { - lbService, err := oc.AdminKubeClient().CoreV1().Services("openshift-ingress").Get(context, gwServiceName, metav1.GetOptions{}) + lbService, err := oc.AdminKubeClient().CoreV1().Services(ingressNamespace).Get(context, gwServiceName, metav1.GetOptions{}) if err != nil { e2e.Logf("Failed to get service %q: %v, retrying...", gwServiceName, err) return false, nil } + if len(lbService.Status.LoadBalancer.Ingress) == 0 { + e2e.Logf("Service %q has no load balancer; retrying...", gwServiceName) + return false, nil + } if lbService.Status.LoadBalancer.Ingress[0].Hostname != "" { lbAddress = lbService.Status.LoadBalancer.Ingress[0].Hostname } else { @@ -393,9 +823,9 @@ func assertGatewayLoadbalancerReady(oc *exutil.CLI, gwName, gwServiceName string } e2e.Logf("Got load balancer address for service %q: %v", gwServiceName, lbAddress) - gw, err := oc.AdminGatewayApiClient().GatewayV1().Gateways("openshift-ingress").Get(context, gwName, metav1.GetOptions{}) + gw, err := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Get(context, gwName, metav1.GetOptions{}) if err != nil { - e2e.Logf("Failed to get gateway %q, retrying...", gwName) + e2e.Logf("Failed to get gateway %q: %v; retrying...", err, gwName) return false, nil } for _, gwAddr := range gw.Status.Addresses { @@ -415,7 +845,7 @@ func assertDNSRecordStatus(oc *exutil.CLI, gatewayName string) { // find the DNS Record and confirm its zone status is True err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 10*time.Minute, false, func(context context.Context) (bool, error) { gatewayDNSRecord := &operatoringressv1.DNSRecord{} - gatewayDNSRecords, err := oc.AdminIngressClient().IngressV1().DNSRecords("openshift-ingress").List(context, metav1.ListOptions{}) + gatewayDNSRecords, err := oc.AdminIngressClient().IngressV1().DNSRecords(ingressNamespace).List(context, metav1.ListOptions{}) if err != nil { e2e.Logf("Failed to list DNS records for gateway %q: %v, retrying...", gatewayName, err) return false, nil @@ -447,8 +877,7 @@ func assertDNSRecordStatus(oc *exutil.CLI, gatewayName string) { // If it can't an error is returned. func createHttpRoute(oc *exutil.CLI, gwName, routeName, hostname, backendRefname string) (*gatewayapiv1.HTTPRoute, error) { namespace := oc.Namespace() - ingressNameSpace := "openshift-ingress" - gateway, errGwStatus := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNameSpace).Get(context.TODO(), gwName, metav1.GetOptions{}) + gateway, errGwStatus := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Get(context.TODO(), gwName, metav1.GetOptions{}) if errGwStatus != nil || gateway == nil { e2e.Failf("Unable to create httpRoute, no gateway available during route assertion %v", errGwStatus) } @@ -467,18 +896,23 @@ func createHttpRoute(oc *exutil.CLI, gwName, routeName, hostname, backendRefname o.Expect(echoServiceErr).NotTo(o.HaveOccurred()) // Create the HTTPRoute - buildHTTPRoute := buildHTTPRoute(routeName, namespace, gateway.Name, ingressNameSpace, hostname, backendRefname) + buildHTTPRoute := buildHTTPRoute(routeName, namespace, gateway.Name, ingressNamespace, hostname, backendRefname) httpRoute, err := oc.GatewayApiClient().GatewayV1().HTTPRoutes(namespace).Create(context.Background(), buildHTTPRoute, metav1.CreateOptions{}) o.Expect(err).NotTo(o.HaveOccurred()) // Confirm the HTTPRoute is up waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 4*time.Minute, false, func(context context.Context) (bool, error) { checkHttpRoute, err := oc.GatewayApiClient().GatewayV1().HTTPRoutes(namespace).Get(context, httpRoute.Name, metav1.GetOptions{}) - o.Expect(err).NotTo(o.HaveOccurred()) - for _, condition := range checkHttpRoute.Status.Parents[0].Conditions { - if condition.Type == string(gatewayapiv1.RouteConditionAccepted) { - if condition.Status == metav1.ConditionTrue { - return true, nil + if err != nil { + e2e.Logf("Failed to get HTTPRoute %q: %v; retrying...", httpRoute.Name, err) + return false, nil + } + if len(checkHttpRoute.Status.Parents) > 0 { + for _, condition := range checkHttpRoute.Status.Parents[0].Conditions { + if condition.Type == string(gatewayapiv1.RouteConditionAccepted) { + if condition.Status == metav1.ConditionTrue { + return true, nil + } } } } @@ -586,8 +1020,7 @@ func buildHTTPRoute(routeName, namespace, parentgateway, parentNamespace, hostna func assertHttpRouteSuccessful(oc *exutil.CLI, gwName, name string) (*gatewayapiv1.HTTPRoute, error) { namespace := oc.Namespace() checkHttpRoute := &gatewayapiv1.HTTPRoute{} - ingressNameSpace := "openshift-ingress" - gateway, errGwStatus := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNameSpace).Get(context.TODO(), gwName, metav1.GetOptions{}) + gateway, errGwStatus := oc.AdminGatewayApiClient().GatewayV1().Gateways(ingressNamespace).Get(context.TODO(), gwName, metav1.GetOptions{}) if errGwStatus != nil || gateway == nil { e2e.Failf("Unable to assert httproute, no gateway available, error %v", errGwStatus) } @@ -595,7 +1028,10 @@ func assertHttpRouteSuccessful(oc *exutil.CLI, gwName, name string) (*gatewayapi // Wait up to 4 minutes for parent(s) to update. err := wait.PollUntilContextTimeout(context.Background(), 2*time.Second, 4*time.Minute, false, func(context context.Context) (bool, error) { checkHttpRoute, err := oc.GatewayApiClient().GatewayV1().HTTPRoutes(namespace).Get(context, name, metav1.GetOptions{}) - o.Expect(err).NotTo(o.HaveOccurred()) + if err != nil { + e2e.Logf("Failed to get HTTPRoute %s/%s: %v; retrying...", namespace, name, err) + return false, nil + } numParents := len(checkHttpRoute.Status.Parents) if numParents == 0 { @@ -673,3 +1109,352 @@ func isOKD(oc *exutil.CLI) (bool, error) { } return false, nil } + +// annotationKeyForTest returns the key for an annotation on the default +// gatewayclass that indicates whether the specified test is done. +func annotationKeyForTest(testName string) string { + return fmt.Sprintf("test-%s-done", testName) +} + +// markTestDone adds an annotation to the default gatewayclass that all the +// GatewayAPIController tests use to indicate that a particular test has ended. +// These annotations are used to determine whether it is safe to clean up the +// gatewayclass and other shared resources. +func markTestDone(oc *exutil.CLI, testName string) { + gwc, err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Get(context.Background(), gatewayClassName, metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + + if gwc.Annotations == nil { + gwc.Annotations = map[string]string{} + } + gwc.Annotations[annotationKeyForTest(testName)] = "" + _, err = oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Update(context.Background(), gwc, metav1.UpdateOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) +} + +// checkAllTestsDone checks the annotations on the default gatewayclass that all +// the GatewayAPIController tests use to determine whether all the tests are +// done. +func checkAllTestsDone(oc *exutil.CLI) bool { + gwc, err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Get(context.Background(), gatewayClassName, metav1.GetOptions{}) + o.Expect(err).NotTo(o.HaveOccurred()) + + for _, testName := range testNames { + if _, ok := gwc.Annotations[annotationKeyForTest(testName)]; !ok { + return false + } + } + + return true +} + +// getNestedString returns a string value of a nested field value of an +// unstructured.Unstructured object. If the named field is of a non-string +// type, getNestedString returns the empty string. +func getNestedString(obj map[string]any, field string) string { + val, found, err := unstructured.NestedString(obj, field) + if !found || err != nil { + return "" + } + return val +} + +// extractObjectReference returns a ObjectReference value of a nested field +// value of an unstructured.Unstructured object. +func extractObjectReference(v map[string]any) corev1.ObjectReference { + return corev1.ObjectReference{ + Kind: getNestedString(v, "kind"), + Namespace: getNestedString(v, "namespace"), + Name: getNestedString(v, "name"), + UID: types.UID(getNestedString(v, "uid")), + APIVersion: getNestedString(v, "apiVersion"), + ResourceVersion: getNestedString(v, "resourceVersion"), + FieldPath: getNestedString(v, "fieldPath"), + } +} + +// used to wait for a deployment is ready +func pollWaitDeploymentReady(oc *exutil.CLI, ns, deploymentName string) { + err := wait.Poll(3*time.Second, 300*time.Second, func() (bool, error) { + deployment, err := oc.AdminKubeClient().AppsV1().Deployments(ns).Get(context.Background(), deploymentName, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get %q deployment: %v, retrying...", deploymentName, err) + return false, nil + } + + if readyReplicas := deployment.Status.ReadyReplicas; readyReplicas < 1 { + e2e.Logf(`The deployment %s in %s namespace is not ready(ReadyReplicas: %v), retrying...`, deploymentName, ns, readyReplicas) + return false, nil + } + + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred()) +} + +// used to wait for a deployment is automatically recreated +func pollWaitDeploymentCreated(oc *exutil.CLI, ns, deploymentName string, originalCreatedTime metav1.Time) { + err := wait.Poll(3*time.Second, 300*time.Second, func() (bool, error) { + deployment, err := oc.AdminKubeClient().AppsV1().Deployments(ns).Get(context.Background(), deploymentName, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get %q deployment: %v, retrying...", deploymentName, err) + return false, nil + } + + if deployment.CreationTimestamp == originalCreatedTime { + e2e.Logf("Orignal deployment %q in namespace %q is not deleted yet, retrying...", deploymentName, ns) + return false, nil + } + + if readyReplicas := deployment.Status.ReadyReplicas; readyReplicas < 1 { + e2e.Logf(`The deployment %s in %s namespace is not ready(ReadyReplicas: %v), retrying...`, deploymentName, ns, readyReplicas) + return false, nil + } + + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred()) +} + +// used to wait for the istio is created successfully by checking its readyReplicas +func pollWaitIstioCreated(oc *exutil.CLI, ingressNamespace, istioName, originalCreatedTimestamp string) { + err := wait.Poll(3*time.Second, 300*time.Second, func() (bool, error) { + readyReplicasStr, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("-n", ingressNamespace, "istio/"+istioName, `-o=jsonpath={.status.revisions.ready}`).Output() + if err != nil { + e2e.Logf("Failed to check istio %q, error: %v, retrying...", istioName, err) + return false, nil + } + + readyReplicas, err := strconv.Atoi(readyReplicasStr) + if err != nil { + e2e.Logf("Failed to convert readyReplicasStr %q to int, error: %v, retrying...", readyReplicasStr, err) + return false, nil + } + + if readyReplicas < 1 { + e2e.Logf("No ready replicas found for istio %q", istioName) + return false, nil + } + + currentCreatedTimestamp, err := oc.AsAdmin().WithoutNamespace().Run("get").Args("-n", ingressNamespace, "istio/"+istioName, `-o=jsonpath={.metadata.creationTimestamp}`).Output() + if err != nil { + e2e.Logf("Failed to check istio %q, error: %v, retrying...", istioName, err) + return false, nil + } + + if currentCreatedTimestamp == originalCreatedTimestamp { + e2e.Logf("Original istio %q in namespace %q is not deleted yet, retrying...", istioName, ingressNamespace) + return false, nil + } + + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred()) +} + +// used to wait for the gateway lb service is automatically recreated successfully +func pollWaitGWLBServiceRecreated(oc *exutil.CLI, ingressNamespace, gatewayLbService string, originalCreatedTime metav1.Time) { + var lbAddress string + err := wait.Poll(3*time.Second, 300*time.Second, func() (bool, error) { + lbService, err := oc.AdminKubeClient().CoreV1().Services(ingressNamespace).Get(context.Background(), gatewayLbService, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get the gateway lb service %q: %v, retrying...", gatewayLbService, err) + return false, nil + } + + if lbService.ObjectMeta.CreationTimestamp == originalCreatedTime { + e2e.Logf("Original gateway lb service %q is not deleted yet, retrying...", gatewayLbService) + return false, nil + } + + if len(lbService.Status.LoadBalancer.Ingress) == 0 { + e2e.Logf("New gateway lb service %q is created, but without lb hostname or ip, retrying...", gatewayLbService) + return false, nil + } + + if lbService.Status.LoadBalancer.Ingress[0].Hostname != "" { + lbAddress = lbService.Status.LoadBalancer.Ingress[0].Hostname + } else { + lbAddress = lbService.Status.LoadBalancer.Ingress[0].IP + } + if lbAddress == "" { + e2e.Logf("No load balancer address for service %q, retrying...", gatewayLbService) + return false, nil + } + + e2e.Logf("Got load balancer address for service %q: %v", gatewayLbService, lbAddress) + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred()) +} + +// used to get a gateway dnsrecord from a given dnsrecordList +func getGWDNSRecords(dnsrecordList *operatoringressv1.DNSRecordList, gwName string) (operatoringressv1.DNSRecord, error) { + for _, dnsrecord := range dnsrecordList.Items { + if strings.Contains(dnsrecord.ObjectMeta.Name, gwName+"-") { + return dnsrecord, nil + } + } + + return operatoringressv1.DNSRecord{}, errors.New("Could not get the name of the gw dnsrecord") +} + +// used to wait for the gateway dnsrecord is automatically recreated successfully +func pollWaitGWDNSRecordsRecreated(oc *exutil.CLI, gwName, ingressNamespace, expectedtargets string, originalCreatedTime metav1.Time) { + err := wait.Poll(3*time.Second, 300*time.Second, func() (bool, error) { + dnsrecordList, err := oc.AdminIngressClient().IngressV1().DNSRecords(ingressNamespace).List(context.Background(), metav1.ListOptions{}) + if err != nil { + e2e.Logf("Failed to List DNSRecords in namespace %q: %v, retrying...", ingressNamespace, err) + return false, nil + } + + dnsrecord, err := getGWDNSRecords(dnsrecordList, gwName) + if err != nil { + e2e.Logf("Failed to get the DNSRecord name for gateway %q in namespace %q: %v, retrying...", gwName, ingressNamespace, err) + return false, nil + } + + if dnsrecord.ObjectMeta.CreationTimestamp == originalCreatedTime { + e2e.Logf("Original DNSRecord of GW %q is not deleted yet, retrying...", gwName) + return false, nil + } + + currentTargets := getSortedString(dnsrecord.Spec.Targets) + if currentTargets != expectedtargets { + e2e.Logf("Current DNSRecord targets %q for gateway %q differ from expected %q, retrying...", currentTargets, gwName, expectedtargets) + return false, nil + } + + for _, zone := range dnsrecord.Status.Zones { + for _, condition := range zone.Conditions { + if condition.Type == "Published" && condition.Status != "True" { + e2e.Logf(`DNSRecord %q is not published in zone %q, retrying...`, dnsrecord.Name, zone) + return false, nil + } + } + } + return true, nil + }) + o.Expect(err).NotTo(o.HaveOccurred()) +} + +// used to sort string type of slice or string which can be split to the slice by the space character +func getSortedString(obj interface{}) string { + objList := []string{} + str, ok := obj.(string) + if ok { + objList = strings.Split(str, " ") + } + strList, ok := obj.([]string) + if ok { + objList = strList + } + sort.Strings(objList) + return strings.Join(objList, " ") +} + +func waitForIstiodPodDeletion(oc *exutil.CLI) { + o.Eventually(func(g o.Gomega) { + podsList, err := oc.AdminKubeClient().CoreV1().Pods(ingressNamespace).List(context.Background(), metav1.ListOptions{LabelSelector: "app=istiod"}) + g.Expect(err).NotTo(o.HaveOccurred()) + g.Expect(podsList.Items).Should(o.BeEmpty()) + }).WithTimeout(10 * time.Minute).WithPolling(10 * time.Second).Should(o.Succeed()) +} + +func checkIstiodExists(oc *exutil.CLI, namespace string, name string) error { + waitErr := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, 5*time.Minute, false, func(context context.Context) (bool, error) { + istiod, err := oc.AdminKubeClient().AppsV1().Deployments(namespace).Get(context, name, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get istiod deployment %q: %v; retrying...", name, err) + return false, nil + } + e2e.Logf("Successfully found the istiod Deployment: %s", istiod) + return true, nil + }) + o.Expect(waitErr).NotTo(o.HaveOccurred(), "Timed out looking for the deployment %q", name) + return nil +} + +// ensureSailOperatorResourceDoesNotExist checks that no Sail Operator resources of the given type exist +// by querying for resources with the Sail Operator label in the openshift-operators namespace +func ensureSailOperatorResourceDoesNotExist(oc *exutil.CLI, resourceType string) { + labelSelector := fmt.Sprintf("operators.coreos.com/%s", serviceMeshOperatorName) + output, err := oc.AsAdmin().Run("get").Args("-n", expectedSubscriptionNamespace, resourceType, "-l", labelSelector, "-o", "name").Output() + + // If the CRD doesn't exist (OLM not installed), that's fine - no resources exist + if err != nil && strings.Contains(err.Error(), "the server doesn't have a resource type") { + return + } + + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to query %s with label %s in namespace %s", resourceType, labelSelector, expectedSubscriptionNamespace) + o.Expect(strings.TrimSpace(output)).To(o.BeEmpty(), + "Expected no Sail Operator %s with label %s in namespace %s, but found:\n%s", resourceType, labelSelector, expectedSubscriptionNamespace, output) +} + +func checkGatewayClassCondition(oc *exutil.CLI, name string, conditionType string, conditionStatus metav1.ConditionStatus) error { + timeout := 20 * time.Minute + waitErr := wait.PollUntilContextTimeout(context.Background(), 10*time.Second, timeout, false, func(context context.Context) (bool, error) { + gwc, err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Get(context, name, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get gatewayclass %s: %v; retrying...", name, err) + return false, nil + } + for _, condition := range gwc.Status.Conditions { + if condition.Type == conditionType && condition.Status == conditionStatus { + e2e.Logf("GatewayClass %q has condition %s=%s", name, conditionType, conditionStatus) + return true, nil + } + } + e2e.Logf("Found gatewayclass %s but condition %s is not %s, retrying...", name, conditionType, conditionStatus) + return false, nil + }) + + o.Expect(waitErr).NotTo(o.HaveOccurred(), "GatewayClass %q condition %s=%s was not met within %v", name, conditionType, conditionStatus, timeout) + return nil +} + +func checkGatewayClassFinalizer(oc *exutil.CLI, name string, expectedFinalizer string) error { + timeout := 5 * time.Minute + waitErr := wait.PollUntilContextTimeout(context.Background(), 10*time.Second, timeout, false, func(context context.Context) (bool, error) { + gwc, err := oc.AdminGatewayApiClient().GatewayV1().GatewayClasses().Get(context, name, metav1.GetOptions{}) + if err != nil { + e2e.Logf("Failed to get gatewayclass %s: %v; retrying...", name, err) + return false, nil + } + for _, finalizer := range gwc.Finalizers { + if finalizer == expectedFinalizer { + e2e.Logf("The gatewayClass, %q has the expected finalizer %s", name, expectedFinalizer) + return true, nil + } + } + e2e.Logf("The gatewayclass %s, does not have the expected finalizer, retrying...", name) + return false, nil + }) + + o.Expect(waitErr).NotTo(o.HaveOccurred(), "GatewayClass %q could not find the expected finalizer within %v", name, timeout) + return nil +} + +func assertIstioCRDsOwnedByCIO(oc *exutil.CLI) error { + crdList, err := oc.AdminApiextensionsClient().ApiextensionsV1().CustomResourceDefinitions().List(context.Background(), metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to list CRDs: %w", err) + } + + istioFound := false + for _, crd := range crdList.Items { + if strings.HasSuffix(crd.Name, "istio.io") { + istioFound = true + if value, ok := crd.Labels["ingress.operator.openshift.io/owned"]; ok && value == "true" { + e2e.Logf("CRD %s has the specific label value: %s", crd.Name, value) + continue + } + return fmt.Errorf("CRD %s is not managed by CIO", crd.Name) + } + } + + if !istioFound { + return fmt.Errorf("There are no istio.io CRDs found") + } + return nil +} diff --git a/test/extended/util/annotate/generated/zz_generated.annotations.go b/test/extended/util/annotate/generated/zz_generated.annotations.go index 6aca94448eb5..4599848a2b5d 100644 --- a/test/extended/util/annotate/generated/zz_generated.annotations.go +++ b/test/extended/util/annotate/generated/zz_generated.annotations.go @@ -1539,6 +1539,8 @@ var Annotations = map[string]string{ "[sig-network-edge][Feature:Idling] Unidling with Deployments [apigroup:route.openshift.io] should work with UDP": " [Suite:openshift/conformance/parallel]", + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure GIE is enabled after creating an inferencePool CRD": " [Suite:openshift/conformance/parallel]", + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure HTTPRoute object is created": " [Suite:openshift/conformance/parallel]", "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure LB, service, and dnsRecord are created for a Gateway object": " [Suite:openshift/conformance/parallel]", @@ -1549,6 +1551,20 @@ var Annotations = map[string]string{ "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure default gatewayclass is accepted": " [Suite:openshift/conformance/parallel]", + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure gateway loadbalancer service and dnsrecords could be deleted and then get recreated [Serial]": " [Suite:openshift/conformance/serial]", + + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure istiod deployment and the istio could be deleted and then get recreated [Serial]": " [Suite:openshift/conformance/serial]", + + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains CIO management conditions after creation": " [Suite:openshift/conformance/parallel]", + + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains sail finalizer after creation": " [Suite:openshift/conformance/parallel]", + + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Istio CRDs are managed by CIO and istiod deployment exists": " [Suite:openshift/conformance/parallel]", + + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Sail operator resources are not installed": " [Suite:openshift/conformance/parallel]", + + "[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure istiod Deployment contains the correct label": " [Suite:openshift/conformance/parallel]", + "[sig-network] Internal connectivity for TCP and UDP on ports 9000-9999 is allowed [Serial:Self]": " [Suite:openshift/conformance/parallel]", "[sig-network] ServiceCIDR should be blocked": " [Suite:openshift/conformance/parallel]", diff --git a/zz_generated.manifests/test-reporting.yaml b/zz_generated.manifests/test-reporting.yaml index 7e1348a694f4..2e2a7ca42dda 100644 --- a/zz_generated.manifests/test-reporting.yaml +++ b/zz_generated.manifests/test-reporting.yaml @@ -405,6 +405,8 @@ spec: Verify Gateway API CRDs and ensure required CRDs should already be installed' - featureGate: GatewayAPIController tests: + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + Ensure GIE is enabled after creating an inferencePool CRD' - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure HTTPRoute object is created' - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] @@ -415,6 +417,44 @@ spec: Ensure custom gatewayclass can be accepted' - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] Ensure default gatewayclass is accepted' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + Ensure gateway loadbalancer service and dnsrecords could be deleted and then + get recreated [Serial]' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + Ensure istiod deployment and the istio could be deleted and then get recreated + [Serial]' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains CIO management + conditions after creation' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains sail finalizer + after creation' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Istio CRDs are managed by CIO + and istiod deployment exists' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Sail operator resources are not + installed' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure istiod Deployment contains the + correct label' + - featureGate: GatewayAPIWithoutOLM + tests: + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains CIO management + conditions after creation' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure GatewayClass contains sail finalizer + after creation' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Istio CRDs are managed by CIO + and istiod deployment exists' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure Sail operator resources are not + installed' + - testName: '[sig-network-edge][OCPFeatureGate:GatewayAPIController][Feature:Router][apigroup:gateway.networking.k8s.io] + [OCPFeatureGate:GatewayAPIWithoutOLM] Ensure istiod Deployment contains the + correct label' - featureGate: GitRepoVolumeDriver tests: - testName: '[sig-storage] EmptyDir wrapper volumes should not cause race condition