@@ -19,6 +19,7 @@ import (
1919 operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
2020 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
2121
22+ admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
2223 appsv1 "k8s.io/api/apps/v1"
2324 corev1 "k8s.io/api/core/v1"
2425 rbacv1 "k8s.io/api/rbac/v1"
@@ -43,6 +44,10 @@ const (
4344 openshiftIstiodDeploymentName = "istiod-openshift-gateway"
4445 // openshiftSMCPName holds the expected OSSM ServiceMeshControlPlane name
4546 openshiftSMCPName = "openshift-gateway"
47+ // cvoNamespace is the namespace of cluster version operator (CVO).
48+ cvoNamespace = "openshift-cluster-version"
49+ // cvoDeploymentName is the name of cluster version operator's deployment.
50+ cvoDeploymentName = "cluster-version-operator"
4651)
4752
4853// updateIngressOperatorRole updates the ingress-operator cluster role with cluster-admin privilege.
@@ -149,6 +154,53 @@ func deleteExistingCRD(t *testing.T, crdName string) error {
149154 return nil
150155}
151156
157+ // deleteExistingVAP deletes if the VAP of the given name exists and returns an error if not.
158+ func deleteExistingVAP (t * testing.T , vapName string ) error {
159+ t .Helper ()
160+
161+ vap := & admissionregistrationv1.ValidatingAdmissionPolicy {}
162+ newVAP := & admissionregistrationv1.ValidatingAdmissionPolicy {}
163+ name := types.NamespacedName {Name : vapName }
164+
165+ // Retrieve the object to be deleted.
166+ if err := wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , 30 * time .Second , false , func (context context.Context ) (bool , error ) {
167+ if err := kclient .Get (context , name , vap ); err != nil {
168+ t .Logf ("failed to get vap %q: %v, retrying ..." , vapName , err )
169+ return false , nil
170+ }
171+ return true , nil
172+ }); err != nil {
173+ return fmt .Errorf ("failed to get vap %q: %w" , vapName , err )
174+ }
175+
176+ if err := kclient .Delete (context .Background (), vap ); err != nil {
177+ return fmt .Errorf ("failed to delete vap %q: %w" , vapName , err )
178+ }
179+
180+ // Verify VAP was not recreated.
181+ if err := wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , 30 * time .Second , false , func (ctx context.Context ) (bool , error ) {
182+ if err := kclient .Get (ctx , name , newVAP ); err != nil {
183+ if kerrors .IsNotFound (err ) {
184+ // VAP does not exist as expected.
185+ return true , nil
186+ }
187+ t .Logf ("failed to get vap %q: %v, retrying ..." , vapName , err )
188+ return false , nil
189+ }
190+ // Check if new VAP got recreated.
191+ if newVAP != nil && newVAP .UID != vap .UID {
192+ return true , fmt .Errorf ("vap %q got recreated" , vapName )
193+ }
194+ t .Logf ("vap %q still exists, retrying ..." , vapName )
195+ return false , nil
196+ }); err != nil {
197+ return fmt .Errorf ("failed to verify deletion of vap %q: %v" , vapName , err )
198+ }
199+
200+ t .Logf ("deleted vap %q" , vapName )
201+ return nil
202+ }
203+
152204// createHttpRoute checks if the HTTPRoute can be created.
153205// If it can't an error is returned.
154206func createHttpRoute (namespace , routeName , parentNamespace , hostname , backendRefname string , gateway * gatewayapiv1.Gateway ) (* gatewayapiv1.HTTPRoute , error ) {
@@ -272,6 +324,71 @@ func buildHTTPRoute(routeName, namespace, parentgateway, parentNamespace, hostna
272324 }
273325}
274326
327+ // buildGWAPICRDFromName initializes the GatewayAPI CRD deducing most of its required fields from the given name.
328+ func buildGWAPICRDFromName (name string ) * apiextensionsv1.CustomResourceDefinition {
329+ var (
330+ plural = strings .Split (name , "." )[0 ]
331+ group , _ = strings .CutPrefix (name , plural + "." )
332+ scope = apiextensionsv1 .NamespaceScoped
333+ // removing trailing "s"
334+ singular = plural [0 : len (plural )- 1 ]
335+ kind string
336+ )
337+
338+ switch plural {
339+ case "gatewayclasses" :
340+ singular = "gatewayclass"
341+ kind = "GatewayClass"
342+ scope = apiextensionsv1 .ClusterScoped
343+ case "gateways" :
344+ kind = "Gateway"
345+ case "httproutes" :
346+ kind = "HTTPRoute"
347+ case "referencegrants" :
348+ kind = "ReferenceGrant"
349+ }
350+
351+ return & apiextensionsv1.CustomResourceDefinition {
352+ ObjectMeta : metav1.ObjectMeta {
353+ Name : plural + "." + group ,
354+ Annotations : map [string ]string {
355+ "api-approved.kubernetes.io" : "https://github.com/kubernetes-sigs/gateway-api/pull/2466" ,
356+ },
357+ },
358+ Spec : apiextensionsv1.CustomResourceDefinitionSpec {
359+ Group : group ,
360+ Names : apiextensionsv1.CustomResourceDefinitionNames {
361+ Singular : singular ,
362+ Plural : plural ,
363+ Kind : kind ,
364+ },
365+ Scope : scope ,
366+ Versions : []apiextensionsv1.CustomResourceDefinitionVersion {
367+ {
368+ Name : "v1" ,
369+ Storage : true ,
370+ Served : true ,
371+ Schema : & apiextensionsv1.CustomResourceValidation {
372+ OpenAPIV3Schema : & apiextensionsv1.JSONSchemaProps {
373+ Type : "object" ,
374+ },
375+ },
376+ },
377+ {
378+ Name : "v1beta1" ,
379+ Storage : false ,
380+ Served : true ,
381+ Schema : & apiextensionsv1.CustomResourceValidation {
382+ OpenAPIV3Schema : & apiextensionsv1.JSONSchemaProps {
383+ Type : "object" ,
384+ },
385+ },
386+ },
387+ },
388+ },
389+ }
390+ }
391+
275392// assertSubscription checks if the Subscription of the given name exists and returns an error if not.
276393func assertSubscription (t * testing.T , namespace , subName string ) error {
277394 t .Helper ()
@@ -651,3 +768,77 @@ func assertDNSRecord(t *testing.T, recordName types.NamespacedName) error {
651768 })
652769 return err
653770}
771+
772+ // assertVAP checks if the VAP of the given name exists, and returns an error if not.
773+ func assertVAP (t * testing.T , name string ) error {
774+ t .Helper ()
775+ vap := & admissionregistrationv1.ValidatingAdmissionPolicy {}
776+ return wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , 1 * time .Minute , false , func (context context.Context ) (bool , error ) {
777+ if err := kclient .Get (context , types.NamespacedName {Name : name }, vap ); err != nil {
778+ t .Logf ("failed to get vap %q: %v, retrying..." , name , err )
779+ return false , nil
780+ }
781+ return true , nil
782+ })
783+ }
784+
785+ // scaleDeployment scales the deployment with the given name to the specified number of replicas.
786+ func scaleDeployment (t * testing.T , namespace , name string , replicas int32 ) error {
787+ t .Helper ()
788+
789+ nsName := types.NamespacedName {Namespace : namespace , Name : name }
790+ return wait .PollUntilContextTimeout (context .Background (), 1 * time .Second , 30 * time .Second , false , func (context context.Context ) (bool , error ) {
791+ depl := & appsv1.Deployment {}
792+ if err := kclient .Get (context , nsName , depl ); err != nil {
793+ t .Logf ("failed to get deployment %q: %v, retrying..." , nsName , err )
794+ return false , nil
795+ }
796+ if * depl .Spec .Replicas != replicas {
797+ depl .Spec .Replicas = & replicas
798+ if err := kclient .Update (context , depl ); err != nil {
799+ t .Logf ("failed to update deployment %q: %v, retrying..." , nsName , err )
800+ return false , nil
801+ }
802+ }
803+ t .Logf ("scaled deployment %q to %d replica(s)" , nsName , replicas )
804+ return true , nil
805+ })
806+ }
807+
808+ // vapManager helps to disable the VAP resource which is managed by CVO.
809+ type vapManager struct {
810+ t * testing.T
811+ name string
812+ }
813+
814+ // newVAPManager returns a new instance of VAPManager.
815+ func newVAPManager (t * testing.T , vapName string ) * vapManager {
816+ return & vapManager {
817+ t : t ,
818+ name : vapName ,
819+ }
820+ }
821+
822+ // disable scales down CVO and removes the VAP resource.
823+ func (m * vapManager ) disable () (error , func ()) {
824+ if err := scaleDeployment (m .t , cvoNamespace , cvoDeploymentName , 0 ); err != nil {
825+ return fmt .Errorf ("failed to scale down cvo: %w" , err ), func () { /*scale down didn't work, nothing to do*/ }
826+ }
827+ if err := deleteExistingVAP (m .t , m .name ); err != nil {
828+ return fmt .Errorf ("failed to delete vap %q: %w" , m .name , err ), func () {
829+ if err := scaleDeployment (m .t , cvoNamespace , cvoDeploymentName , 1 ); err != nil {
830+ m .t .Errorf ("failed to scale up cvo: %v" , err )
831+ }
832+ }
833+ }
834+ return nil , nil
835+ }
836+
837+ // Enable scales up CVO and waits until the VAP is recreated.
838+ func (m * vapManager ) enable () {
839+ if err := scaleDeployment (m .t , cvoNamespace , cvoDeploymentName , 1 ); err != nil {
840+ m .t .Errorf ("failed to scale up cvo: %v" , err )
841+ } else if err := assertVAP (m .t , m .name ); err != nil {
842+ m .t .Errorf ("failed to find vap %q: %v" , m .name , err )
843+ }
844+ }
0 commit comments