Added test for namespace controller

Signed-off-by: Anuj Agrawal <anujagrawal380@gmail.com>
This commit is contained in:
Anuj Agrawal 2024-08-14 12:32:41 +05:30
parent 235ec911b1
commit 57575675ef
1 changed files with 436 additions and 0 deletions

View File

@ -0,0 +1,436 @@
/*
Copyright 2023 The Karmada Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package namespace
import (
"context"
"regexp"
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/names"
"github.com/karmada-io/karmada/pkg/util/overridemanager"
)
func TestController_namespaceShouldBeSynced(t *testing.T) {
tests := []struct {
name string
namespace string
skipped []*regexp.Regexp
want bool
}{
{
name: "Reserved namespace",
namespace: "kube-system",
want: true,
},
{
name: "Default namespace",
namespace: "default",
want: false,
},
{
name: "Regular namespace",
namespace: "test-namespace",
want: true,
},
{
name: "Skipped namespace",
namespace: "skip-me",
skipped: []*regexp.Regexp{regexp.MustCompile("skip-.*")},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Controller{
SkippedPropagatingNamespaces: tt.skipped,
}
got := c.namespaceShouldBeSynced(tt.namespace)
assert.Equal(t, tt.want, got)
})
}
}
func TestController_Reconcile(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = clusterv1alpha1.Install(scheme)
_ = policyv1alpha1.Install(scheme)
_ = workv1alpha1.Install(scheme)
tests := []struct {
name string
namespace *corev1.Namespace
clusters []clusterv1alpha1.Cluster
expectedResult controllerruntime.Result
expectedError bool
}{
{
name: "Namespace should be synced",
namespace: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test-namespace",
},
},
clusters: []clusterv1alpha1.Cluster{
{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
},
},
},
expectedResult: controllerruntime.Result{},
expectedError: false,
},
{
name: "Namespace should not be synced",
namespace: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-system",
},
},
expectedResult: controllerruntime.Result{},
expectedError: false,
},
{
name: "Namespace with skip auto propagation label",
namespace: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "skip-namespace",
Labels: map[string]string{
policyv1alpha1.NamespaceSkipAutoPropagationLabel: "true",
},
},
},
expectedResult: controllerruntime.Result{},
expectedError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.namespace).WithLists(&clusterv1alpha1.ClusterList{Items: tt.clusters}).Build()
fakeRecorder := record.NewFakeRecorder(100)
c := &Controller{
Client: fakeClient,
EventRecorder: fakeRecorder,
OverrideManager: overridemanager.New(fakeClient, fakeRecorder),
}
req := controllerruntime.Request{
NamespacedName: types.NamespacedName{
Name: tt.namespace.Name,
},
}
result, err := c.Reconcile(context.Background(), req)
assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err != nil)
if tt.name == "Namespace should be synced" {
work := &workv1alpha1.Work{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Namespace: names.GenerateExecutionSpaceName(tt.clusters[0].Name),
Name: names.GenerateWorkName(tt.namespace.Kind, tt.namespace.Name, tt.namespace.Namespace),
}, work)
assert.NoError(t, err)
assert.NotNil(t, work)
}
})
}
}
func TestController_buildWorks(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = clusterv1alpha1.Install(scheme)
_ = workv1alpha1.Install(scheme)
_ = policyv1alpha1.Install(scheme)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test-namespace",
},
}
clusters := []clusterv1alpha1.Cluster{
{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster2",
},
},
}
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(namespace, &clusters[0], &clusters[1]).Build()
fakeRecorder := record.NewFakeRecorder(100)
c := &Controller{
Client: fakeClient,
EventRecorder: fakeRecorder,
OverrideManager: overridemanager.New(fakeClient, fakeRecorder),
}
err := c.buildWorks(context.Background(), namespace, clusters)
assert.NoError(t, err)
for _, cluster := range clusters {
work := &workv1alpha1.Work{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Namespace: names.GenerateExecutionSpaceName(cluster.Name),
Name: names.GenerateWorkName(namespace.Kind, namespace.Name, namespace.Namespace),
}, work)
assert.NoError(t, err)
assert.NotNil(t, work)
assert.Equal(t, namespace.Name, work.OwnerReferences[0].Name)
}
}
func TestController_buildWorksWithOverridePolicy(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = clusterv1alpha1.Install(scheme)
_ = workv1alpha1.Install(scheme)
_ = policyv1alpha1.Install(scheme)
namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test-namespace",
},
}
clusters := []clusterv1alpha1.Cluster{
{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
},
},
}
overridePolicy := &policyv1alpha1.ClusterOverridePolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-override",
},
Spec: policyv1alpha1.OverrideSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{
APIVersion: "v1",
Kind: "Namespace",
Name: "test-namespace",
},
},
OverrideRules: []policyv1alpha1.RuleWithCluster{
{
TargetCluster: &policyv1alpha1.ClusterAffinity{
ClusterNames: []string{"cluster1"},
},
Overriders: policyv1alpha1.Overriders{
Plaintext: []policyv1alpha1.PlaintextOverrider{
{
Path: "/metadata/labels/overridden",
Operator: "add",
Value: apiextensionsv1.JSON{
Raw: []byte(`"true"`),
},
},
},
},
},
},
},
}
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(namespace, &clusters[0], overridePolicy).Build()
fakeRecorder := record.NewFakeRecorder(100)
c := &Controller{
Client: fakeClient,
EventRecorder: fakeRecorder,
OverrideManager: overridemanager.New(fakeClient, fakeRecorder),
}
err := c.buildWorks(context.Background(), namespace, clusters)
assert.NoError(t, err)
work := &workv1alpha1.Work{}
err = fakeClient.Get(context.Background(), types.NamespacedName{
Namespace: names.GenerateExecutionSpaceName(clusters[0].Name),
Name: names.GenerateWorkName(namespace.Kind, namespace.Name, namespace.Namespace),
}, work)
assert.NoError(t, err)
assert.NotNil(t, work)
t.Logf("Work found: %+v", work)
t.Logf("Work annotations: %v", work.Annotations)
t.Logf("Work spec: %+v", work.Spec)
assert.NotNil(t, work)
}
func TestController_SetupWithManager(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = clusterv1alpha1.Install(scheme)
_ = workv1alpha1.Install(scheme)
mgr, err := controllerruntime.NewManager(controllerruntime.GetConfigOrDie(), controllerruntime.Options{Scheme: scheme})
assert.NoError(t, err)
c := &Controller{
Client: mgr.GetClient(),
EventRecorder: mgr.GetEventRecorderFor("test-controller"),
OverrideManager: overridemanager.New(mgr.GetClient(), mgr.GetEventRecorderFor("test-controller")),
}
err = c.SetupWithManager(mgr)
assert.NoError(t, err)
}
func TestClusterNamespaceFn(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
namespace1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "namespace1"}}
namespace2 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "namespace2"}}
fakeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(namespace1, namespace2).
Build()
c := &Controller{Client: fakeClient}
clusterNamespaceFn := handler.MapFunc(
func(ctx context.Context, _ client.Object) []reconcile.Request {
var requests []reconcile.Request
namespaceList := &corev1.NamespaceList{}
if err := c.Client.List(ctx, namespaceList); err != nil {
klog.Errorf("Failed to list namespace, error: %v", err)
return nil
}
for _, namespace := range namespaceList.Items {
requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{
Name: namespace.Name,
}})
}
return requests
})
requests := clusterNamespaceFn(context.Background(), nil)
assert.Len(t, requests, 2)
assert.Contains(t, requests, reconcile.Request{NamespacedName: types.NamespacedName{Name: "namespace1"}})
assert.Contains(t, requests, reconcile.Request{NamespacedName: types.NamespacedName{Name: "namespace2"}})
}
func TestClusterOverridePolicyNamespaceFn(t *testing.T) {
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = policyv1alpha1.Install(scheme)
cop := &policyv1alpha1.ClusterOverridePolicy{
ObjectMeta: metav1.ObjectMeta{Name: "test-policy"},
Spec: policyv1alpha1.OverrideSpec{
ResourceSelectors: []policyv1alpha1.ResourceSelector{
{APIVersion: "v1", Kind: "Namespace", Name: "test-namespace"},
},
},
}
namespace1 := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}}
fakeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(namespace1, cop).
Build()
c := &Controller{Client: fakeClient}
clusterOverridePolicyNamespaceFn := handler.MapFunc(
func(ctx context.Context, obj client.Object) []reconcile.Request {
var requests []reconcile.Request
cop, ok := obj.(*policyv1alpha1.ClusterOverridePolicy)
if !ok {
return requests
}
selectedNamespaces := sets.NewString()
containsAllNamespace := false
for _, rs := range cop.Spec.ResourceSelectors {
if rs.APIVersion != "v1" || rs.Kind != "Namespace" {
continue
}
if rs.Name == "" {
containsAllNamespace = true
break
}
selectedNamespaces.Insert(rs.Name)
}
if containsAllNamespace {
namespaceList := &corev1.NamespaceList{}
if err := c.Client.List(ctx, namespaceList); err != nil {
klog.Errorf("Failed to list namespace, error: %v", err)
return nil
}
for _, namespace := range namespaceList.Items {
requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{
Name: namespace.Name,
}})
}
return requests
}
for _, ns := range selectedNamespaces.UnsortedList() {
requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{
Name: ns,
}})
}
return requests
})
requests := clusterOverridePolicyNamespaceFn(context.Background(), cop)
assert.Len(t, requests, 1)
assert.Contains(t, requests, reconcile.Request{NamespacedName: types.NamespacedName{Name: "test-namespace"}})
}