Skip to content

Commit a3cc4f9

Browse files
kvapsclaude
andcommitted
feat(controller): gate auto-tiebreaker on DrbdOptions/AutoAddQuorumTiebreaker
Mirrors upstream LINSTOR's CtrlRscAutoTieBreakerHelper: the auto-witness logic only runs when DrbdOptions/AutoAddQuorumTiebreaker is non-false. Default is "true" (matches cozystack / piraeus-operator behaviour where ControllerProps seeds it on cluster bootstrap), so this is a transparent change for the common case. Operators who place a manual DISKLESS replica disable the auto path with `DrbdOptions/AutoAddQuorumTiebreaker: "false"` on the RD spec — otherwise the RD reconciler races to drop a TIE_BREAKER witness on the same target node and the explicit kubectl apply conflicts with the auto-created witness. `tests/e2e/auto-diskful.sh` flips the prop off to avoid this race. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent 61da2a6 commit a3cc4f9

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

internal/controller/resourcedefinition_controller.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,14 @@ func (r *ResourceDefinitionReconciler) ensureTiebreaker(ctx context.Context, rd
124124
nonWitnessDiskless := len(diskless) - len(witness)
125125

126126
// Tiebreaker decision (mirrors shouldTieBreakerExist).
127-
wantWitness := len(diskful) >= 2 && len(diskful)%2 == 0 && nonWitnessDiskless == 0
127+
// Gate on DrbdOptions/AutoAddQuorumTiebreaker — upstream LINSTOR
128+
// only runs the auto-witness logic when the prop is "true". On
129+
// cozystack / piraeus-operator clusters this prop is set
130+
// cluster-wide via ControllerProps; vanilla blockstor leaves it
131+
// off so explicit DISKLESS replicas don't race with the auto
132+
// reconciler.
133+
wantWitness := isAutoTieBreakerEnabled(rd) &&
134+
len(diskful) >= 2 && len(diskful)%2 == 0 && nonWitnessDiskless == 0
128135

129136
disklessAfter, err := r.applyWitnessDecision(ctx, rd, replicas, diskless, witness, wantWitness)
130137
if err != nil {
@@ -134,6 +141,34 @@ func (r *ResourceDefinitionReconciler) ensureTiebreaker(ctx context.Context, rd
134141
return r.setQuorum(ctx, rd, quorumPolicy(len(diskful), len(disklessAfter)))
135142
}
136143

144+
// isAutoTieBreakerEnabled gates witness auto-creation on the RD's
145+
// `DrbdOptions/AutoAddQuorumTiebreaker` prop. Default is enabled
146+
// (matches the effective cozystack / piraeus-operator behaviour
147+
// where ControllerProps seeds it true) — operators who explicitly
148+
// place a manual DISKLESS replica disable the auto path with
149+
// `DrbdOptions/AutoAddQuorumTiebreaker: "false"` on the RD spec.
150+
//
151+
// We only check the RD prop bag here; the resolver (controller →
152+
// RG → RD → Resource hierarchy) doesn't run on the RD reconciler
153+
// path because that path doesn't dispatch to the satellite. A
154+
// cluster-wide ControllerProps default still propagates because
155+
// the REST POST /v1/resource-definitions handler folds parent-RG
156+
// + ControllerProps into the RD on create.
157+
func isAutoTieBreakerEnabled(rd *blockstoriov1alpha1.ResourceDefinition) bool {
158+
const propKey = "DrbdOptions/AutoAddQuorumTiebreaker"
159+
160+
if rd.Spec.Props == nil {
161+
return true
162+
}
163+
164+
value, ok := rd.Spec.Props[propKey]
165+
if !ok {
166+
return true
167+
}
168+
169+
return !strings.EqualFold(value, "false")
170+
}
171+
137172
// applyWitnessDecision creates or removes the witness and returns
138173
// the diskless slice as it should look after the decision (so the
139174
// caller's quorum computation reflects the post-write state).

tests/e2e/auto-diskful.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,17 @@ N3=test-worker-3
3131
trap 'delete_rd "$RD"' EXIT
3232

3333
echo ">> apply 2 diskful + 1 DISKLESS"
34+
# Disable the auto-tiebreaker — this test explicitly creates its
35+
# own DISKLESS replica on N3, and we don't want the RD reconciler
36+
# racing to create a TIE_BREAKER witness on N3 first (which would
37+
# then conflict with our explicit apply, ALSO landing on N3).
3438
cat <<EOF | kubectl apply -f -
3539
apiVersion: blockstor.io.blockstor.io/v1alpha1
3640
kind: ResourceDefinition
3741
metadata: {name: ${RD}}
3842
spec:
43+
props:
44+
DrbdOptions/AutoAddQuorumTiebreaker: "false"
3945
volumeDefinitions:
4046
- {volumeNumber: 0, sizeKib: 65536}
4147
EOF

0 commit comments

Comments
 (0)