mirror of
https://github.com/open-cluster-management-io/ocm.git
synced 2026-05-15 05:38:51 +00:00
550 lines
18 KiB
Go
550 lines
18 KiB
Go
package helpers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
opereatorfake "github.com/open-cluster-management/api/client/operator/clientset/versioned/fake"
|
|
operatorapiv1 "github.com/open-cluster-management/api/operator/v1"
|
|
"github.com/openshift/library-go/pkg/operator/events/eventstesting"
|
|
operatorhelpers "github.com/openshift/library-go/pkg/operator/v1helpers"
|
|
admissionv1 "k8s.io/api/admissionregistration/v1"
|
|
fakeapiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
"k8s.io/apimachinery/pkg/api/equality"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/util/diff"
|
|
fakekube "k8s.io/client-go/kubernetes/fake"
|
|
fakeapiregistration "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
|
|
)
|
|
|
|
func TestUpdateStatusCondition(t *testing.T) {
|
|
nowish := metav1.Now()
|
|
beforeish := metav1.Time{Time: nowish.Add(-10 * time.Second)}
|
|
afterish := metav1.Time{Time: nowish.Add(10 * time.Second)}
|
|
|
|
cases := []struct {
|
|
name string
|
|
startingConditions []metav1.Condition
|
|
newCondition metav1.Condition
|
|
expectedUpdated bool
|
|
expectedConditions []metav1.Condition
|
|
}{
|
|
{
|
|
name: "add to empty",
|
|
startingConditions: []metav1.Condition{},
|
|
newCondition: newCondition("test", "True", "my-reason", "my-message", nil),
|
|
expectedUpdated: true,
|
|
expectedConditions: []metav1.Condition{newCondition("test", "True", "my-reason", "my-message", nil)},
|
|
},
|
|
{
|
|
name: "add to non-conflicting",
|
|
startingConditions: []metav1.Condition{
|
|
newCondition("two", "True", "my-reason", "my-message", nil),
|
|
},
|
|
newCondition: newCondition("one", "True", "my-reason", "my-message", nil),
|
|
expectedUpdated: true,
|
|
expectedConditions: []metav1.Condition{
|
|
newCondition("two", "True", "my-reason", "my-message", nil),
|
|
newCondition("one", "True", "my-reason", "my-message", nil),
|
|
},
|
|
},
|
|
{
|
|
name: "change existing status",
|
|
startingConditions: []metav1.Condition{
|
|
newCondition("two", "True", "my-reason", "my-message", nil),
|
|
newCondition("one", "True", "my-reason", "my-message", nil),
|
|
},
|
|
newCondition: newCondition("one", "False", "my-different-reason", "my-othermessage", nil),
|
|
expectedUpdated: true,
|
|
expectedConditions: []metav1.Condition{
|
|
newCondition("two", "True", "my-reason", "my-message", nil),
|
|
newCondition("one", "False", "my-different-reason", "my-othermessage", nil),
|
|
},
|
|
},
|
|
{
|
|
name: "leave existing transition time",
|
|
startingConditions: []metav1.Condition{
|
|
newCondition("two", "True", "my-reason", "my-message", nil),
|
|
newCondition("one", "True", "my-reason", "my-message", &beforeish),
|
|
},
|
|
newCondition: newCondition("one", "True", "my-reason", "my-message", &afterish),
|
|
expectedUpdated: false,
|
|
expectedConditions: []metav1.Condition{
|
|
newCondition("two", "True", "my-reason", "my-message", nil),
|
|
newCondition("one", "True", "my-reason", "my-message", &beforeish),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
fakeOperatorClient := opereatorfake.NewSimpleClientset(
|
|
&operatorapiv1.ClusterManager{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "testmanagedcluster"},
|
|
Status: operatorapiv1.ClusterManagerStatus{
|
|
Conditions: c.startingConditions,
|
|
},
|
|
},
|
|
&operatorapiv1.Klusterlet{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "testmanagedcluster"},
|
|
Status: operatorapiv1.KlusterletStatus{
|
|
Conditions: c.startingConditions,
|
|
},
|
|
},
|
|
)
|
|
|
|
hubstatus, updated, err := UpdateClusterManagerStatus(
|
|
context.TODO(),
|
|
fakeOperatorClient.OperatorV1().ClusterManagers(),
|
|
"testmanagedcluster",
|
|
UpdateClusterManagerConditionFn(c.newCondition),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("unexpected err: %v", err)
|
|
}
|
|
if updated != c.expectedUpdated {
|
|
t.Errorf("expected %t, but %t", c.expectedUpdated, updated)
|
|
}
|
|
|
|
klusterletstatus, updated, err := UpdateKlusterletStatus(
|
|
context.TODO(),
|
|
fakeOperatorClient.OperatorV1().Klusterlets(),
|
|
"testmanagedcluster",
|
|
UpdateKlusterletConditionFn(c.newCondition),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("unexpected err: %v", err)
|
|
}
|
|
if updated != c.expectedUpdated {
|
|
t.Errorf("expected %t, but %t", c.expectedUpdated, updated)
|
|
}
|
|
|
|
for i := range c.expectedConditions {
|
|
expected := c.expectedConditions[i]
|
|
hubactual := hubstatus.Conditions[i]
|
|
if expected.LastTransitionTime == (metav1.Time{}) {
|
|
hubactual.LastTransitionTime = metav1.Time{}
|
|
}
|
|
if !equality.Semantic.DeepEqual(expected, hubactual) {
|
|
t.Errorf(diff.ObjectDiff(expected, hubactual))
|
|
}
|
|
|
|
klusterletactual := klusterletstatus.Conditions[i]
|
|
if expected.LastTransitionTime == (metav1.Time{}) {
|
|
klusterletactual.LastTransitionTime = metav1.Time{}
|
|
}
|
|
if !equality.Semantic.DeepEqual(expected, klusterletactual) {
|
|
t.Errorf(diff.ObjectDiff(expected, klusterletactual))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func newCondition(name, status, reason, message string, lastTransition *metav1.Time) metav1.Condition {
|
|
ret := metav1.Condition{
|
|
Type: name,
|
|
Status: metav1.ConditionStatus(status),
|
|
Reason: reason,
|
|
Message: message,
|
|
}
|
|
if lastTransition != nil {
|
|
ret.LastTransitionTime = *lastTransition
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func newValidatingWebhookConfiguration(name, svc, svcNameSpace string) *admissionv1.ValidatingWebhookConfiguration {
|
|
return &admissionv1.ValidatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionv1.ValidatingWebhook{
|
|
{
|
|
ClientConfig: admissionv1.WebhookClientConfig{
|
|
Service: &admissionv1.ServiceReference{
|
|
Name: svc,
|
|
Namespace: svcNameSpace,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func newMutatingWebhookConfiguration(name, svc, svcNameSpace string) *admissionv1.MutatingWebhookConfiguration {
|
|
return &admissionv1.MutatingWebhookConfiguration{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
},
|
|
Webhooks: []admissionv1.MutatingWebhook{
|
|
{
|
|
ClientConfig: admissionv1.WebhookClientConfig{
|
|
Service: &admissionv1.ServiceReference{
|
|
Name: svc,
|
|
Namespace: svcNameSpace,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func newUnstructured(
|
|
apiVersion, kind, namespace, name string, content map[string]interface{}) *unstructured.Unstructured {
|
|
object := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": apiVersion,
|
|
"kind": kind,
|
|
"metadata": map[string]interface{}{
|
|
"namespace": namespace,
|
|
"name": name,
|
|
},
|
|
},
|
|
}
|
|
for key, val := range content {
|
|
object.Object[key] = val
|
|
}
|
|
|
|
return object
|
|
}
|
|
|
|
func newDeployment(name, namespace string, generation int64) *appsv1.Deployment {
|
|
return &appsv1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: name,
|
|
Namespace: namespace,
|
|
Generation: generation,
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestApplyValidatingWebhookConfiguration(t *testing.T) {
|
|
testcase := []struct {
|
|
name string
|
|
existing []runtime.Object
|
|
expected *admissionv1.ValidatingWebhookConfiguration
|
|
expectUpdated bool
|
|
}{
|
|
{
|
|
name: "Create a new configuration",
|
|
expectUpdated: true,
|
|
existing: []runtime.Object{},
|
|
expected: newValidatingWebhookConfiguration("test", "svc1", "svc1"),
|
|
},
|
|
{
|
|
name: "update an existing configuration",
|
|
expectUpdated: true,
|
|
existing: []runtime.Object{newValidatingWebhookConfiguration("test", "svc1", "svc1")},
|
|
expected: newValidatingWebhookConfiguration("test", "svc2", "svc2"),
|
|
},
|
|
{
|
|
name: "skip update",
|
|
expectUpdated: false,
|
|
existing: []runtime.Object{newValidatingWebhookConfiguration("test", "svc1", "svc1")},
|
|
expected: newValidatingWebhookConfiguration("test", "svc1", "svc1"),
|
|
},
|
|
}
|
|
|
|
for _, c := range testcase {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
fakeKubeClient := fakekube.NewSimpleClientset(c.existing...)
|
|
_, updated, err := ApplyValidatingWebhookConfiguration(fakeKubeClient.AdmissionregistrationV1(), c.expected)
|
|
if err != nil {
|
|
t.Errorf("Expected no error when applying: %v", err)
|
|
}
|
|
|
|
if updated != c.expectUpdated {
|
|
t.Errorf("Expect update is %t, but got %t", c.expectUpdated, updated)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplyMutatingWebhookConfiguration(t *testing.T) {
|
|
testcase := []struct {
|
|
name string
|
|
existing []runtime.Object
|
|
expected *admissionv1.MutatingWebhookConfiguration
|
|
expectUpdated bool
|
|
}{
|
|
{
|
|
name: "Create a new configuration",
|
|
expectUpdated: true,
|
|
existing: []runtime.Object{},
|
|
expected: newMutatingWebhookConfiguration("test", "svc1", "svc1"),
|
|
},
|
|
{
|
|
name: "update an existing configuration",
|
|
expectUpdated: true,
|
|
existing: []runtime.Object{newMutatingWebhookConfiguration("test", "svc1", "svc1")},
|
|
expected: newMutatingWebhookConfiguration("test", "svc2", "svc2"),
|
|
},
|
|
{
|
|
name: "skip update",
|
|
expectUpdated: false,
|
|
existing: []runtime.Object{newMutatingWebhookConfiguration("test", "svc1", "svc1")},
|
|
expected: newMutatingWebhookConfiguration("test", "svc1", "svc1"),
|
|
},
|
|
}
|
|
|
|
for _, c := range testcase {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
fakeKubeClient := fakekube.NewSimpleClientset(c.existing...)
|
|
_, updated, err := ApplyMutatingWebhookConfiguration(fakeKubeClient.AdmissionregistrationV1(), c.expected)
|
|
if err != nil {
|
|
t.Errorf("Expected no error when applying: %v", err)
|
|
}
|
|
|
|
if updated != c.expectUpdated {
|
|
t.Errorf("Expect update is %t, but got %t", c.expectUpdated, updated)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplyDirectly(t *testing.T) {
|
|
testcase := []struct {
|
|
name string
|
|
applyFiles map[string]runtime.Object
|
|
applyFileNames []string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
name: "Apply webhooks & apiservice & secret",
|
|
applyFiles: map[string]runtime.Object{
|
|
"validatingwebhooks": newUnstructured("admissionregistration.k8s.io/v1", "ValidatingWebhookConfiguration", "", "", map[string]interface{}{"webhooks": []interface{}{}}),
|
|
"mutatingwebhooks": newUnstructured("admissionregistration.k8s.io/v1", "MutatingWebhookConfiguration", "", "", map[string]interface{}{"webhooks": []interface{}{}}),
|
|
"apiservice": newUnstructured("apiregistration.k8s.io/v1", "APIService", "", "", map[string]interface{}{"spec": map[string]interface{}{"service": map[string]string{"name": "svc1", "namespace": "svc1"}}}),
|
|
"secret": newUnstructured("v1", "Secret", "ns1", "n1", map[string]interface{}{"data": map[string]interface{}{"key1": []byte("key1")}}),
|
|
},
|
|
applyFileNames: []string{"validatingwebhooks", "mutatingwebhooks", "apiservice", "secret"},
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "Apply unhandled object",
|
|
applyFiles: map[string]runtime.Object{
|
|
"kind1": newUnstructured("v1", "Kind1", "ns1", "n1", map[string]interface{}{"spec": map[string]interface{}{"key1": []byte("key1")}}),
|
|
},
|
|
applyFileNames: []string{"kind1"},
|
|
expectErr: true,
|
|
},
|
|
}
|
|
|
|
for _, c := range testcase {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
fakeKubeClient := fakekube.NewSimpleClientset()
|
|
fakeResgistrationClient := fakeapiregistration.NewSimpleClientset()
|
|
fakeExtensionClient := fakeapiextensions.NewSimpleClientset()
|
|
results := ApplyDirectly(
|
|
fakeKubeClient, fakeExtensionClient, fakeResgistrationClient.ApiregistrationV1(),
|
|
eventstesting.NewTestingEventRecorder(t),
|
|
func(name string) ([]byte, error) {
|
|
if c.applyFiles[name] == nil {
|
|
return nil, fmt.Errorf("Failed to find file")
|
|
}
|
|
|
|
return json.Marshal(c.applyFiles[name])
|
|
},
|
|
c.applyFileNames...,
|
|
)
|
|
aggregatedErr := []error{}
|
|
for _, r := range results {
|
|
if r.Error != nil {
|
|
aggregatedErr = append(aggregatedErr, r.Error)
|
|
}
|
|
}
|
|
|
|
if len(aggregatedErr) == 0 && c.expectErr {
|
|
t.Errorf("Expect an apply error")
|
|
}
|
|
if len(aggregatedErr) != 0 && !c.expectErr {
|
|
t.Errorf("Expect no apply error, %v", operatorhelpers.NewMultiLineAggregate(aggregatedErr))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeleteStaticObject(t *testing.T) {
|
|
applyFiles := map[string]runtime.Object{
|
|
"validatingwebhooks": newUnstructured("admissionregistration.k8s.io/v1", "ValidatingWebhookConfiguration", "", "", map[string]interface{}{"webhooks": []interface{}{}}),
|
|
"mutatingwebhooks": newUnstructured("admissionregistration.k8s.io/v1", "MutatingWebhookConfiguration", "", "", map[string]interface{}{"webhooks": []interface{}{}}),
|
|
"apiservice": newUnstructured("apiregistration.k8s.io/v1", "APIService", "", "", map[string]interface{}{"spec": map[string]interface{}{"service": map[string]string{"name": "svc1", "namespace": "svc1"}}}),
|
|
"secret": newUnstructured("v1", "Secret", "ns1", "n1", map[string]interface{}{"data": map[string]interface{}{"key1": []byte("key1")}}),
|
|
"crd": newUnstructured("apiextensions.k8s.io/v1beta1", "CustomResourceDefinition", "", "", map[string]interface{}{}),
|
|
"kind1": newUnstructured("v1", "Kind1", "ns1", "n1", map[string]interface{}{"spec": map[string]interface{}{"key1": []byte("key1")}}),
|
|
}
|
|
testcase := []struct {
|
|
name string
|
|
applyFileName string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
name: "Delete validating webhooks",
|
|
applyFileName: "validatingwebhooks",
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "Delete mutating webhooks",
|
|
applyFileName: "mutatingwebhooks",
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "Delete apiservice",
|
|
applyFileName: "apiservice",
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "Delete secret",
|
|
applyFileName: "secret",
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "Delete crd",
|
|
applyFileName: "crd",
|
|
expectErr: false,
|
|
},
|
|
{
|
|
name: "Delete unhandled object",
|
|
applyFileName: "kind1",
|
|
expectErr: true,
|
|
},
|
|
}
|
|
|
|
for _, c := range testcase {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
fakeKubeClient := fakekube.NewSimpleClientset()
|
|
fakeResgistrationClient := fakeapiregistration.NewSimpleClientset()
|
|
fakeExtensionClient := fakeapiextensions.NewSimpleClientset()
|
|
err := CleanUpStaticObject(
|
|
context.TODO(),
|
|
fakeKubeClient, fakeExtensionClient, fakeResgistrationClient.ApiregistrationV1(),
|
|
func(name string) ([]byte, error) {
|
|
if applyFiles[name] == nil {
|
|
return nil, fmt.Errorf("Failed to find file")
|
|
}
|
|
|
|
return json.Marshal(applyFiles[name])
|
|
},
|
|
c.applyFileName,
|
|
)
|
|
|
|
if err == nil && c.expectErr {
|
|
t.Errorf("Expect an apply error")
|
|
}
|
|
if err != nil && !c.expectErr {
|
|
t.Errorf("Expect no apply error, %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateGeneration(t *testing.T) {
|
|
gvr := appsv1.SchemeGroupVersion.WithResource("deployments")
|
|
cases := []struct {
|
|
name string
|
|
startingGeneration []operatorapiv1.GenerationStatus
|
|
newGeneration operatorapiv1.GenerationStatus
|
|
expectedUpdated bool
|
|
expectedGeneration []operatorapiv1.GenerationStatus
|
|
}{
|
|
{
|
|
name: "add to empty",
|
|
startingGeneration: []operatorapiv1.GenerationStatus{},
|
|
newGeneration: NewGenerationStatus(gvr, newDeployment("test", "test", 0)),
|
|
expectedUpdated: true,
|
|
expectedGeneration: []operatorapiv1.GenerationStatus{NewGenerationStatus(gvr, newDeployment("test", "test", 0))},
|
|
},
|
|
{
|
|
name: "add to non-conflicting",
|
|
startingGeneration: []operatorapiv1.GenerationStatus{
|
|
NewGenerationStatus(gvr, newDeployment("test", "test", 0)),
|
|
},
|
|
newGeneration: NewGenerationStatus(gvr, newDeployment("test2", "test", 0)),
|
|
expectedUpdated: true,
|
|
expectedGeneration: []operatorapiv1.GenerationStatus{
|
|
NewGenerationStatus(gvr, newDeployment("test", "test", 0)),
|
|
NewGenerationStatus(gvr, newDeployment("test2", "test", 0)),
|
|
},
|
|
},
|
|
{
|
|
name: "change existing status",
|
|
startingGeneration: []operatorapiv1.GenerationStatus{
|
|
NewGenerationStatus(gvr, newDeployment("test", "test", 0)),
|
|
NewGenerationStatus(gvr, newDeployment("test2", "test", 0)),
|
|
},
|
|
newGeneration: NewGenerationStatus(gvr, newDeployment("test", "test", 1)),
|
|
expectedUpdated: true,
|
|
expectedGeneration: []operatorapiv1.GenerationStatus{
|
|
NewGenerationStatus(gvr, newDeployment("test", "test", 1)),
|
|
NewGenerationStatus(gvr, newDeployment("test2", "test", 0)),
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
fakeOperatorClient := opereatorfake.NewSimpleClientset(
|
|
&operatorapiv1.ClusterManager{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "testmanagedcluster"},
|
|
Status: operatorapiv1.ClusterManagerStatus{
|
|
Generations: c.startingGeneration,
|
|
},
|
|
},
|
|
&operatorapiv1.Klusterlet{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "testmanagedcluster"},
|
|
Status: operatorapiv1.KlusterletStatus{
|
|
Generations: c.startingGeneration,
|
|
},
|
|
},
|
|
)
|
|
|
|
hubstatus, updated, err := UpdateClusterManagerStatus(
|
|
context.TODO(),
|
|
fakeOperatorClient.OperatorV1().ClusterManagers(),
|
|
"testmanagedcluster",
|
|
UpdateClusterManagerGenerationsFn(c.newGeneration),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("unexpected err: %v", err)
|
|
}
|
|
if updated != c.expectedUpdated {
|
|
t.Errorf("expected %t, but %t", c.expectedUpdated, updated)
|
|
}
|
|
|
|
klusterletstatus, updated, err := UpdateKlusterletStatus(
|
|
context.TODO(),
|
|
fakeOperatorClient.OperatorV1().Klusterlets(),
|
|
"testmanagedcluster",
|
|
UpdateKlusterletGenerationsFn(c.newGeneration),
|
|
)
|
|
if err != nil {
|
|
t.Errorf("unexpected err: %v", err)
|
|
}
|
|
if updated != c.expectedUpdated {
|
|
t.Errorf("expected %t, but %t", c.expectedUpdated, updated)
|
|
}
|
|
|
|
for i := range c.expectedGeneration {
|
|
expected := c.expectedGeneration[i]
|
|
hubactual := hubstatus.Generations[i]
|
|
if !equality.Semantic.DeepEqual(expected, hubactual) {
|
|
t.Errorf(diff.ObjectDiff(expected, hubactual))
|
|
}
|
|
|
|
klusterletactual := klusterletstatus.Generations[i]
|
|
if !equality.Semantic.DeepEqual(expected, klusterletactual) {
|
|
t.Errorf(diff.ObjectDiff(expected, klusterletactual))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|