From fdf7b535861d1bb917cdec7328d59c79e67e6bba Mon Sep 17 00:00:00 2001 From: Mohamed Awnallah Date: Mon, 30 Sep 2024 22:35:41 +0300 Subject: [PATCH] operator/pkg: add test helpers Signed-off-by: Mohamed Awnallah --- operator/pkg/tasks/deinit/test_helpers.go | 62 +++++++++ operator/pkg/tasks/init/test_helpers.go | 157 ++++++++++++++++++++++ operator/pkg/util/util.go | 64 +++++++++ 3 files changed, 283 insertions(+) create mode 100644 operator/pkg/tasks/deinit/test_helpers.go create mode 100644 operator/pkg/tasks/init/test_helpers.go diff --git a/operator/pkg/tasks/deinit/test_helpers.go b/operator/pkg/tasks/deinit/test_helpers.go new file mode 100644 index 000000000000..1fe8dd754660 --- /dev/null +++ b/operator/pkg/tasks/deinit/test_helpers.go @@ -0,0 +1,62 @@ +/* +Copyright 2024 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 tasks + +import ( + clientset "k8s.io/client-go/kubernetes" +) + +// TestInterface defines the interface for retrieving test data. +type TestInterface interface { + // Get returns the data from the test instance. + Get() string +} + +// MyTestData is a struct that implements the TestInterface. +type MyTestData struct { + Data string +} + +// Get returns the data stored in the MyTestData struct. +func (m *MyTestData) Get() string { + return m.Data +} + +// TestDeInitData contains the configuration and state required to deinitialize Karmada components. +type TestDeInitData struct { + name string + namespace string + remoteClient clientset.Interface +} + +// Ensure TestDeInitData implements InitData interface at compile time. +var _ DeInitData = &TestDeInitData{} + +// GetName returns the name of the current Karmada installation. +func (t *TestDeInitData) GetName() string { + return t.name +} + +// GetNamespace returns the namespace of the current Karmada installation. +func (t *TestDeInitData) GetNamespace() string { + return t.namespace +} + +// RemoteClient returns the Kubernetes client for remote interactions. +func (t *TestDeInitData) RemoteClient() clientset.Interface { + return t.remoteClient +} diff --git a/operator/pkg/tasks/init/test_helpers.go b/operator/pkg/tasks/init/test_helpers.go new file mode 100644 index 000000000000..e480b5ff8584 --- /dev/null +++ b/operator/pkg/tasks/init/test_helpers.go @@ -0,0 +1,157 @@ +/* +Copyright 2024 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 tasks + +import ( + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + + operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1" + "github.com/karmada-io/karmada/operator/pkg/certs" +) + +// TestInterface defines the interface for retrieving test data. +type TestInterface interface { + // Get returns the data from the test instance. + Get() string +} + +// MyTestData is a struct that implements the TestInterface. +type MyTestData struct { + Data string +} + +// Get returns the data stored in the MyTestData struct. +func (m *MyTestData) Get() string { + return m.Data +} + +// TestInitData contains the configuration and state required to initialize Karmada components. +type TestInitData struct { + Name string + Namespace string + ControlplaneConfigREST *rest.Config + DataDirectory string + CrdTarballArchive operatorv1alpha1.CRDTarball + KarmadaVersionRelease string + ComponentsUnits *operatorv1alpha1.KarmadaComponents + FeatureGatesOptions map[string]bool + RemoteClientConnector clientset.Interface + KarmadaClientConnector clientset.Interface + ControlplaneAddr string + Certs []*certs.KarmadaCert +} + +// Ensure TestInitData implements InitData interface at compile time. +var _ InitData = &TestInitData{} + +// GetName returns the name of the current Karmada installation. +func (t *TestInitData) GetName() string { + return t.Name +} + +// GetNamespace returns the namespace of the current Karmada installation. +func (t *TestInitData) GetNamespace() string { + return t.Namespace +} + +// SetControlplaneConfig sets the control plane configuration for Karmada. +func (t *TestInitData) SetControlplaneConfig(config *rest.Config) { + t.ControlplaneConfigREST = config +} + +// ControlplaneConfig returns the control plane configuration. +func (t *TestInitData) ControlplaneConfig() *rest.Config { + return t.ControlplaneConfigREST +} + +// ControlplaneAddress returns the address of the control plane. +func (t *TestInitData) ControlplaneAddress() string { + return t.ControlplaneAddr +} + +// RemoteClient returns the Kubernetes client for remote interactions. +func (t *TestInitData) RemoteClient() clientset.Interface { + return t.RemoteClientConnector +} + +// KarmadaClient returns the Kubernetes client for interacting with Karmada. +func (t *TestInitData) KarmadaClient() clientset.Interface { + return t.KarmadaClientConnector +} + +// DataDir returns the data directory used by Karmada. +func (t *TestInitData) DataDir() string { + return t.DataDirectory +} + +// CrdTarball returns the CRD tarball used for Karmada installation. +func (t *TestInitData) CrdTarball() operatorv1alpha1.CRDTarball { + return t.CrdTarballArchive +} + +// KarmadaVersion returns the version of Karmada being used. +func (t *TestInitData) KarmadaVersion() string { + return t.KarmadaVersionRelease +} + +// Components returns the Karmada components used in the current installation. +func (t *TestInitData) Components() *operatorv1alpha1.KarmadaComponents { + return t.ComponentsUnits +} + +// FeatureGates returns the feature gates enabled for the current installation. +func (t *TestInitData) FeatureGates() map[string]bool { + return t.FeatureGatesOptions +} + +// AddCert adds a Karmada certificate to the TestInitData. +func (t *TestInitData) AddCert(cert *certs.KarmadaCert) { + t.Certs = append(t.Certs, cert) +} + +// GetCert retrieves a Karmada certificate by its name. +func (t *TestInitData) GetCert(name string) *certs.KarmadaCert { + for _, cert := range t.Certs { + parts := strings.Split(cert.CertName(), ".") + if parts[0] == name { + return cert + } + } + return nil +} + +// CertList returns a list of all Karmada certificates stored in TestInitData. +func (t *TestInitData) CertList() []*certs.KarmadaCert { + return t.Certs +} + +// LoadCertFromSecret loads a Karmada certificate from a Kubernetes secret. +func (t *TestInitData) LoadCertFromSecret(secret *corev1.Secret) error { + if len(secret.Data) == 0 { + return fmt.Errorf("cert data is empty") + } + + // Dummy implementation: load empty certificate. + cert := &certs.KarmadaCert{} + t.AddCert(cert) + return nil +} diff --git a/operator/pkg/util/util.go b/operator/pkg/util/util.go index 6214527211c5..99365b2d11e4 100644 --- a/operator/pkg/util/util.go +++ b/operator/pkg/util/util.go @@ -29,12 +29,28 @@ import ( "strings" "time" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/klog/v2" "sigs.k8s.io/yaml" + operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1" + "github.com/karmada-io/karmada/operator/pkg/workflow" "github.com/karmada-io/karmada/pkg/util" ) +var ( + // ClientFactory creates a new Kubernetes clientset from the provided kubeconfig. + ClientFactory = func(kubeconfig *rest.Config) (clientset.Interface, error) { + return clientset.NewForConfig(kubeconfig) + } + + // BuildClientFromSecretRefFactory constructs a Kubernetes clientset using a LocalSecretReference. + BuildClientFromSecretRefFactory = func(client clientset.Interface, ref *operatorv1alpha1.LocalSecretReference) (clientset.Interface, error) { + return BuildClientFromSecretRef(client, ref) + } +) + // Downloader Download progress type Downloader struct { io.Reader @@ -222,3 +238,51 @@ func ReplaceYamlForReg(path, destResource string, reg *regexp.Regexp) ([]byte, e repl := reg.ReplaceAllString(string(data), destResource) return yaml.YAMLToJSON([]byte(repl)) } + +// ContainAllTasks checks if all tasks in the subset are present in the tasks slice. +// Returns an error if any subset task is not found; nil otherwise. +func ContainAllTasks(tasks, subset []workflow.Task) error { + for _, subsetTask := range subset { + found := false + for _, task := range tasks { + found = DeepEqualTasks(task, subsetTask) == nil + if found { + break + } + } + if !found { + return fmt.Errorf("subset task %v not found in tasks", subsetTask) + } + } + return nil +} + +// DeepEqualTasks checks if two workflow.Task instances are deeply equal. +// It returns an error if they differ, or nil if they are equal. +// The comparison includes the task name, RunSubTasks flag, +// and the length and contents of the Tasks slice. +// Function references and behavior are not compared; only the values +// of the specified fields are considered. Any differences are detailed +// in the returned error. +func DeepEqualTasks(t1, t2 workflow.Task) error { + if t1.Name != t2.Name { + return fmt.Errorf("expected t1 name %s, but got %s", t2.Name, t1.Name) + } + + if t1.RunSubTasks != t2.RunSubTasks { + return fmt.Errorf("expected t1 RunSubTasks flag %t, but got %t", t2.RunSubTasks, t1.RunSubTasks) + } + + if len(t1.Tasks) != len(t2.Tasks) { + return fmt.Errorf("expected t1 tasks length %d, but got %d", len(t2.Tasks), len(t1.Tasks)) + } + + for index := range t1.Tasks { + err := DeepEqualTasks(t1.Tasks[index], t2.Tasks[index]) + if err != nil { + return fmt.Errorf("unexpected error; tasks are not equal at index %d: %v", index, err) + } + } + + return nil +}