Skip to content

Commit bf1e1bb

Browse files
authored
test: E2E for muliple cluster resource snapshots for same group index (#651)
1 parent 1b03afc commit bf1e1bb

File tree

4 files changed

+245
-4
lines changed

4 files changed

+245
-4
lines changed

pkg/controllers/clusterresourceplacement/controller.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ import (
3535
)
3636

3737
// The max size of an object in k8s is 1.5MB because of ETCD limit https://etcd.io/docs/v3.3/dev-guide/limit/.
38-
// We choose 1MB as the max size for all the selected resources within one clusterResourceSnapshot object.
39-
var resourceSnapshotResourceSizeLimit = 1024 * (1 << 10) // 1 MB
38+
// We choose 800KB as the soft limit for all the selected resources within one clusterResourceSnapshot object because of this test in k8s which checks
39+
// if object size is greater than 1MB https://github.com/kubernetes/kubernetes/blob/db1990f48b92d603f469c1c89e2ad36da1b74846/test/integration/master/synthetic_master_test.go#L337
40+
var resourceSnapshotResourceSizeLimit = 800 * (1 << 10) // 800KB
4041

4142
func (r *Reconciler) Reconcile(ctx context.Context, key controller.QueueKey) (ctrl.Result, error) {
4243
name, ok := key.(string)

test/e2e/placement_selecting_resources_test.go

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package e2e
77
import (
88
"fmt"
99
"strconv"
10+
"time"
1011

1112
"github.com/google/go-cmp/cmp"
1213
. "github.com/onsi/ginkgo/v2"
@@ -17,11 +18,19 @@ import (
1718
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1819
"k8s.io/apimachinery/pkg/types"
1920
"k8s.io/utils/pointer"
21+
"sigs.k8s.io/controller-runtime/pkg/client"
2022

2123
placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1"
2224
"go.goms.io/fleet/pkg/controllers/clusterresourceplacement"
2325
"go.goms.io/fleet/pkg/controllers/work"
2426
scheduler "go.goms.io/fleet/pkg/scheduler/framework"
27+
"go.goms.io/fleet/pkg/utils"
28+
"go.goms.io/fleet/test/e2e/framework"
29+
)
30+
31+
var (
32+
// we are propagating large secrets from hub to member clusters the timeout needs to be large.
33+
largeEventuallyDuration = time.Minute * 5
2534
)
2635

2736
// Note that this container will run in parallel with other containers.
@@ -1227,3 +1236,234 @@ var _ = Describe("validating CRP revision history allowing multiple revisions wh
12271236
Eventually(finalizerRemovedActual, eventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to remove controller finalizers from CRP %s", crpName)
12281237
})
12291238
})
1239+
1240+
// running spec in parallel with other specs causes timeouts.
1241+
var _ = Describe("validating CRP when selected resources cross the 1MB limit", Ordered, Serial, func() {
1242+
crpName := fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())
1243+
BeforeAll(func() {
1244+
By("creating resources for multiple resource snapshots")
1245+
createResourcesForMultipleResourceSnapshots()
1246+
1247+
// Create the CRP.
1248+
crp := &placementv1beta1.ClusterResourcePlacement{
1249+
ObjectMeta: metav1.ObjectMeta{
1250+
Name: crpName,
1251+
// Add a custom finalizer; this would allow us to better observe
1252+
// the behavior of the controllers.
1253+
Finalizers: []string{customDeletionBlockerFinalizer},
1254+
},
1255+
Spec: placementv1beta1.ClusterResourcePlacementSpec{
1256+
Policy: &placementv1beta1.PlacementPolicy{
1257+
PlacementType: placementv1beta1.PickFixedPlacementType,
1258+
ClusterNames: []string{memberCluster1EastProdName, memberCluster2EastCanaryName},
1259+
},
1260+
ResourceSelectors: []placementv1beta1.ClusterResourceSelector{
1261+
{
1262+
Group: "",
1263+
Kind: "Namespace",
1264+
Version: "v1",
1265+
Name: fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess()),
1266+
},
1267+
},
1268+
},
1269+
}
1270+
By(fmt.Sprintf("creating placement %s", crpName))
1271+
Expect(hubClient.Create(ctx, crp)).To(Succeed(), "Failed to create CRP %s", crpName)
1272+
})
1273+
1274+
AfterAll(func() {
1275+
By(fmt.Sprintf("deleting placement %s", crpName))
1276+
cleanupCRP(crpName)
1277+
1278+
By("deleting resources created for multiple resource snapshots")
1279+
cleanupWorkResources()
1280+
})
1281+
1282+
It("check if created cluster resource snapshots are as expected", func() {
1283+
Eventually(multipleResourceSnapshotsCreatedActual("2", "2", "0"), largeEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to check created cluster resource snapshots", crpName)
1284+
})
1285+
1286+
It("should update CRP status as expected", func() {
1287+
crpStatusUpdatedActual := crpStatusUpdatedActual(resourceIdentifiersForMultipleResourcesSnapshots(), []string{memberCluster1EastProdName, memberCluster2EastCanaryName}, nil, "0")
1288+
Eventually(crpStatusUpdatedActual, largeEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to update CRP %s status as expected", crpName)
1289+
})
1290+
1291+
It("should place the selected resources on member clusters", func() {
1292+
targetMemberClusters := []*framework.Cluster{memberCluster1EastProd, memberCluster2EastCanary}
1293+
checkIfPlacedWorkResourcesOnTargetMemberClusters(targetMemberClusters)
1294+
checkIfPlacedLargeSecretResourcesOnTargetMemberClusters(targetMemberClusters)
1295+
})
1296+
1297+
It("can delete the CRP", func() {
1298+
// Delete the CRP.
1299+
crp := &placementv1beta1.ClusterResourcePlacement{
1300+
ObjectMeta: metav1.ObjectMeta{
1301+
Name: crpName,
1302+
},
1303+
}
1304+
Expect(hubClient.Delete(ctx, crp)).To(Succeed(), "Failed to delete CRP %s", crpName)
1305+
})
1306+
1307+
It("should remove placed resources from all member clusters", func() {
1308+
targetMemberClusters := []*framework.Cluster{memberCluster1EastProd, memberCluster2EastCanary}
1309+
checkIfRemovedWorkResourcesFromTargetMemberClusters(targetMemberClusters)
1310+
})
1311+
1312+
It("should remove controller finalizers from CRP", func() {
1313+
finalizerRemovedActual := allFinalizersExceptForCustomDeletionBlockerRemovedFromCRPActual()
1314+
Eventually(finalizerRemovedActual, largeEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to remove controller finalizers from CRP %s", crpName)
1315+
})
1316+
})
1317+
1318+
func createResourcesForMultipleResourceSnapshots() {
1319+
createWorkResources()
1320+
1321+
for i := 0; i < 3; i++ {
1322+
var secret corev1.Secret
1323+
Expect(utils.GetObjectFromManifest("../integration/manifests/resources/test-large-secret.yaml", &secret)).Should(Succeed(), "Failed to read large secret from file")
1324+
secret.Namespace = workNamespace().Name
1325+
secret.Name = fmt.Sprintf(appSecretNameTemplate, i)
1326+
Expect(hubClient.Create(ctx, &secret)).To(Succeed(), "Failed to create secret %s/%s", secret.Name, secret.Namespace)
1327+
}
1328+
1329+
// sleep for 5 seconds to ensure secrets exist to prevent flake.
1330+
<-time.After(5 * time.Second)
1331+
}
1332+
1333+
func multipleResourceSnapshotsCreatedActual(wantTotalNumberOfResourceSnapshots, wantNumberOfMasterIndexedResourceSnapshots, wantResourceIndex string) func() error {
1334+
crpName := fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())
1335+
1336+
return func() error {
1337+
var resourceSnapshotList placementv1beta1.ClusterResourceSnapshotList
1338+
masterResourceSnapshotLabels := client.MatchingLabels{
1339+
placementv1beta1.IsLatestSnapshotLabel: strconv.FormatBool(true),
1340+
placementv1beta1.CRPTrackingLabel: crpName,
1341+
}
1342+
if err := hubClient.List(ctx, &resourceSnapshotList, masterResourceSnapshotLabels); err != nil {
1343+
return err
1344+
}
1345+
// there should be only one master resource snapshot.
1346+
if len(resourceSnapshotList.Items) != 1 {
1347+
return fmt.Errorf("number of master cluster resource snapshots has unexpected value: got %d, want %d", len(resourceSnapshotList.Items), 1)
1348+
}
1349+
masterResourceSnapshot := resourceSnapshotList.Items[0]
1350+
// labels to list all existing cluster resource snapshots.
1351+
resourceSnapshotListLabels := client.MatchingLabels{placementv1beta1.CRPTrackingLabel: crpName}
1352+
if err := hubClient.List(ctx, &resourceSnapshotList, resourceSnapshotListLabels); err != nil {
1353+
return err
1354+
}
1355+
// ensure total number of cluster resource snapshots equals to wanted number of cluster resource snapshots
1356+
if strconv.Itoa(len(resourceSnapshotList.Items)) != wantTotalNumberOfResourceSnapshots {
1357+
return fmt.Errorf("total number of cluster resource snapshots has unexpected value: got %s, want %s", strconv.Itoa(len(resourceSnapshotList.Items)), wantTotalNumberOfResourceSnapshots)
1358+
}
1359+
numberOfResourceSnapshots := masterResourceSnapshot.Annotations[placementv1beta1.NumberOfResourceSnapshotsAnnotation]
1360+
if numberOfResourceSnapshots != wantNumberOfMasterIndexedResourceSnapshots {
1361+
return fmt.Errorf("NumberOfResourceSnapshotsAnnotation in master cluster resource snapshot has unexpected value: got %s, want %s", numberOfResourceSnapshots, wantNumberOfMasterIndexedResourceSnapshots)
1362+
}
1363+
masterResourceIndex := masterResourceSnapshot.Labels[placementv1beta1.ResourceIndexLabel]
1364+
if masterResourceIndex != wantResourceIndex {
1365+
return fmt.Errorf("resource index for master cluster resource snapshot %s has unexpected value: got %s, want %s", masterResourceSnapshot.Name, masterResourceIndex, wantResourceIndex)
1366+
}
1367+
// labels to list all cluster resource snapshots with master resource index.
1368+
resourceSnapshotListLabels = client.MatchingLabels{
1369+
placementv1beta1.ResourceIndexLabel: masterResourceIndex,
1370+
placementv1beta1.CRPTrackingLabel: crpName,
1371+
}
1372+
if err := hubClient.List(ctx, &resourceSnapshotList, resourceSnapshotListLabels); err != nil {
1373+
return err
1374+
}
1375+
if strconv.Itoa(len(resourceSnapshotList.Items)) != wantNumberOfMasterIndexedResourceSnapshots {
1376+
return fmt.Errorf("number of cluster resource snapshots with master resource index has unexpected value: got %s, want %s", strconv.Itoa(len(resourceSnapshotList.Items)), wantNumberOfMasterIndexedResourceSnapshots)
1377+
}
1378+
return nil
1379+
}
1380+
}
1381+
1382+
func resourceIdentifiersForMultipleResourcesSnapshots() []placementv1beta1.ResourceIdentifier {
1383+
workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
1384+
var placementResourceIdentifiers []placementv1beta1.ResourceIdentifier
1385+
1386+
for i := 2; i >= 0; i-- {
1387+
placementResourceIdentifiers = append(placementResourceIdentifiers, placementv1beta1.ResourceIdentifier{
1388+
Kind: "Secret",
1389+
Name: fmt.Sprintf(appSecretNameTemplate, i),
1390+
Namespace: workNamespaceName,
1391+
Version: "v1",
1392+
})
1393+
}
1394+
1395+
placementResourceIdentifiers = append(placementResourceIdentifiers, placementv1beta1.ResourceIdentifier{
1396+
Kind: "Namespace",
1397+
Name: workNamespaceName,
1398+
Version: "v1",
1399+
})
1400+
placementResourceIdentifiers = append(placementResourceIdentifiers, placementv1beta1.ResourceIdentifier{
1401+
Kind: "ConfigMap",
1402+
Name: fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess()),
1403+
Version: "v1",
1404+
Namespace: workNamespaceName,
1405+
})
1406+
1407+
return placementResourceIdentifiers
1408+
}
1409+
1410+
func checkIfPlacedWorkResourcesOnTargetMemberClusters(targetMemberClusters []*framework.Cluster) {
1411+
for idx := range targetMemberClusters {
1412+
memberCluster := targetMemberClusters[idx]
1413+
1414+
workResourcesPlacedActual := workNamespaceAndConfigMapPlacedOnClusterActual(memberCluster)
1415+
Eventually(workResourcesPlacedActual, largeEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to place work resources on member cluster %s", memberCluster.ClusterName)
1416+
}
1417+
}
1418+
1419+
func checkIfPlacedLargeSecretResourcesOnTargetMemberClusters(targetMemberClusters []*framework.Cluster) {
1420+
for idx := range targetMemberClusters {
1421+
memberCluster := targetMemberClusters[idx]
1422+
1423+
secretsPlacedActual := secretsPlacedOnClusterActual(memberCluster)
1424+
Eventually(secretsPlacedActual(), largeEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to place large secrets on member cluster %s", memberCluster.ClusterName)
1425+
}
1426+
}
1427+
1428+
func checkIfRemovedWorkResourcesFromTargetMemberClusters(targetMemberClusters []*framework.Cluster) {
1429+
for idx := range targetMemberClusters {
1430+
memberCluster := targetMemberClusters[idx]
1431+
1432+
workResourcesRemovedActual := workNamespaceRemovedFromClusterActual(memberCluster)
1433+
Eventually(workResourcesRemovedActual, largeEventuallyDuration, eventuallyInterval).Should(Succeed(), "Failed to remove work resources from member cluster %s", memberCluster.ClusterName)
1434+
}
1435+
}
1436+
1437+
func secretsPlacedOnClusterActual(cluster *framework.Cluster) func() error {
1438+
workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
1439+
return func() error {
1440+
for i := 0; i < 3; i++ {
1441+
if err := validateSecretOnCluster(cluster, types.NamespacedName{Name: fmt.Sprintf(appSecretNameTemplate, i), Namespace: workNamespaceName}); err != nil {
1442+
return err
1443+
}
1444+
}
1445+
return nil
1446+
}
1447+
}
1448+
1449+
func validateSecretOnCluster(cluster *framework.Cluster, name types.NamespacedName) error {
1450+
secret := &corev1.Secret{}
1451+
if err := cluster.KubeClient.Get(ctx, name, secret); err != nil {
1452+
return err
1453+
}
1454+
1455+
// Use the object created in the hub cluster as reference.
1456+
wantSecret := &corev1.Secret{}
1457+
if err := hubClient.Get(ctx, name, wantSecret); err != nil {
1458+
return err
1459+
}
1460+
if diff := cmp.Diff(
1461+
secret, wantSecret,
1462+
ignoreObjectMetaAutoGeneratedFields,
1463+
ignoreObjectMetaAnnotationField,
1464+
); diff != "" {
1465+
return fmt.Errorf("app secret %s diff (-got, +want): %s", name.Name, diff)
1466+
}
1467+
1468+
return nil
1469+
}

test/e2e/resources_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import (
2323
const (
2424
workNamespaceNameTemplate = "application-%d"
2525
appConfigMapNameTemplate = "app-config-%d"
26+
appSecretNameTemplate = "app-secret-%d" // #nosec G101
2627
crpNameTemplate = "crp-%d"
2728
mcNameTemplate = "mc-%d"
2829
internalServiceExportNameTemplate = "ise-%d"
2930
internalServiceImportNameTemplate = "isi-%d"
3031
endpointSliceExportNameTemplate = "ep-%d"
31-
endpointSliceNameTemplate = "es-%d"
3232

3333
customDeletionBlockerFinalizer = "custom-deletion-blocker-finalizer"
3434
workNamespaceLabelName = "process"

test/e2e/utils_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ func createResourcesForFleetGuardRail() {
310310
{
311311
APIGroup: rbacv1.GroupName,
312312
Kind: "User",
313-
Name: testUser,
313+
Name: "test-user",
314314
},
315315
},
316316
RoleRef: rbacv1.RoleRef{

0 commit comments

Comments
 (0)