Skip to content

Commit bf23552

Browse files
kvapsclaude
andcommitted
fix(controller): expand minor allocator over multi-volume RDs
A 2-volume RD consumes minors {base, base+1} — the .res renderer emits volume k at base+k. The allocator only tracked the BASE minor per Resource, so a fresh allocation on a node where a 2-volume sibling was already running picked base+1 and drbdadm rejected the new .res with `conflicting use of device-minor`. Expand each Resource's recorded base to the full range based on the parent RD's len(VolumeDefinitions). Cache the lookup per RD so N-replica allocators don't load the same RD N times. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent 655deda commit bf23552

1 file changed

Lines changed: 48 additions & 1 deletion

File tree

internal/controller/resource_controller.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,8 +519,55 @@ func (r *ResourceReconciler) takenPortsOnNode(ctx context.Context, nodeName stri
519519
return r.takenOnNode(ctx, nodeName, func(s *blockstoriov1alpha1.ResourceStatus) *int32 { return s.DRBDPort })
520520
}
521521

522+
// takenMinorsOnNode returns every minor consumed on the node. A
523+
// multi-volume RD consumes N consecutive minors (the .res renderer
524+
// emits volume k at base+k), so for each Resource we expand its
525+
// recorded base minor to the full range based on the parent RD's
526+
// VolumeDefinitions count. Without this expansion, a fresh
527+
// Resource's allocator would happily pick base+1 on a node where a
528+
// 2-volume sibling already owns base..base+1.
522529
func (r *ResourceReconciler) takenMinorsOnNode(ctx context.Context, nodeName string) ([]int32, error) {
523-
return r.takenOnNode(ctx, nodeName, func(s *blockstoriov1alpha1.ResourceStatus) *int32 { return s.DRBDMinor })
530+
list := &blockstoriov1alpha1.ResourceList{}
531+
if err := r.List(ctx, list); err != nil {
532+
return nil, err
533+
}
534+
535+
rdVolCounts := map[string]int{}
536+
537+
out := make([]int32, 0, len(list.Items))
538+
539+
for i := range list.Items {
540+
if list.Items[i].Spec.NodeName != nodeName {
541+
continue
542+
}
543+
544+
base := list.Items[i].Status.DRBDMinor
545+
if base == nil {
546+
continue
547+
}
548+
549+
rdName := list.Items[i].Spec.ResourceDefinitionName
550+
volCount, cached := rdVolCounts[rdName]
551+
552+
if !cached {
553+
volCount = 1 // safe default: at least the base is taken
554+
555+
var rd blockstoriov1alpha1.ResourceDefinition
556+
if err := r.Get(ctx, client.ObjectKey{Name: rdName}, &rd); err == nil {
557+
if n := len(rd.Spec.VolumeDefinitions); n > 0 {
558+
volCount = n
559+
}
560+
}
561+
562+
rdVolCounts[rdName] = volCount
563+
}
564+
565+
for off := range int32(volCount) {
566+
out = append(out, *base+off)
567+
}
568+
}
569+
570+
return out, nil
524571
}
525572

526573
// takenOnNode is the shared scan: list every Resource on `nodeName`,

0 commit comments

Comments
 (0)