diff --git a/.github/workflows/code-verify.yml b/.github/workflows/code-verify.yml index 199b771..8cd40cf 100644 --- a/.github/workflows/code-verify.yml +++ b/.github/workflows/code-verify.yml @@ -2,6 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 name: Static code analysis and unit testing +permissions: + contents: read + on: push: paths-ignore: diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 23a7298..7167a66 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -2,6 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 name: E2E tests +permissions: + contents: read on: pull_request: @@ -12,7 +14,7 @@ on: inputs: test_parallel_process: description: Number of parallel processes for the E2E test suite - default: '1' + default: '2' required: false concurrency: @@ -73,7 +75,7 @@ jobs: - name: ✅ Run E2E test suite env: - TEST_PARALLEL_PROCESSES: ${{ github.event.inputs.test_parallel_process || 1 }} + TEST_PARALLEL_PROCESSES: ${{ github.event.inputs.test_parallel_process || 2 }} run: | make test-e2e \ IMG=${{ steps.kind.outputs.LOCAL_REGISTRY }}/redkey-operator:${{ env.OPERATOR_VERSION }} \ diff --git a/controllers/redis_manager.go b/controllers/redis_manager.go index 8eddf94..569089f 100644 --- a/controllers/redis_manager.go +++ b/controllers/redis_manager.go @@ -379,6 +379,11 @@ func (r *RedkeyClusterReconciler) doSlowUpgradeResharding(ctx context.Context, r // RollingUpdate r.logInfo(redkeyCluster.NamespacedName(), "Executing partition Rolling Update", "partition", currentPartition) + if currentPartition < 0 || currentPartition > math.MaxInt32 { + err = fmt.Errorf("invalid partition index %d: must be between 0 and %d", currentPartition, int(math.MaxInt32)) + r.logError(redkeyCluster.NamespacedName(), err, "Partition value out of int32 range") + return err + } localPartition := int32(currentPartition) existingStatefulSet.Spec.UpdateStrategy = v1.StatefulSetUpdateStrategy{ Type: v1.RollingUpdateStatefulSetStrategyType, diff --git a/test/e2e/redkey_suite_test.go b/test/e2e/redkey_suite_test.go index 60f47aa..f4fe47d 100644 --- a/test/e2e/redkey_suite_test.go +++ b/test/e2e/redkey_suite_test.go @@ -455,99 +455,99 @@ var _ = Describe("Redkey Operator & RedkeyCluster E2E", Label("operator", "clust ) }) - // Context("Primary-Replica layout", func() { - // const ( - // clusterName = "primary-test" - // initPrimaries = int32(3) - // initPerPrimary = int32(1) - // ) - - // var key types.NamespacedName - // BeforeEach(func() { - // key = types.NamespacedName{Namespace: namespace.Name, Name: clusterName} - - // // single bootstrap for every entry - // mustCreateAndReady( - // clusterName, - // initPrimaries, initPerPrimary, - // "", // no PVC - // framework.GetRedisImage(), - // true, // purgeKeys - // true, // ephemeral - // redkeyv1.Pdb{}, - // redkeyv1.RedkeyClusterOverrideSpec{}, - // ) - // }) - - // // ---------------------------------------------------------------- helpers - - // checkLayout := func(primaries, perPrimary int32) { - // Eventually(func() (bool, error) { - // return framework.ValidateRedkeyClusterPrimaryReplica( - // ctx, k8sClient, key, primaries, perPrimary) - // }, defaultWait*2, defaultPoll).Should(BeTrue()) - // } - - // type tc struct { - // desc string - // mutate func(*redkeyv1.RedkeyCluster) - // wantRep int32 - // wantPerPrimary int32 - // } - - // DescribeTable("primary/replica mutations", - // func(ctx SpecContext, t tc) { - // if t.mutate == nil { - // checkLayout(t.wantRep, t.wantPerPrimary) - // return - // } - - // rc, _, err := framework.ChangeCluster( - // ctx, k8sClient, key, - // framework.ChangeClusterOptions{Mutate: t.mutate}, - // ) - // Expect(err).NotTo(HaveOccurred()) - // Expect(rc.Spec.Primaries).To(Equal(t.wantRep)) - // Expect(rc.Spec.ReplicasPerPrimary).To(Equal(t.wantPerPrimary)) - - // checkLayout(t.wantRep, t.wantPerPrimary) - // }, - - // // ────────────────────────────────────────────────────────── - // Entry("baseline distribution is correct", SpecTimeout(rebalancingTestDuration), - // tc{ - // desc: "validateInitial", - // mutate: nil, - // wantRep: initPrimaries, - // wantPerPrimary: initPerPrimary, - // }, - // ), - - // Entry("scale up to 5/2", SpecTimeout(rebalancingTestDuration*2), - // tc{ - // desc: "scaleUp", - // mutate: func(r *redkeyv1.RedkeyCluster) { - // r.Spec.Primaries = 5 - // r.Spec.ReplicasPerPrimary = 2 - // }, - // wantRep: 5, - // wantPerPrimary: 2, - // }, - // ), - - // Entry("scale down to 3/1", SpecTimeout(rebalancingTestDuration*2), - // tc{ - // desc: "scaleDown", - // mutate: func(r *redkeyv1.RedkeyCluster) { - // r.Spec.Primaries = 3 - // r.Spec.ReplicasPerPrimary = 1 - // }, - // wantRep: 3, - // wantPerPrimary: 1, - // }, - // ), - // ) - // }) + Context("Primary-Replica layout", func() { + const ( + clusterName = "primary-test" + initPrimaries = int32(3) + initPerPrimary = int32(1) + ) + + var key types.NamespacedName + BeforeEach(func() { + key = types.NamespacedName{Namespace: namespace.Name, Name: clusterName} + + // single bootstrap for every entry + mustCreateAndReady( + clusterName, + initPrimaries, initPerPrimary, + "", // no PVC + framework.GetRedisImage(), + true, // purgeKeys + true, // ephemeral + redkeyv1.Pdb{}, + redkeyv1.RedkeyClusterOverrideSpec{}, + ) + }) + + // ---------------------------------------------------------------- helpers + + checkLayout := func(primaries, perPrimary int32) { + Eventually(func() (bool, error) { + return framework.ValidateRedkeyClusterPrimaryReplica( + ctx, k8sClient, key, primaries, perPrimary) + }, defaultWait*2, defaultPoll).Should(BeTrue()) + } + + type tc struct { + desc string + mutate func(*redkeyv1.RedkeyCluster) + wantRep int32 + wantPerPrimary int32 + } + + DescribeTable("primary/replica mutations", + func(ctx SpecContext, t tc) { + if t.mutate == nil { + checkLayout(t.wantRep, t.wantPerPrimary) + return + } + + rc, _, err := framework.ChangeCluster( + ctx, k8sClient, key, + framework.ChangeClusterOptions{Mutate: t.mutate}, + ) + Expect(err).NotTo(HaveOccurred()) + Expect(rc.Spec.Primaries).To(Equal(t.wantRep)) + Expect(rc.Spec.ReplicasPerPrimary).To(Equal(t.wantPerPrimary)) + + checkLayout(t.wantRep, t.wantPerPrimary) + }, + + // ────────────────────────────────────────────────────────── + Entry("baseline distribution is correct", SpecTimeout(rebalancingTestDuration), + tc{ + desc: "validateInitial", + mutate: nil, + wantRep: initPrimaries, + wantPerPrimary: initPerPrimary, + }, + ), + + Entry("scale up to 5/2", SpecTimeout(rebalancingTestDuration*2), + tc{ + desc: "scaleUp", + mutate: func(r *redkeyv1.RedkeyCluster) { + r.Spec.Primaries = 5 + r.Spec.ReplicasPerPrimary = 2 + }, + wantRep: 5, + wantPerPrimary: 2, + }, + ), + + Entry("scale down to 3/1", SpecTimeout(rebalancingTestDuration*2), + tc{ + desc: "scaleDown", + mutate: func(r *redkeyv1.RedkeyCluster) { + r.Spec.Primaries = 3 + r.Spec.ReplicasPerPrimary = 1 + }, + wantRep: 3, + wantPerPrimary: 1, + }, + ), + ) + }) Context("Data integrity across scale", func() { const base = "data-test"