Skip to content

Commit 450f3d9

Browse files
authored
Merge pull request #6916 from zhzhuang-zju/operatortest
unit operator CI and E2E
2 parents 4b5aa6b + afd31ee commit 450f3d9

File tree

9 files changed

+192
-87
lines changed

9 files changed

+192
-87
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -163,62 +163,6 @@ jobs:
163163
name: karmada_kind_log_${{ matrix.k8s }}
164164
path: /tmp/karmada/
165165

166-
e2e-operator:
167-
name: operator e2e test
168-
needs: build
169-
runs-on: ubuntu-22.04
170-
strategy:
171-
fail-fast: false
172-
matrix:
173-
# Here support the latest three minor releases of Kubernetes, this can be considered to be roughly
174-
# the same as the End of Life of the Kubernetes release: https://kubernetes.io/releases/
175-
# Please remember to update the CI Schedule Workflow when we add a new version.
176-
k8s: [ v1.31.0, v1.32.0, v1.33.0 ]
177-
steps:
178-
# Free up disk space on Ubuntu
179-
- name: Free Disk Space (Ubuntu)
180-
uses: jlumbroso/free-disk-space@main
181-
with:
182-
# this might remove tools that are actually needed, if set to "true" but frees about 6 GB
183-
tool-cache: false
184-
# all of these default to true, but feel free to set to "false" if necessary for your workflow
185-
android: true
186-
dotnet: true
187-
haskell: true
188-
large-packages: false
189-
docker-images: false
190-
swap-storage: false
191-
- name: checkout code
192-
uses: actions/checkout@v5
193-
with:
194-
# Number of commits to fetch. 0 indicates all history for all branches and tags.
195-
# We need to guess version via git tags.
196-
fetch-depth: 0
197-
- name: install Go
198-
uses: actions/setup-go@v6
199-
with:
200-
go-version-file: go.mod
201-
- name: setup operator e2e test environment
202-
run: |
203-
export CLUSTER_VERSION=kindest/node:${{ matrix.k8s }}
204-
hack/operator-e2e-environment.sh
205-
- name: run e2e
206-
run: |
207-
export ARTIFACTS_PATH=${{ github.workspace }}/karmada-operator-e2e-logs/${{ matrix.k8s }}/
208-
hack/run-e2e-operator.sh
209-
- name: upload logs
210-
if: always()
211-
uses: actions/upload-artifact@v5
212-
with:
213-
name: karmada_operator_e2e_log_${{ matrix.k8s }}
214-
path: ${{ github.workspace }}/karmada-operator-e2e-logs/${{ matrix.k8s }}/
215-
- name: upload kind logs
216-
if: always()
217-
uses: actions/upload-artifact@v5
218-
with:
219-
name: karmada_operator_kind_log_${{ matrix.k8s }}
220-
path: /tmp/karmada/
221-
222166
e2e-init:
223167
name: init e2e test
224168
needs: build

.github/workflows/installation-operator.yaml

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,34 +49,23 @@ jobs:
4949
uses: actions/setup-go@v6
5050
with:
5151
go-version-file: go.mod
52-
- name: setup operator test environment
52+
- name: setup operator e2e test environment
5353
run: |
5454
export CLUSTER_VERSION=kindest/node:${{ matrix.k8s }}
55-
hack/local-up-karmada-by-operator.sh
56-
- name: run operator test
55+
hack/operator-e2e-environment.sh
56+
- name: run e2e
5757
run: |
58-
# run a single e2e
59-
export KUBECONFIG=${HOME}/.kube/karmada.config
60-
kubectl config use-context karmada-apiserver
61-
GO111MODULE=on go install github.com/onsi/ginkgo/v2/ginkgo
62-
ginkgo -v --race --trace -p --focus="[BasicPropagation] propagation testing deployment propagation testing" ./test/e2e/suites/base
63-
- name: export logs
64-
if: always()
65-
run: |
66-
export ARTIFACTS_PATH=${{ github.workspace }}/karmada-operator-test-logs/${{ matrix.k8s }}/
67-
mkdir -p $ARTIFACTS_PATH
68-
69-
mkdir -p $ARTIFACTS_PATH/karmada-host
70-
kind export logs --name=karmada-host $ARTIFACTS_PATH/karmada-host
58+
export ARTIFACTS_PATH=${{ github.workspace }}/karmada-operator-e2e-logs/${{ matrix.k8s }}/
59+
hack/run-e2e-operator.sh
7160
- name: upload logs
7261
if: always()
7362
uses: actions/upload-artifact@v5
7463
with:
75-
name: karmada_operator_test_logs_${{ matrix.k8s }}
76-
path: ${{ github.workspace }}/karmada-operator-test-logs/${{ matrix.k8s }}/
64+
name: karmada_operator_e2e_log_${{ matrix.k8s }}
65+
path: ${{ github.workspace }}/karmada-operator-e2e-logs/${{ matrix.k8s }}/
7766
- name: upload kind logs
7867
if: always()
7968
uses: actions/upload-artifact@v5
8069
with:
81-
name: karmada_kind_log_${{ matrix.k8s }}
70+
name: karmada_operator_kind_log_${{ matrix.k8s }}
8271
path: /tmp/karmada/

hack/run-e2e-operator.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ GO111MODULE=on go install github.com/onsi/ginkgo/v2/ginkgo
4242

4343
# Run e2e
4444
export KUBECONFIG=${HOST_KUBECONFIG}
45+
export PUSH_MODE_CLUSTER_NAME=${MEMBER_CLUSTER_1_NAME:-"member1"}
46+
export PUSH_MODE_KUBECONFIG_PATH=${PUSH_MODE_KUBECONFIG_PATH:-"$KUBECONFIG_PATH/members.config"}
4547

4648
set +e
4749
ginkgo -v --race --trace --fail-fast -p --randomize-all ./test/e2e/suites/operator

test/e2e/suites/operator/api_sidecar_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ var _ = ginkgo.Describe("API server sidecar configuration testing", func() {
6666
})
6767

6868
ginkgo.By("Check if API server sidecar configuration works", func() {
69-
apiserver, err = kubeClient.AppsV1().Deployments(testNamespace).Get(context.TODO(), karmadaName+"-apiserver", metav1.GetOptions{})
69+
apiserver, err = hostClient.AppsV1().Deployments(testNamespace).Get(context.TODO(), karmadaName+"-apiserver", metav1.GetOptions{})
7070
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
7171
containers := apiserver.Spec.Template.Spec.Containers
7272
gomega.Expect(len(containers)).Should(gomega.Equal(2))
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
Copyright 2025 The Karmada Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
"time"
24+
25+
"github.com/onsi/ginkgo/v2"
26+
"github.com/onsi/gomega"
27+
appsv1 "k8s.io/api/apps/v1"
28+
corev1 "k8s.io/api/core/v1"
29+
"k8s.io/apimachinery/pkg/api/meta"
30+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/util/rand"
32+
"k8s.io/client-go/kubernetes"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
35+
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
36+
"github.com/karmada-io/karmada/operator/pkg/constants"
37+
operatorutil "github.com/karmada-io/karmada/operator/pkg/util"
38+
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
39+
policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
40+
karmada "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
41+
"github.com/karmada-io/karmada/pkg/util/gclient"
42+
"github.com/karmada-io/karmada/test/e2e/framework"
43+
testhelper "github.com/karmada-io/karmada/test/helper"
44+
)
45+
46+
const karmadactlTimeout = time.Second * 60
47+
48+
var karmadaContext = fmt.Sprintf("%s@%s", constants.UserName, constants.ClusterName)
49+
50+
var _ = ginkgo.Describe("Base E2E: deploy a karmada instance and do a propagation testing", func() {
51+
var karmadaName string
52+
var controlPlaneConfig client.Client
53+
var kubeClient kubernetes.Interface
54+
var karmadaClient karmada.Interface
55+
var karmadaConfigFilePath string
56+
57+
var pushModeClusterName string
58+
var pushModeKubeConfigPath string
59+
var pushModeClusterClient kubernetes.Interface
60+
var targetClusters []string
61+
62+
var deploymentNamespace string
63+
var deploymentName string
64+
var policyName string
65+
66+
ginkgo.Context("Karmada Operator base testing", func() {
67+
ginkgo.BeforeEach(func() {
68+
karmadaName = KarmadaInstanceNamePrefix + rand.String(RandomStrLength)
69+
InitializeKarmadaInstance(operatorClient, testNamespace, karmadaName, func(karmada *operatorv1alpha1.Karmada) {
70+
karmada.Spec.Components.KarmadaAPIServer.ServiceType = corev1.ServiceTypeNodePort
71+
})
72+
})
73+
74+
ginkgo.BeforeEach(func() {
75+
homeDir := os.Getenv("HOME")
76+
karmadaConfigFilePath = fmt.Sprintf("%s/.kube/karmada-%s.config", homeDir, karmadaName)
77+
secret, err := hostClient.CoreV1().Secrets(testNamespace).Get(context.Background(), operatorutil.AdminKarmadaConfigSecretName(karmadaName), metav1.GetOptions{})
78+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
79+
karmadaConfigBytes := secret.Data["karmada.config"]
80+
err = writeKubeConfigToFile(karmadaConfigBytes, karmadaConfigFilePath)
81+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
82+
karmadaRestConfig, err := framework.LoadRESTClientConfig(karmadaConfigFilePath, karmadaContext)
83+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
84+
controlPlaneConfig = gclient.NewForConfigOrDie(karmadaRestConfig)
85+
kubeClient = kubernetes.NewForConfigOrDie(karmadaRestConfig)
86+
karmadaClient = karmada.NewForConfigOrDie(karmadaRestConfig)
87+
88+
pushModeClusterName = os.Getenv("PUSH_MODE_CLUSTER_NAME")
89+
pushModeKubeConfigPath = os.Getenv("PUSH_MODE_KUBECONFIG_PATH")
90+
pushModeClusterRestConfig, err := framework.LoadRESTClientConfig(pushModeKubeConfigPath, pushModeClusterName)
91+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
92+
pushModeClusterClient = kubernetes.NewForConfigOrDie(pushModeClusterRestConfig)
93+
94+
targetClusters = []string{pushModeClusterName}
95+
})
96+
97+
ginkgo.AfterEach(func() {
98+
// Clean up resources
99+
framework.RemoveDeployment(kubeClient, deploymentNamespace, deploymentName)
100+
framework.RemovePropagationPolicy(karmadaClient, deploymentNamespace, policyName)
101+
framework.RemoveNamespace(kubeClient, deploymentNamespace)
102+
103+
// Unjoin the push mode cluster
104+
cmd := framework.NewKarmadactlCommand(karmadaConfigFilePath, karmadaContext, karmadactlPath, "", karmadactlTimeout,
105+
"unjoin", "--cluster-kubeconfig", pushModeKubeConfigPath, "--cluster-context", pushModeClusterName, "--cluster-namespace", "karmada-cluster",
106+
"--v", "4", pushModeClusterName)
107+
_, err := cmd.ExecOrDie()
108+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
109+
110+
// Delete the karmada instance
111+
err = operatorClient.OperatorV1alpha1().Karmadas(testNamespace).Delete(context.Background(), karmadaName, metav1.DeleteOptions{})
112+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
113+
os.Remove(karmadaConfigFilePath)
114+
})
115+
116+
ginkgo.It("Deploy a karmada instance and do a propagation testing", func() {
117+
ginkgo.By("join a push mode cluster", func() {
118+
cmd := framework.NewKarmadactlCommand(karmadaConfigFilePath, karmadaContext, karmadactlPath, "", karmadactlTimeout, "join",
119+
"--cluster-kubeconfig", pushModeKubeConfigPath, "--cluster-context", pushModeClusterName, "--cluster-namespace", "karmada-cluster",
120+
"--v", "4", pushModeClusterName)
121+
_, err := cmd.ExecOrDie()
122+
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
123+
})
124+
125+
ginkgo.By("Wait for the new cluster to be ready", func() {
126+
framework.WaitClusterFitWith(controlPlaneConfig, pushModeClusterName, func(cluster *clusterv1alpha1.Cluster) bool {
127+
return meta.IsStatusConditionPresentAndEqual(cluster.Status.Conditions, clusterv1alpha1.ClusterConditionReady, metav1.ConditionTrue)
128+
})
129+
})
130+
131+
ginkgo.By("Do a simple propagation testing", func() {
132+
deploymentNamespace = "deployment-" + rand.String(RandomStrLength)
133+
framework.CreateNamespace(kubeClient, testhelper.NewNamespace(deploymentNamespace))
134+
135+
policyName = "deployment" + rand.String(RandomStrLength)
136+
deploymentName = policyName
137+
138+
deployment := testhelper.NewDeployment(deploymentNamespace, deploymentName)
139+
policy := testhelper.NewPropagationPolicy(deploymentNamespace, policyName, []policyv1alpha1.ResourceSelector{
140+
{
141+
APIVersion: deployment.APIVersion,
142+
Kind: deployment.Kind,
143+
Name: deployment.Name,
144+
},
145+
}, policyv1alpha1.Placement{
146+
ClusterAffinity: &policyv1alpha1.ClusterAffinity{
147+
ClusterNames: targetClusters,
148+
},
149+
})
150+
151+
framework.CreateDeployment(kubeClient, deployment)
152+
framework.CreatePropagationPolicy(karmadaClient, policy)
153+
framework.WaitDeploymentFitWith(pushModeClusterClient, deployment.Namespace, deployment.Name,
154+
func(*appsv1.Deployment) bool {
155+
return true
156+
})
157+
})
158+
})
159+
})
160+
})
161+
162+
func writeKubeConfigToFile(config []byte, filePath string) error {
163+
// Write the config to the specified file path
164+
err := os.WriteFile(filePath, config, 0600)
165+
if err != nil {
166+
return fmt.Errorf("failed to write kubeconfig to file: %v", err)
167+
}
168+
return nil
169+
}

test/e2e/suites/operator/priorityclass_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ var _ = ginkgo.Describe("PriorityClass configuration testing", func() {
4747
ginkgo.It("Custom priorityClass configuration", func() {
4848
ginkgo.By("Check if default value is system-node-critical", func() {
4949
// take etcd as a representative of StatefulSet.
50-
etcd, err := kubeClient.AppsV1().StatefulSets(testNamespace).Get(context.TODO(), karmadaName+"-etcd", metav1.GetOptions{})
50+
etcd, err := hostClient.AppsV1().StatefulSets(testNamespace).Get(context.Background(), karmadaName+"-etcd", metav1.GetOptions{})
5151
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
5252
gomega.Expect(etcd.Spec.Template.Spec.PriorityClassName).Should(gomega.Equal("system-node-critical"))
5353

5454
// take karmada-apiserver as a representative of Deployment.
55-
karmadaApiserver, err := kubeClient.AppsV1().Deployments(testNamespace).Get(context.TODO(), karmadaName+"-apiserver", metav1.GetOptions{})
55+
karmadaApiserver, err := hostClient.AppsV1().Deployments(testNamespace).Get(context.TODO(), karmadaName+"-apiserver", metav1.GetOptions{})
5656
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
5757
gomega.Expect(karmadaApiserver.Spec.Template.Spec.PriorityClassName).Should(gomega.Equal("system-node-critical"))
5858
})
@@ -68,12 +68,12 @@ var _ = ginkgo.Describe("PriorityClass configuration testing", func() {
6868

6969
ginkgo.By("Check if the PriorityClass is applied correctly", func() {
7070
// take etcd as a representative of StatefulSet.
71-
etcd, err := kubeClient.AppsV1().StatefulSets(testNamespace).Get(context.TODO(), karmadaName+"-etcd", metav1.GetOptions{ResourceVersion: "0"})
71+
etcd, err := hostClient.AppsV1().StatefulSets(testNamespace).Get(context.TODO(), karmadaName+"-etcd", metav1.GetOptions{ResourceVersion: "0"})
7272
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
7373
gomega.Expect(etcd.Spec.Template.Spec.PriorityClassName).Should(gomega.Equal("system-cluster-critical"))
7474

7575
// take karmada-apiserver as a representative of Deployment.
76-
karmadaApiserver, err := kubeClient.AppsV1().Deployments(testNamespace).Get(context.TODO(), karmadaName+"-apiserver", metav1.GetOptions{ResourceVersion: "0"})
76+
karmadaApiserver, err := hostClient.AppsV1().Deployments(testNamespace).Get(context.TODO(), karmadaName+"-apiserver", metav1.GetOptions{ResourceVersion: "0"})
7777
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
7878
gomega.Expect(karmadaApiserver.Spec.Template.Spec.PriorityClassName).Should(gomega.Equal("system-cluster-critical"))
7979
})

test/e2e/suites/operator/status_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ var _ = ginkgo.Describe("Status testing", func() {
7070
gomega.Expect(secretRef).ShouldNot(gomega.BeNil())
7171
gomega.Expect(secretRef.Namespace).Should(gomega.Equal(karmadaObject.GetNamespace()))
7272
gomega.Expect(secretRef.Name).Should(gomega.Equal(operatorutil.AdminKarmadaConfigSecretName(karmadaObject.GetName())))
73-
_, err := kubeClient.CoreV1().Secrets(secretRef.Namespace).Get(context.TODO(), secretRef.Name, metav1.GetOptions{})
73+
_, err := hostClient.CoreV1().Secrets(secretRef.Namespace).Get(context.Background(), secretRef.Name, metav1.GetOptions{})
7474
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
7575
})
7676

7777
ginkgo.By("Check if the status.apiServerService can ref to the right service", func() {
7878
apiServerService := karmadaObject.Status.APIServerService
7979
gomega.Expect(apiServerService).ShouldNot(gomega.BeNil())
8080
gomega.Expect(apiServerService.Name).Should(gomega.Equal(operatorutil.KarmadaAPIServerName(karmadaObject.GetName())))
81-
_, err := kubeClient.CoreV1().Services(karmadaObject.GetNamespace()).Get(context.TODO(), apiServerService.Name, metav1.GetOptions{})
81+
_, err := hostClient.CoreV1().Services(karmadaObject.GetNamespace()).Get(context.TODO(), apiServerService.Name, metav1.GetOptions{})
8282
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
8383
})
8484
})

test/e2e/suites/operator/suite_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ var (
5858
kubeconfig string
5959
karmadactlPath string
6060
restConfig *rest.Config
61-
kubeClient kubernetes.Interface
61+
hostClient kubernetes.Interface
6262
testNamespace string
6363
operatorClient operator.Interface
6464
)
@@ -93,11 +93,11 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
9393
restConfig, err = framework.LoadRESTClientConfig(kubeconfig, hostContext)
9494
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
9595

96-
kubeClient, err = kubernetes.NewForConfig(restConfig)
96+
hostClient, err = kubernetes.NewForConfig(restConfig)
9797
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
9898

9999
testNamespace = fmt.Sprintf("operatortest-%s", rand.String(RandomStrLength))
100-
err = setupTestNamespace(testNamespace, kubeClient)
100+
err = setupTestNamespace(testNamespace, hostClient)
101101
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
102102

103103
operatorClient, err = operator.NewForConfig(restConfig)
@@ -107,7 +107,7 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
107107
var _ = ginkgo.SynchronizedAfterSuite(func() {
108108
// cleanup all namespaces we created both in control plane and member clusters.
109109
// It will not return error even if there is no such namespace in there that may happen in case setup failed.
110-
err := cleanupTestNamespace(testNamespace, kubeClient)
110+
err := cleanupTestNamespace(testNamespace, hostClient)
111111
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
112112
}, func() {})
113113

test/helper/karmada.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ func NewKarmada(namespace string, name string) *operatorv1alpha1.Karmada {
3535
HTTPSource: &operatorv1alpha1.HTTPSource{URL: "http://local"},
3636
},
3737
Components: &operatorv1alpha1.KarmadaComponents{
38-
Etcd: &operatorv1alpha1.Etcd{},
38+
Etcd: &operatorv1alpha1.Etcd{},
39+
KarmadaAPIServer: &operatorv1alpha1.KarmadaAPIServer{},
3940
KarmadaAggregatedAPIServer: &operatorv1alpha1.KarmadaAggregatedAPIServer{
4041
CommonSettings: operatorv1alpha1.CommonSettings{
4142
Image: operatorv1alpha1.Image{

0 commit comments

Comments
 (0)