Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkg/utils/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@ func ShouldPropagateObj(informerManager informer.Manager, uObj *unstructured.Uns
if secret.Type == corev1.SecretTypeServiceAccountToken {
return false, nil
}
case corev1.SchemeGroupVersion.WithKind("PersistentVolumeClaim"):
// Skip PersistentVolumeClaims to avoid conflicts with the PVCs created by statefulset controller
return false, nil
case corev1.SchemeGroupVersion.WithKind("Endpoints"):
// we assume that all endpoints with the same name of a service is created by the service controller
if _, err := informerManager.Lister(ServiceGVR).ByNamespace(uObj.GetNamespace()).Get(uObj.GetName()); err != nil {
Expand Down
13 changes: 13 additions & 0 deletions pkg/utils/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,19 @@ func TestShouldPropagateObj_PodAndReplicaSet(t *testing.T) {
ownerReferences: nil,
want: true,
},
{
name: "PersistentVolumeClaim should NOT propagate",
obj: map[string]interface{}{
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"metadata": map[string]interface{}{
"name": "test-pvc",
"namespace": "default",
},
},
ownerReferences: nil,
want: false,
},
}

for _, tt := range tests {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/enveloped_object_placement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ var _ = Describe("placing wrapped resources using a CRP", func() {
// read the test resources.
readDeploymentTestManifest(&testDeployment)
readDaemonSetTestManifest(&testDaemonSet)
readStatefulSetTestManifest(&testStatefulSet, true)
readStatefulSetTestManifest(&testStatefulSet, StatefulSetInvalidStorage)
readEnvelopeResourceTestManifest(&testResourceEnvelope)
})

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/placement_selecting_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1392,11 +1392,11 @@ var _ = Describe("creating CRP and checking selected resources order", Ordered,

It("should update CRP status with the correct order of the selected resources", func() {
// Define the expected resources in order
// Note: PVCs are not propagated, so they should not appear in selected resources
expectedResources := []placementv1beta1.ResourceIdentifier{
{Kind: "Namespace", Name: nsName, Version: "v1"},
{Kind: "Secret", Name: secret.Name, Namespace: nsName, Version: "v1"},
{Kind: "ConfigMap", Name: configMap.Name, Namespace: nsName, Version: "v1"},
{Kind: "PersistentVolumeClaim", Name: pvc.Name, Namespace: nsName, Version: "v1"},
{Group: "rbac.authorization.k8s.io", Kind: "Role", Name: role.Name, Namespace: nsName, Version: "v1"},
}

Expand Down
75 changes: 74 additions & 1 deletion test/e2e/resource_placement_hub_workload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package e2e
import (
"fmt"

"github.com/google/go-cmp/cmp"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
Expand All @@ -37,12 +38,14 @@ var _ = Describe("placing workloads using a CRP with PickAll policy", Label("res
var testDeployment appsv1.Deployment
var testDaemonSet appsv1.DaemonSet
var testJob batchv1.Job
var testStatefulSet appsv1.StatefulSet

BeforeAll(func() {
// Read the test manifests
readDeploymentTestManifest(&testDeployment)
readDaemonSetTestManifest(&testDaemonSet)
readJobTestManifest(&testJob)
readStatefulSetTestManifest(&testStatefulSet, StatefulSetWithStorage)
workNamespace := appNamespace()

// Create namespace and workloads
Expand All @@ -51,9 +54,11 @@ var _ = Describe("placing workloads using a CRP with PickAll policy", Label("res
testDeployment.Namespace = workNamespace.Name
testDaemonSet.Namespace = workNamespace.Name
testJob.Namespace = workNamespace.Name
testStatefulSet.Namespace = workNamespace.Name
Expect(hubClient.Create(ctx, &testDeployment)).To(Succeed(), "Failed to create test deployment %s", testDeployment.Name)
Expect(hubClient.Create(ctx, &testDaemonSet)).To(Succeed(), "Failed to create test daemonset %s", testDaemonSet.Name)
Expect(hubClient.Create(ctx, &testJob)).To(Succeed(), "Failed to create test job %s", testJob.Name)
Expect(hubClient.Create(ctx, &testStatefulSet)).To(Succeed(), "Failed to create test statefulset %s", testStatefulSet.Name)

// Create the CRP that selects the namespace
By("creating CRP that selects the namespace")
Expand Down Expand Up @@ -105,9 +110,16 @@ var _ = Describe("placing workloads using a CRP with PickAll policy", Label("res
Name: testJob.Name,
Namespace: workNamespace.Name,
},
{
Group: "apps",
Version: "v1",
Kind: "StatefulSet",
Name: testStatefulSet.Name,
Namespace: workNamespace.Name,
},
}
// Use customizedPlacementStatusUpdatedActual with resourceIsTrackable=false
// because Jobs don't have availability tracking like Deployments/DaemonSets do
// because Jobs don't have availability tracking like Deployments/DaemonSets/StatefulSets do
crpKey := types.NamespacedName{Name: crpName}
crpStatusUpdatedActual := customizedPlacementStatusUpdatedActual(crpKey, wantSelectedResources, allMemberClusterNames, nil, "0", false)
Eventually(crpStatusUpdatedActual, workloadEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update CRP status as expected")
Expand Down Expand Up @@ -170,6 +182,13 @@ var _ = Describe("placing workloads using a CRP with PickAll policy", Label("res
"Hub job should complete successfully")
})

It("should verify hub statefulset is ready", func() {
By("checking hub statefulset status")
statefulSetReadyActual := waitForStatefulSetToBeReady(hubClient, &testStatefulSet)
Eventually(statefulSetReadyActual, workloadEventuallyDuration, eventuallyInterval).Should(Succeed(),
"Hub statefulset should be ready before placement")
})

It("should place the deployment on all member clusters", func() {
By("verifying deployment is placed and ready on all member clusters")
for idx := range allMemberClusters {
Expand Down Expand Up @@ -206,6 +225,24 @@ var _ = Describe("placing workloads using a CRP with PickAll policy", Label("res
}
})

It("should place the statefulset on all member clusters", func() {
By("verifying statefulset is placed and ready on all member clusters")
for idx := range allMemberClusters {
memberCluster := allMemberClusters[idx]
statefulsetPlacedActual := waitForStatefulSetPlacementToReady(memberCluster, &testStatefulSet)
Eventually(statefulsetPlacedActual, workloadEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to place statefulset on member cluster %s", memberCluster.ClusterName)
}
})

It("should verify statefulset replicas are ready on all clusters", func() {
By("checking statefulset status on each cluster")
for _, cluster := range allMemberClusters {
statefulSetReadyActual := waitForStatefulSetToBeReady(cluster.KubeClient, &testStatefulSet)
Eventually(statefulSetReadyActual, workloadEventuallyDuration, eventuallyInterval).Should(Succeed(),
"StatefulSet should be ready on cluster %s", cluster.ClusterName)
}
})

It("should verify deployment replicas are ready on all clusters", func() {
By("checking deployment status on each cluster")
for _, cluster := range allMemberClusters {
Expand All @@ -232,6 +269,42 @@ var _ = Describe("placing workloads using a CRP with PickAll policy", Label("res
})
})

func waitForStatefulSetToBeReady(kubeClient client.Client, testStatefulSet *appsv1.StatefulSet) func() error {
return func() error {
var statefulSet appsv1.StatefulSet
if err := kubeClient.Get(ctx, types.NamespacedName{
Name: testStatefulSet.Name,
Namespace: testStatefulSet.Namespace,
}, &statefulSet); err != nil {
return err
}

// Verify statefulset is ready
requiredReplicas := int32(1)
if statefulSet.Spec.Replicas != nil {
requiredReplicas = *statefulSet.Spec.Replicas
}

wantStatus := appsv1.StatefulSetStatus{
ObservedGeneration: statefulSet.Generation,
CurrentReplicas: requiredReplicas,
UpdatedReplicas: requiredReplicas,
}

gotStatus := appsv1.StatefulSetStatus{
ObservedGeneration: statefulSet.Status.ObservedGeneration,
CurrentReplicas: statefulSet.Status.CurrentReplicas,
UpdatedReplicas: statefulSet.Status.UpdatedReplicas,
}

if diff := cmp.Diff(wantStatus, gotStatus); diff != "" {
return fmt.Errorf("statefulset not ready (-want +got):\n%s", diff)
}

return nil
}
}

func waitForJobToComplete(kubeClient client.Client, testJob *batchv1.Job) func() error {
return func() error {
var job batchv1.Job
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/resource_placement_rollout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ var _ = Describe("placing namespaced scoped resources using a RP with rollout",
testDaemonSet = appv1.DaemonSet{}
readDaemonSetTestManifest(&testDaemonSet)
testStatefulSet = appv1.StatefulSet{}
readStatefulSetTestManifest(&testStatefulSet, false)
readStatefulSetTestManifest(&testStatefulSet, StatefulSetBasic)
testService = corev1.Service{}
readServiceTestManifest(&testService)
testJob = batchv1.Job{}
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/resource_placement_selecting_resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1013,10 +1013,10 @@ var _ = Describe("testing RP selecting resources", Label("resourceplacement"), f

It("should update RP status with the correct order of the selected resources", func() {
// Define the expected resources in order.
// Note: PVCs are not propagated, so they should not appear in selected resources
expectedResources := []placementv1beta1.ResourceIdentifier{
{Kind: "Secret", Name: secret.Name, Namespace: nsName, Version: "v1"},
{Kind: "ConfigMap", Name: configMap.Name, Namespace: nsName, Version: "v1"},
{Kind: "PersistentVolumeClaim", Name: pvc.Name, Namespace: nsName, Version: "v1"},
{Group: "rbac.authorization.k8s.io", Kind: "Role", Name: role.Name, Namespace: nsName, Version: "v1"},
}

Expand Down
28 changes: 28 additions & 0 deletions test/e2e/resources/statefulset-with-storage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: test-ss
spec:
selector:
matchLabels:
app: test-ss
serviceName: "test-ss-svc"
replicas: 2
template:
metadata:
labels:
app: test-ss
spec:
terminationGracePeriodSeconds: 10
containers:
- name: pause
image: k8s.gcr.io/pause:3.8
volumeClaimTemplates:
- metadata:
name: test-ss-pvc
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard"
resources:
requests:
storage: 100Mi
2 changes: 1 addition & 1 deletion test/e2e/rollout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ var _ = Describe("placing wrapped resources using a CRP", Ordered, func() {

BeforeAll(func() {
// Create the test resources.
readStatefulSetTestManifest(&testStatefulSet, false)
readStatefulSetTestManifest(&testStatefulSet, StatefulSetBasic)
readEnvelopeResourceTestManifest(&testStatefulSetEnvelope)
wantSelectedResources = []placementv1beta1.ResourceIdentifier{
{
Expand Down
29 changes: 23 additions & 6 deletions test/e2e/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ import (
"github.com/kubefleet-dev/kubefleet/test/e2e/framework"
)

// StatefulSetVariant represents different StatefulSet configurations for testing
type StatefulSetVariant int

const (
// StatefulSetBasic is a StatefulSet without any persistent volume claims
StatefulSetBasic StatefulSetVariant = iota
// StatefulSetInvalidStorage is a StatefulSet with a non-existent storage class
StatefulSetInvalidStorage
// StatefulSetWithStorage is a StatefulSet with a valid standard storage class
StatefulSetWithStorage
)

var (
croTestAnnotationKey = "cro-test-annotation"
croTestAnnotationValue = "cro-test-annotation-val"
Expand Down Expand Up @@ -1537,13 +1549,18 @@ func readDaemonSetTestManifest(testDaemonSet *appsv1.DaemonSet) {
Expect(err).Should(Succeed())
}

func readStatefulSetTestManifest(testStatefulSet *appsv1.StatefulSet, withVolume bool) {
func readStatefulSetTestManifest(testStatefulSet *appsv1.StatefulSet, variant StatefulSetVariant) {
By("Read the statefulSet resource")
if withVolume {
Expect(utils.GetObjectFromManifest("resources/statefulset-with-volume.yaml", testStatefulSet)).Should(Succeed())
} else {
Expect(utils.GetObjectFromManifest("resources/test-statefulset.yaml", testStatefulSet)).Should(Succeed())
}
var manifestPath string
switch variant {
case StatefulSetBasic:
manifestPath = "resources/statefulset-basic.yaml"
case StatefulSetInvalidStorage:
manifestPath = "resources/statefulset-invalid-storage.yaml"
case StatefulSetWithStorage:
manifestPath = "resources/statefulset-with-storage.yaml"
}
Expect(utils.GetObjectFromManifest(manifestPath, testStatefulSet)).Should(Succeed())
}

func readServiceTestManifest(testService *corev1.Service) {
Expand Down
Loading