Skip to content

Commit 9ed22ce

Browse files
authored
add test framework (#1495)
1 parent 7628964 commit 9ed22ce

File tree

10 files changed

+461
-0
lines changed

10 files changed

+461
-0
lines changed

test/framework/framework.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package framework
2+
3+
import (
4+
"github.com/go-logr/logr"
5+
. "github.com/onsi/gomega"
6+
"k8s.io/apimachinery/pkg/runtime"
7+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
8+
"k8s.io/client-go/rest"
9+
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
10+
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws"
11+
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/throttle"
12+
k8sresources "sigs.k8s.io/aws-load-balancer-controller/test/framework/resources/k8s"
13+
"sigs.k8s.io/aws-load-balancer-controller/test/framework/utils"
14+
ctrl "sigs.k8s.io/controller-runtime"
15+
"sigs.k8s.io/controller-runtime/pkg/cache"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
"sync"
18+
)
19+
20+
var (
21+
frameworkSingleton *Framework
22+
frameworkSingletonInitOnce sync.Once
23+
)
24+
25+
type Framework struct {
26+
Options Options
27+
RestCfg *rest.Config
28+
K8sClient client.Client
29+
Cloud aws.Cloud
30+
31+
NSManager k8sresources.NamespaceManager
32+
DPManager k8sresources.DeploymentManager
33+
SVCManager k8sresources.ServiceManager
34+
INGManager k8sresources.IngressManager
35+
36+
Logger logr.Logger
37+
StopChan <-chan struct{}
38+
}
39+
40+
// New constructs new framework
41+
func New() *Framework {
42+
frameworkSingletonInitOnce.Do(func() {
43+
frameworkSingleton = initFramework()
44+
})
45+
return frameworkSingleton
46+
}
47+
48+
func initFramework() *Framework {
49+
err := globalOptions.Validate()
50+
Expect(err).NotTo(HaveOccurred())
51+
restCfg := ctrl.GetConfigOrDie()
52+
53+
k8sSchema := runtime.NewScheme()
54+
clientgoscheme.AddToScheme(k8sSchema)
55+
elbv2api.AddToScheme(k8sSchema)
56+
57+
stopChan := ctrl.SetupSignalHandler()
58+
cache, err := cache.New(restCfg, cache.Options{Scheme: k8sSchema})
59+
Expect(err).NotTo(HaveOccurred())
60+
go func() {
61+
cache.Start(stopChan)
62+
}()
63+
64+
cache.WaitForCacheSync(stopChan)
65+
realClient, err := client.New(restCfg, client.Options{Scheme: k8sSchema})
66+
Expect(err).NotTo(HaveOccurred())
67+
k8sClient := client.DelegatingClient{
68+
Reader: &client.DelegatingReader{
69+
CacheReader: cache,
70+
ClientReader: realClient,
71+
},
72+
Writer: realClient,
73+
StatusClient: realClient,
74+
}
75+
cloud, err := aws.NewCloud(aws.CloudConfig{
76+
Region: globalOptions.AWSRegion,
77+
VpcID: globalOptions.AWSVPCID,
78+
MaxRetries: 3,
79+
ThrottleConfig: throttle.NewDefaultServiceOperationsThrottleConfig(),
80+
}, nil)
81+
Expect(err).NotTo(HaveOccurred())
82+
83+
logger := utils.NewGinkgoLogger()
84+
85+
f := &Framework{
86+
Options: globalOptions,
87+
RestCfg: restCfg,
88+
K8sClient: k8sClient,
89+
Cloud: cloud,
90+
91+
NSManager: k8sresources.NewDefaultNamespaceManager(k8sClient, logger),
92+
DPManager: k8sresources.NewDefaultDeploymentManager(k8sClient, logger),
93+
SVCManager: k8sresources.NewDefaultServiceManager(k8sClient, logger),
94+
INGManager: k8sresources.NewDefaultIngressManager(k8sClient, logger),
95+
Logger: logger,
96+
StopChan: stopChan,
97+
}
98+
99+
return f
100+
}

test/framework/options.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package framework
2+
3+
import (
4+
"flag"
5+
"github.com/pkg/errors"
6+
)
7+
8+
var globalOptions Options
9+
10+
func init() {
11+
globalOptions.BindFlags()
12+
}
13+
14+
// configuration options
15+
type Options struct {
16+
ClusterName string
17+
AWSRegion string
18+
AWSVPCID string
19+
}
20+
21+
func (options *Options) BindFlags() {
22+
flag.StringVar(&options.ClusterName, "cluster-name", "", `Kubernetes cluster name (required)`)
23+
flag.StringVar(&options.AWSRegion, "aws-region", "", `AWS Region for the kubernetes cluster`)
24+
flag.StringVar(&options.AWSVPCID, "aws-vpc-id", "", `AWS VPC ID for the kubernetes cluster`)
25+
}
26+
27+
func (options *Options) Validate() error {
28+
if len(options.ClusterName) == 0 {
29+
return errors.Errorf("%s must be set!", "cluster-name")
30+
}
31+
if len(options.AWSRegion) == 0 {
32+
return errors.Errorf("%s must be set!", "aws-region")
33+
}
34+
if len(options.AWSVPCID) == 0 {
35+
return errors.Errorf("%s must be set!", "aws-vpc-id")
36+
}
37+
return nil
38+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package k8s
2+
3+
import (
4+
"context"
5+
"github.com/go-logr/logr"
6+
appsv1 "k8s.io/api/apps/v1"
7+
apierrs "k8s.io/apimachinery/pkg/api/errors"
8+
"k8s.io/apimachinery/pkg/util/wait"
9+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
10+
"sigs.k8s.io/aws-load-balancer-controller/test/framework/utils"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
)
13+
14+
// DeploymentManager is responsible for deployment resources.
15+
type DeploymentManager interface {
16+
WaitUntilDeploymentReady(ctx context.Context, dp *appsv1.Deployment) (*appsv1.Deployment, error)
17+
WaitUntilDeploymentDeleted(ctx context.Context, dp *appsv1.Deployment) error
18+
}
19+
20+
// NewDefaultDeploymentManager constructs new DeploymentManager
21+
func NewDefaultDeploymentManager(k8sClient client.Client, logger logr.Logger) *defaultDeploymentManager {
22+
return &defaultDeploymentManager{
23+
k8sClient: k8sClient,
24+
logger: logger,
25+
}
26+
}
27+
28+
var _ DeploymentManager = &defaultDeploymentManager{}
29+
30+
// default implementation for DeploymentManager.
31+
type defaultDeploymentManager struct {
32+
k8sClient client.Client
33+
logger logr.Logger
34+
}
35+
36+
func (m *defaultDeploymentManager) WaitUntilDeploymentReady(ctx context.Context, dp *appsv1.Deployment) (*appsv1.Deployment, error) {
37+
observedDP := &appsv1.Deployment{}
38+
return observedDP, wait.PollImmediateUntil(utils.PollIntervalShort, func() (bool, error) {
39+
if err := m.k8sClient.Get(ctx, k8s.NamespacedName(dp), observedDP); err != nil {
40+
return false, err
41+
}
42+
if observedDP.Status.UpdatedReplicas == (*dp.Spec.Replicas) &&
43+
observedDP.Status.Replicas == (*dp.Spec.Replicas) &&
44+
observedDP.Status.AvailableReplicas == (*dp.Spec.Replicas) &&
45+
observedDP.Status.ObservedGeneration >= dp.Generation {
46+
return true, nil
47+
}
48+
return false, nil
49+
}, ctx.Done())
50+
}
51+
52+
func (m *defaultDeploymentManager) WaitUntilDeploymentDeleted(ctx context.Context, dp *appsv1.Deployment) error {
53+
observedDP := &appsv1.Deployment{}
54+
return wait.PollImmediateUntil(utils.PollIntervalShort, func() (bool, error) {
55+
if err := m.k8sClient.Get(ctx, k8s.NamespacedName(dp), observedDP); err != nil {
56+
if apierrs.IsNotFound(err) {
57+
return true, nil
58+
}
59+
return false, err
60+
}
61+
return false, nil
62+
}, ctx.Done())
63+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package k8s
2+
3+
import (
4+
"context"
5+
"github.com/go-logr/logr"
6+
networking "k8s.io/api/networking/v1beta1"
7+
apierrs "k8s.io/apimachinery/pkg/api/errors"
8+
"k8s.io/apimachinery/pkg/util/wait"
9+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
10+
"sigs.k8s.io/aws-load-balancer-controller/test/framework/utils"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
)
13+
14+
// IngressManager is responsible for Ingress resources.
15+
type IngressManager interface {
16+
WaitUntilIngressReady(ctx context.Context, ing *networking.Ingress) (*networking.Ingress, error)
17+
WaitUntilIngressDeleted(ctx context.Context, ing *networking.Ingress) error
18+
}
19+
20+
// NewDefaultIngressManager constructs new IngressManager.
21+
func NewDefaultIngressManager(k8sClient client.Client, logger logr.Logger) *defaultIngressManager {
22+
return &defaultIngressManager{
23+
k8sClient: k8sClient,
24+
logger: logger,
25+
}
26+
}
27+
28+
var _ IngressManager = &defaultIngressManager{}
29+
30+
// default implementation for IngressManager.
31+
type defaultIngressManager struct {
32+
k8sClient client.Client
33+
logger logr.Logger
34+
}
35+
36+
func (m *defaultIngressManager) WaitUntilIngressReady(ctx context.Context, ing *networking.Ingress) (*networking.Ingress, error) {
37+
observedING := &networking.Ingress{}
38+
return observedING, wait.PollImmediateUntil(utils.PollIntervalShort, func() (bool, error) {
39+
if err := m.k8sClient.Get(ctx, k8s.NamespacedName(ing), observedING); err != nil {
40+
return false, err
41+
}
42+
for _, lbIngress := range observedING.Status.LoadBalancer.Ingress {
43+
if len(lbIngress.Hostname) != 0 || len(lbIngress.IP) != 0 {
44+
return true, nil
45+
}
46+
}
47+
return false, nil
48+
}, ctx.Done())
49+
}
50+
51+
func (m *defaultIngressManager) WaitUntilIngressDeleted(ctx context.Context, ing *networking.Ingress) error {
52+
observedING := &networking.Ingress{}
53+
return wait.PollImmediateUntil(utils.PollIntervalShort, func() (bool, error) {
54+
if err := m.k8sClient.Get(ctx, k8s.NamespacedName(ing), observedING); err != nil {
55+
if apierrs.IsNotFound(err) {
56+
return true, nil
57+
}
58+
return false, err
59+
}
60+
return false, nil
61+
}, ctx.Done())
62+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package k8s
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/go-logr/logr"
7+
corev1 "k8s.io/api/core/v1"
8+
apierrs "k8s.io/apimachinery/pkg/api/errors"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/types"
11+
"k8s.io/apimachinery/pkg/util/wait"
12+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
13+
"sigs.k8s.io/aws-load-balancer-controller/test/framework/utils"
14+
"sigs.k8s.io/controller-runtime/pkg/client"
15+
)
16+
17+
// NamespaceManager is responsible for namespace resources.
18+
type NamespaceManager interface {
19+
AllocateNamespace(ctx context.Context, baseName string) (*corev1.Namespace, error)
20+
WaitUntilNamespaceDeleted(ctx context.Context, ns *corev1.Namespace) error
21+
}
22+
23+
// NewDefaultNamespaceManager constructs new defaultNamespaceManager.
24+
func NewDefaultNamespaceManager(k8sClient client.Client, logger logr.Logger) *defaultNamespaceManager {
25+
return &defaultNamespaceManager{
26+
k8sClient: k8sClient,
27+
logger: logger,
28+
}
29+
}
30+
31+
var _ NamespaceManager = &defaultNamespaceManager{}
32+
33+
// default implementation for NamespaceManager
34+
type defaultNamespaceManager struct {
35+
k8sClient client.Client
36+
logger logr.Logger
37+
}
38+
39+
func (m *defaultNamespaceManager) AllocateNamespace(ctx context.Context, baseName string) (*corev1.Namespace, error) {
40+
name, err := m.findAvailableNamespaceName(ctx, baseName)
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
ns := &corev1.Namespace{
46+
ObjectMeta: metav1.ObjectMeta{
47+
Name: name,
48+
},
49+
}
50+
err = m.k8sClient.Create(ctx, ns)
51+
return ns, err
52+
}
53+
54+
func (m *defaultNamespaceManager) WaitUntilNamespaceDeleted(ctx context.Context, ns *corev1.Namespace) error {
55+
gotNS := &corev1.Namespace{}
56+
return wait.PollImmediateUntil(utils.PollIntervalShort, func() (bool, error) {
57+
if err := m.k8sClient.Get(ctx, k8s.NamespacedName(ns), gotNS); err != nil {
58+
if apierrs.IsNotFound(err) {
59+
return true, nil
60+
}
61+
return false, err
62+
}
63+
return false, nil
64+
}, ctx.Done())
65+
}
66+
67+
// findAvailableNamespaceName random namespace name starting with baseName.
68+
func (m *defaultNamespaceManager) findAvailableNamespaceName(ctx context.Context, baseName string) (string, error) {
69+
var name string
70+
gotNS := &corev1.Namespace{}
71+
err := wait.PollImmediateUntil(utils.PollIntervalShort, func() (bool, error) {
72+
name = fmt.Sprintf("%v-%v", baseName, utils.RandomDNS1123Label(6))
73+
if err := m.k8sClient.Get(ctx, types.NamespacedName{Name: name}, gotNS); err != nil {
74+
if apierrs.IsNotFound(err) {
75+
return true, nil
76+
}
77+
return false, err
78+
}
79+
return false, nil
80+
}, ctx.Done())
81+
return name, err
82+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package k8s
2+
3+
import (
4+
"context"
5+
"github.com/go-logr/logr"
6+
corev1 "k8s.io/api/core/v1"
7+
apierrs "k8s.io/apimachinery/pkg/api/errors"
8+
"k8s.io/apimachinery/pkg/util/wait"
9+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
10+
"sigs.k8s.io/aws-load-balancer-controller/test/framework/utils"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
)
13+
14+
// ServiceManager is responsible for Service resources.
15+
type ServiceManager interface {
16+
WaitUntilServiceDeleted(ctx context.Context, svc *corev1.Service) error
17+
}
18+
19+
// NewDefaultServiceManager constructs new ServiceManager.
20+
func NewDefaultServiceManager(k8sClient client.Client, logger logr.Logger) *defaultServiceManager {
21+
return &defaultServiceManager{
22+
k8sClient: k8sClient,
23+
logger: logger,
24+
}
25+
}
26+
27+
var _ ServiceManager = &defaultServiceManager{}
28+
29+
// default implementation for ServiceManager.
30+
type defaultServiceManager struct {
31+
k8sClient client.Client
32+
logger logr.Logger
33+
}
34+
35+
func (m *defaultServiceManager) WaitUntilServiceDeleted(ctx context.Context, svc *corev1.Service) error {
36+
observedSVC := &corev1.Service{}
37+
return wait.PollImmediateUntil(utils.PollIntervalShort, func() (bool, error) {
38+
if err := m.k8sClient.Get(ctx, k8s.NamespacedName(svc), observedSVC); err != nil {
39+
if apierrs.IsNotFound(err) {
40+
return true, nil
41+
}
42+
return false, err
43+
}
44+
return false, nil
45+
}, ctx.Done())
46+
}

0 commit comments

Comments
 (0)