Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
69 changes: 69 additions & 0 deletions tests/aws-cloud-controller-manager-tests-ext/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# aws-cloud-controller-manager-tests-ext (OTE binary)

The OpenShift Tests Extension (OTE) binary `aws-cloud-controller-manager-tests-ext`
exposes the upstream e2e tests from `cloud-provider-aws` (implemented under `tests/e2e`)
to the OpenShift test framework. Tests are selected/curated by the filters defined in
`main.go`, so that only the intended subset is available in the OpenShift test pipeline.

OpenShift-specific (downstream) tests live under `tests/aws-cloud-controller-manager-tests-ext/e2e`
and are added to the list of tests executed by `openshift-tests`.

The OTE library uses dot imports to register both upstream and downstream Ginkgo specs into the suite.

```mermaid
flowchart LR
A["Upstream Tests<br/>kubernetes/cloud-provider-aws<br/>tests/e2e"] --> C["OTE Binary<br/>aws-cloud-controller-manager-tests-ext"]
B["Downstream Tests<br/>openshift/cloud-provider-aws<br/>tests/aws-cloud-controller-manager-tests-ext/e2e"] --> C
C --> D["openshift-tests<br/>execution framework"]

style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#e8f5e9
style D fill:#f3e5f5
```

## Directory Structure

```
tests/aws-cloud-controller-manager-tests-ext/
├── e2e/
│ ├── loadbalancer.go # OpenShift-specific load balancer tests
│ └── helper.go # Helper functions (AWS clients, feature gate checks, etc.)
├── main.go # Test binary entrypoint
└── README.md
```


## Prerequisites

- Go 1.24+
- Access to an OpenShift cluster on AWS
- Valid `KUBECONFIG` pointing to the cluster
- AWS credentials configured (for tests that query AWS APIs)

## Building the test binary

```sh
make -f openshift-hack/Makefile aws-cloud-controller-manager-tests-ext
```

The binary will be created at the repository root: `./aws-cloud-controller-manager-tests-ext`

## Exploring the test binary

- List the tests

```sh
$ ./aws-cloud-controller-manager-tests-ext list --topology=HighAvailability --platform=aws | jq .[].name
"[cloud-provider-aws-e2e-openshift] loadbalancer NLB feature AWSServiceLBNetworkSecurityGroup should have NLBSecurityGroupMode = Managed in cloud-config [Suite:openshift/conformance/parallel]"
...
"[cloud-provider-aws-e2e] nodes should set zone-id topology label [Suite:openshift/conformance/parallel]"
"[cloud-provider-aws-e2e] nodes should label nodes with topology network info if instance is supported [Suite:openshift/conformance/parallel]"
```

- Run a specific test

```sh
$ export KUBECONFIG=/path/to/kubeconfig
$ ./aws-cloud-controller-manager-tests-ext run-test "[cloud-provider-aws-e2e-openshift] loadbalancer NLB feature AWSServiceLBNetworkSecurityGroup should have security groups attached to default ingress controller NLB [Suite:openshift/conformance/parallel]"
```
118 changes: 118 additions & 0 deletions tests/aws-cloud-controller-manager-tests-ext/e2e/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package e2e

import (
"context"
"fmt"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/test/e2e/framework"
)

// AWS helpers

// getAWSClientLoadBalancer creates an AWS ELBv2 client using default credentials configured in the environment.
func getAWSClientLoadBalancer(ctx context.Context) (*elbv2.Client, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, fmt.Errorf("unable to load AWS config: %v", err)
}
return elbv2.NewFromConfig(cfg), nil
}

// getAWSLoadBalancerFromDNSName finds a load balancer by DNS name using the AWS ELBv2 client.
func getAWSLoadBalancerFromDNSName(ctx context.Context, elbClient *elbv2.Client, lbDNSName string) (*elbv2types.LoadBalancer, error) {
var foundLB *elbv2types.LoadBalancer
framework.Logf("describing load balancers with DNS %s", lbDNSName)

paginator := elbv2.NewDescribeLoadBalancersPaginator(elbClient, &elbv2.DescribeLoadBalancersInput{})
for paginator.HasMorePages() {
page, err := paginator.NextPage(ctx)
if err != nil {
return nil, fmt.Errorf("failed to describe load balancers: %v", err)
}

framework.Logf("found %d load balancers in page", len(page.LoadBalancers))
// Search for the load balancer with matching DNS name in this page
for i := range page.LoadBalancers {
if aws.ToString(page.LoadBalancers[i].DNSName) == lbDNSName {
foundLB = &page.LoadBalancers[i]
framework.Logf("found load balancer with DNS %s", aws.ToString(foundLB.DNSName))
break
}
}
if foundLB != nil {
break
}
}

if foundLB == nil {
return nil, fmt.Errorf("no load balancer found with DNS name: %s", lbDNSName)
}

return foundLB, nil
}

// isFeatureEnabled checks if an OpenShift feature gate is enabled by querying the
// FeatureGate resource named "cluster" using the typed OpenShift config API.
//
// This function uses the official OpenShift config/v1 API types for type-safe
// access to feature gate information, providing better performance and maintainability
// compared to dynamic client approaches.
//
// Parameters:
// - ctx: Context for the API call
// - featureName: Name of the feature gate to check (e.g., "AWSServiceLBNetworkSecurityGroup")
//
// Returns:
// - bool: true if the feature is enabled, false if disabled or not found
// - error: error if the API call fails
//
// Note: For HyperShift clusters, this checks the management cluster's feature gates.
// To check hosted cluster feature gates, use the hosted cluster's kubeconfig.
func isFeatureEnabled(ctx context.Context, featureName string) (bool, error) {
// Get the REST config
restConfig, err := framework.LoadConfig()
if err != nil {
return false, fmt.Errorf("failed to load kubeconfig: %v", err)
}

// Create typed config client (more efficient than dynamic client)
configClient, err := configv1client.NewForConfig(restConfig)
if err != nil {
return false, fmt.Errorf("failed to create config client: %v", err)
}

// Get the FeatureGate resource using typed API
featureGate, err := configClient.FeatureGates().Get(ctx, "cluster", metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("failed to get FeatureGate 'cluster': %v", err)
}

// Iterate through the feature gates status (typed structs)
for _, fg := range featureGate.Status.FeatureGates {
// Check enabled list
for _, enabled := range fg.Enabled {
if string(enabled.Name) == featureName {
framework.Logf("Feature %s is enabled (version %s)", featureName, fg.Version)
return true, nil
}
}

// Check disabled list
for _, disabled := range fg.Disabled {
if string(disabled.Name) == featureName {
framework.Logf("Feature %s is disabled (version %s)", featureName, fg.Version)
return false, nil
}
}
}

// Feature not found in either list
framework.Logf("Feature %s not found in FeatureGate status", featureName)
return false, nil
}
Loading