package dispatcher

import (
	"context"
	"testing"

	"github.com/go-logr/logr"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8sruntime "k8s.io/apimachinery/pkg/runtime"
	apitypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/cache"
	"sigs.k8s.io/controller-runtime/pkg/cache/informertest"
	"sigs.k8s.io/controller-runtime/pkg/client"
	fake "sigs.k8s.io/controller-runtime/pkg/client/fake"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/pkg/scheme"
	"github.com/tidbcloud/shared-provider/pkg/types"
	"github.com/tidbcloud/shared-provider/pkg/utils/labels"
)

const (
	recorderBufSize               = 4096
	kubeClusterConfigMapNamespace = "shared-tier"
	kubeClusterConfigMapName      = "region-to-kube-clusters"
	region                        = "us-west-2"
	kubeClusterName               = "dev-us-west-2-s01"
	kubeClusterNames              = "dev-us-west-2-s01,dev-us-west-2-s02"
	cidr                          = "0.0.0.0/0"
)

func TestReconcileNetwork(t *testing.T) {
	objs := []k8sruntime.Object{
		&corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: kubeClusterConfigMapNamespace,
				Name:      kubeClusterConfigMapName,
			},
			Data: map[string]string{
				region: kubeClusterName,
			},
		},
	}

	cases := []struct {
		namespace         string
		name              string
		clusterName       string
		maxPerKubeCluster int
		hasError          bool
		found             bool
		scheduled         bool

		objs      []k8sruntime.Object // if set, overwrite global objs
		infraObjs []k8sruntime.Object
	}{
		{
			namespace:         "ns1",
			name:              "network-not-found",
			maxPerKubeCluster: 100,
			hasError:          false,
			found:             false,
			scheduled:         false,
		},
		{
			namespace:         "ns2",
			name:              "network-found",
			maxPerKubeCluster: 100,
			hasError:          false,
			found:             true,
			scheduled:         true,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns2",
						Name:      "network-found",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
						CIDR:     cidr,
					},
				},
			},
		},
		{
			namespace:         "ns3",
			name:              "network-with-cluster-name",
			clusterName:       "another-kube-cluster-name",
			maxPerKubeCluster: 100,
			hasError:          false,
			found:             true,
			scheduled:         true,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns3",
						Name:      "network-with-cluster-name",
						Labels: map[string]string{
							labels.LabelKeyKubernetesCluster: "another-kube-cluster-name",
						},
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
						CIDR:     cidr,
					},
				},
			},
		},
		{
			namespace:         "ns4",
			name:              "network-wrong-provider",
			maxPerKubeCluster: 100,
			hasError:          false,
			found:             true,
			scheduled:         false,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns4",
						Name:      "network-wrong-provider",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: "wrong-provider",
						Region:   region,
						CIDR:     cidr,
					},
				},
			},
		},
		{
			namespace:         "ns5",
			name:              "network-cm-not-found",
			maxPerKubeCluster: 100,
			hasError:          true,
			found:             true,
			scheduled:         false,
			objs: []k8sruntime.Object{
				&corev1.ConfigMap{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "another-namespace",
						Name:      "another-name",
					},
					Data: map[string]string{
						region: kubeClusterName,
					},
				},
			},
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns5",
						Name:      "network-cm-not-found",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
						CIDR:     cidr,
					},
				},
			},
		},
		{
			namespace:         "ns6",
			name:              "network-cm-no-cluster",
			maxPerKubeCluster: 100,
			hasError:          false,
			found:             true,
			scheduled:         false,
			objs: []k8sruntime.Object{
				&corev1.ConfigMap{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: kubeClusterConfigMapNamespace,
						Name:      kubeClusterConfigMapName,
					},
					Data: map[string]string{}, // no clusters for the region
				},
			},
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns6",
						Name:      "network-cm-no-cluster",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
						CIDR:     cidr,
					},
				},
			},
		},
		{
			namespace:         "ns7",
			name:              "network-cm-invalid-cluster",
			maxPerKubeCluster: 100,
			hasError:          false,
			found:             true,
			scheduled:         false,
			objs: []k8sruntime.Object{
				&corev1.ConfigMap{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: kubeClusterConfigMapNamespace,
						Name:      kubeClusterConfigMapName,
					},
					Data: map[string]string{
						region: "", // invalid cluster name
					},
				},
			},
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns7",
						Name:      "network-cm-invalid-cluster",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
						CIDR:     cidr,
					},
				},
			},
		},
		{
			namespace:         "ns8",
			name:              "network-not-available",
			maxPerKubeCluster: 0, // can't be scheduled to
			hasError:          false,
			found:             true,
			scheduled:         false,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns8",
						Name:      "network-not-available",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
						CIDR:     cidr,
					},
				},
			},
		},
	}

	for _, c := range cases {
		t.Log("test", c.name)

		objs2 := objs
		if len(c.objs) > 0 {
			objs2 = c.objs
		}
		d := newFakeNetworkDispatcher(objs2, c.infraObjs, c.maxPerKubeCluster)
		r := newFakeReconciler(d, c.infraObjs)
		req := ctrl.Request{
			NamespacedName: apitypes.NamespacedName{
				Namespace: c.namespace,
				Name:      c.name,
			},
		}
		_, err := r.Reconcile(context.Background(), req)
		if c.hasError {
			assert.Error(t, err)
			continue // skip following asserts
		}
		assert.NoError(t, err)

		netGet := &cloudv1.Network{}
		err = r.InfraClient.Get(context.Background(), client.ObjectKey{Namespace: c.namespace, Name: c.name}, netGet)
		if c.found {
			assert.NoError(t, err)
		} else {
			assert.True(t, apierrors.IsNotFound(err))
		}

		if c.scheduled {
			clusterName := c.clusterName
			if clusterName == "" {
				clusterName = kubeClusterName
			}
			assert.Equal(t, clusterName, netGet.Labels[labels.LabelKeyKubernetesCluster])
		} else if c.found {
			assert.Equal(t, "", netGet.Labels[labels.LabelKeyKubernetesCluster])
		}
	}
}

func TestReconcileCluster(t *testing.T) {
	objs := []k8sruntime.Object{
		&corev1.ConfigMap{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: kubeClusterConfigMapNamespace,
				Name:      kubeClusterConfigMapName,
			},
			Data: map[string]string{
				region: kubeClusterNames,
			},
		},
	}

	cases := []struct {
		desc      string
		threshold int

		hasError     bool
		scheduled    string
		rescheduleTo string

		infraObjs []k8sruntime.Object
	}{
		{
			desc:      "cluster has scheduled",
			hasError:  false,
			scheduled: kubeClusterName,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
						Labels: map[string]string{
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
					},
				},
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
						Labels: map[string]string{
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
					},
				},
			},
		},
		{
			desc:     "cluster without network",
			hasError: true,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
					},
				},
			},
		},
		{
			desc:      "wrong provider",
			hasError:  false,
			scheduled: "",
			infraObjs: []k8sruntime.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
					},
					Spec: cloudv1.ClusterSpec{
						NetworkName: "test",
					},
				},
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: "wrong-provider",
						Region:   region,
					},
				},
			},
		},
		{
			desc:      "network is unscheduled",
			hasError:  true,
			scheduled: "",
			infraObjs: []k8sruntime.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
					},
					Spec: cloudv1.ClusterSpec{
						NetworkName: "test",
					},
				},
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
					},
				},
			},
		},
		{
			desc:      "normal case",
			threshold: 100,
			scheduled: kubeClusterName,
			hasError:  false,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
					},
					Spec: cloudv1.ClusterSpec{
						NetworkName: "test",
					},
				},
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
						Labels: map[string]string{
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
					},
				},
			},
		},
		{
			desc:         "reschedule network",
			hasError:     false,
			rescheduleTo: "dev-us-west-2-f02",
			scheduled:    "",
			infraObjs: []k8sruntime.Object{
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
						Labels: map[string]string{
							cloudv1.ProviderTypeLabel:        types.ProviderTypeAWSSharedTier,
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
						Annotations: map[string]string{
							"test": "test",
						},
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
					},
				},
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test-xxx",
						Name:      "test-xxx",
						Labels: map[string]string{
							cloudv1.ProviderTypeLabel:        types.ProviderTypeAWSSharedTier,
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
					},
					Spec: cloudv1.ClusterSpec{
						NetworkName: "test",
					},
				},
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "test",
						Name:      "test",
					},
					Spec: cloudv1.ClusterSpec{
						NetworkName: "test",
					},
				},
			},
		},
	}
	for _, c := range cases {
		t.Log("test", c.desc)

		d := newFakeClusterDispatcher(objs, c.infraObjs, c.threshold)
		r := newFakeReconciler(d, c.infraObjs)
		req := ctrl.Request{
			NamespacedName: apitypes.NamespacedName{
				Namespace: "test",
				Name:      "test",
			},
		}
		_, err := r.Reconcile(context.Background(), req)
		if c.hasError {
			assert.Error(t, err)
			continue // skip following asserts
		}
		assert.NoError(t, err)

		net := &cloudv1.Network{}
		err = r.InfraClient.Get(context.Background(), client.ObjectKey{Namespace: "test", Name: "test"}, net)
		assert.NoError(t, err)

		cluster := &cloudv1.Cluster{}
		err = r.InfraClient.Get(context.Background(), client.ObjectKey{Namespace: "test", Name: "test"}, cluster)
		assert.NoError(t, err)

		if c.rescheduleTo != "" {
			// FIXME(liubo02): Patch is not worked and I don't know why
			// assert.Equal(t, c.rescheduleTo, net.Annotations[types.AnnoKeyReschedule])
		}

		assert.Equal(t, c.scheduled, cluster.Labels[labels.LabelKeyKubernetesCluster])
	}
}

func TestReconcileOthers(t *testing.T) {
	cases := []struct {
		namespace    string
		name         string
		resourceType string
		hasError     bool
		found        bool
		scheduled    bool

		infraObjs []k8sruntime.Object
	}{
		{
			namespace:    "ns5",
			name:         "backup-without-cluster",
			resourceType: TypeBackup,
			hasError:     false, // no error for this case now
			found:        true,
			scheduled:    false,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Backup{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns5",
						Name:      "backup-without-cluster",
					},
				},
			},
		},
		{
			namespace:    "ns6",
			name:         "backup-without-network",
			resourceType: TypeBackup,
			hasError:     true,
			found:        false,
			scheduled:    false,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Backup{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns6",
						Name:      "backup-without-network",
					},
					Spec: cloudv1.BackupSpec{ClusterName: "cluster-backup"},
				},
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns6",
						Name:      "cluster-backup",
					},
				},
			},
		},
		{
			namespace:    "ns7",
			name:         "backup",
			resourceType: TypeBackup,
			hasError:     false,
			found:        true,
			scheduled:    true,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Backup{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns7",
						Name:      "backup",
					},
					Spec: cloudv1.BackupSpec{ClusterName: "cluster-backup"},
				},
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns7",
						Name:      "cluster-backup",
					},
					Spec: cloudv1.ClusterSpec{NetworkName: "network-backup"},
				},
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns7",
						Name:      "network-backup",
						Labels: map[string]string{
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
					},
				},
			},
		},
		{
			namespace:    "ns8",
			name:         "restore",
			resourceType: TypeRestore,
			hasError:     false,
			found:        true,
			scheduled:    true,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Restore{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns8",
						Name:      "restore",
					},
					Spec: cloudv1.RestoreSpec{ClusterName: "cluster-restore"},
				},
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns8",
						Name:      "cluster-restore",
					},
					Spec: cloudv1.ClusterSpec{NetworkName: "network-restore"},
				},
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns8",
						Name:      "network-restore",
						Labels: map[string]string{
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
					},
				},
			},
		},
		{
			namespace:    "ns9",
			name:         "import",
			resourceType: TypeImport,
			hasError:     false,
			found:        true,
			scheduled:    true,
			infraObjs: []k8sruntime.Object{
				&cloudv1.Import{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns9",
						Name:      "import",
					},
					Spec: cloudv1.ImportSpec{ClusterName: "cluster-import"},
				},
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns9",
						Name:      "cluster-import",
					},
					Spec: cloudv1.ClusterSpec{NetworkName: "network-import"},
				},
				&cloudv1.Network{
					ObjectMeta: metav1.ObjectMeta{
						Namespace: "ns9",
						Name:      "network-import",
						Labels: map[string]string{
							labels.LabelKeyKubernetesCluster: kubeClusterName,
						},
					},
					Spec: cloudv1.NetworkSpec{
						Provider: types.ProviderTypeAWSSharedTier,
						Region:   region,
					},
				},
			},
		},
	}

	for _, c := range cases {
		t.Log("test", c.name)

		d, err := newFakeOthersDispatcher(c.resourceType, c.infraObjs)
		assert.NoError(t, err)
		r := newFakeReconciler(d, c.infraObjs)

		req := ctrl.Request{
			NamespacedName: apitypes.NamespacedName{
				Namespace: c.namespace,
				Name:      c.name,
			},
		}
		_, err = r.Reconcile(context.Background(), req)
		if c.hasError {
			assert.Error(t, err)
			continue // skip following asserts
		}
		assert.NoError(t, err)

		var scheduledTo string
		objKey := client.ObjectKey{Namespace: c.namespace, Name: c.name}
		switch c.resourceType {
		case TypeBackup:
			backupGet := &cloudv1.Backup{}
			err = r.InfraClient.Get(context.Background(), objKey, backupGet)
			if c.found {
				assert.NoError(t, err)
				scheduledTo = backupGet.Labels[labels.LabelKeyKubernetesCluster]
			} else {
				assert.True(t, apierrors.IsNotFound(err))
			}
		case TypeRestore:
			restoreGet := &cloudv1.Restore{}
			err = r.InfraClient.Get(context.Background(), objKey, restoreGet)
			if c.found {
				assert.NoError(t, err)
				scheduledTo = restoreGet.Labels[labels.LabelKeyKubernetesCluster]
			} else {
				assert.True(t, apierrors.IsNotFound(err))
			}
		case TypeImport:
			importGet := &cloudv1.Import{}
			err = r.InfraClient.Get(context.Background(), objKey, importGet)
			if c.found {
				assert.NoError(t, err)
				scheduledTo = importGet.Labels[labels.LabelKeyKubernetesCluster]
			} else {
				assert.True(t, apierrors.IsNotFound(err))
			}
		}

		if c.scheduled {
			assert.Equal(t, kubeClusterName, scheduledTo)
		} else {
			assert.Equal(t, "", scheduledTo)
		}
	}
}

func newFakeReconciler(dispatcher Dispatcher, infraObjs []k8sruntime.Object) *Reconciler {
	fakeInfraClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(infraObjs...).
		Build()
	fakeInfraRecorder := record.NewFakeRecorder(recorderBufSize)
	fakeInfraCache := fakeCache{
		Reader:    fakeInfraClient,
		Informers: &informertest.FakeInformers{},
	}

	return &Reconciler{
		Logger:        logr.Discard(),
		InfraClient:   fakeInfraClient,
		InfraCache:    fakeInfraCache,
		InfraRecorder: fakeInfraRecorder,
		d:             dispatcher,
	}
}

func newFakeNetworkDispatcher(objs, infraObjs []k8sruntime.Object, maxPerKubeCluster int) Dispatcher {
	fakeInfraClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(infraObjs...).
		Build()
	fakeInfraRecorder := record.NewFakeRecorder(recorderBufSize)
	fakeInfraCache := fakeCache{
		Reader:    fakeInfraClient,
		Informers: &informertest.FakeInformers{},
	}

	fakeClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(objs...).
		Build()
	fCache := fakeCache{
		Reader:    fakeClient,
		Informers: &informertest.FakeInformers{},
	}

	return &network{
		Logger:                        logr.Discard(),
		InfraClient:                   fakeInfraClient,
		InfraCache:                    fakeInfraCache,
		InfraRecorder:                 fakeInfraRecorder,
		Cache:                         fCache,
		KubeClusterConfigMapName:      kubeClusterConfigMapName,
		KubeClusterConfigMapNamespace: kubeClusterConfigMapNamespace,
		MaxPerKubeCluster:             maxPerKubeCluster,
	}
}

func newFakeOthersDispatcher(resourceType string, infraObjs []k8sruntime.Object) (Dispatcher, error) {
	fakeInfraClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(infraObjs...).
		Build()
	fakeInfraRecorder := record.NewFakeRecorder(recorderBufSize)
	fakeInfraCache := fakeCache{
		Reader:    fakeInfraClient,
		Informers: &informertest.FakeInformers{},
	}

	d := &others{
		Logger:        logr.Discard(),
		InfraClient:   fakeInfraClient,
		InfraCache:    fakeInfraCache,
		InfraRecorder: fakeInfraRecorder,
		newFn:         nil,
		getFn:         nil,
	}
	err := d.attachNewGetFn(resourceType)
	return d, err
}

func newFakeClusterDispatcher(objs, infraObjs []k8sruntime.Object, threshold int) Dispatcher {
	fakeInfraClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(infraObjs...).
		Build()
	fakeInfraRecorder := record.NewFakeRecorder(recorderBufSize)
	fakeInfraCache := fakeCache{
		Reader:    fakeInfraClient,
		Informers: &informertest.FakeInformers{},
	}

	fakeClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithRuntimeObjects(objs...).
		Build()
	fCache := fakeCache{
		Reader:    fakeClient,
		Informers: &informertest.FakeInformers{},
	}

	return &cluster{
		Logger:                        logr.Discard(),
		InfraClient:                   fakeInfraClient,
		InfraCache:                    fakeInfraCache,
		InfraRecorder:                 fakeInfraRecorder,
		Cache:                         fCache,
		KubeClusterConfigMapName:      kubeClusterConfigMapName,
		KubeClusterConfigMapNamespace: kubeClusterConfigMapNamespace,
		RescheduleThreshold:           threshold,
	}
}

type fakeCache struct {
	client.Reader
	cache.Informers
}
