Skip to content

Commit bbd92c8

Browse files
kvapsclaude
andcommitted
feat(capacity): satellite reports pool capacity every 30s; controller updates Status
- New unary RPC `Controller.ReportPoolCapacity(node, []PoolCapacity)`. - pkg/satellite agent runs `runCapacityLoop` on a 30 s ticker; each tick walks Provider.PoolStatus and ships free / total / supports- snapshots back to the controller. - pkg/satellitecontroller.Server.ReportPoolCapacity calls the new StoragePoolStore.SetCapacity which writes via the Status subresource (so it doesn't fight a Spec rewrite from a concurrent Hello). Both in-memory and CRD-backed stores implement it. End-to-end: linstor-csi GetCapacity / autoplacer free-space ranking now have live numbers without anyone running `linstor storage-pool list` by hand. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
1 parent 5071372 commit bbd92c8

8 files changed

Lines changed: 877 additions & 428 deletions

File tree

pkg/satellite/agent.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ func (a *Agent) Run(ctx context.Context) error {
143143
// the satellite (the next Apply will redrive state anyway).
144144
go a.runObserveLoop(ctx, client)
145145

146+
// Periodically push pool capacity so /v1/view/storage-pools shows
147+
// live numbers. Best-effort: errors log and the next tick retries.
148+
go a.runCapacityLoop(ctx, client)
149+
146150
<-ctx.Done()
147151

148152
a.logger.Info("agent stopping", "node", a.cfg.NodeName)
@@ -263,6 +267,56 @@ func (a *Agent) runObserveLoop(ctx context.Context, client satellitepb.Controlle
263267
}
264268
}
265269

270+
// runCapacityLoop walks each registered Provider's PoolStatus on a
271+
// fixed cadence and pushes free/total bytes to the controller via
272+
// ReportPoolCapacity. Best-effort: a failed iteration is logged and
273+
// the next tick retries. Empty Providers map → no-op (the loop still
274+
// runs, but every tick yields a zero-pool request which the
275+
// controller treats as a no-op).
276+
func (a *Agent) runCapacityLoop(ctx context.Context, client satellitepb.ControllerClient) {
277+
tick := time.NewTicker(capacityInterval)
278+
defer tick.Stop()
279+
280+
for {
281+
select {
282+
case <-ctx.Done():
283+
return
284+
case <-tick.C:
285+
}
286+
287+
pools := make([]*satellitepb.PoolCapacity, 0, len(a.cfg.Providers))
288+
289+
for name, p := range a.cfg.Providers {
290+
poolStatus, err := p.PoolStatus(ctx)
291+
if err != nil {
292+
a.logger.Error("PoolStatus", "pool", name, "err", err)
293+
294+
continue
295+
}
296+
297+
pools = append(pools, &satellitepb.PoolCapacity{
298+
PoolName: name,
299+
FreeCapacityKib: poolStatus.FreeCapacityKib,
300+
TotalCapacityKib: poolStatus.TotalCapacityKib,
301+
SupportsSnapshots: poolStatus.SupportsSnapshots,
302+
})
303+
}
304+
305+
_, err := client.ReportPoolCapacity(ctx, &satellitepb.ReportPoolCapacityRequest{
306+
NodeName: a.cfg.NodeName,
307+
Pools: pools,
308+
})
309+
if err != nil {
310+
a.logger.Error("ReportPoolCapacity", "err", err)
311+
}
312+
}
313+
}
314+
315+
// capacityInterval is the periodic-push cadence. Long enough to keep
316+
// CRD writes cheap, short enough that a freshly-allocated LV shows up
317+
// in /v1/view/storage-pools' free_capacity within ~half a minute.
318+
const capacityInterval = 30 * time.Second
319+
266320
// observeBuffer caps the events2 → Observer in-flight queue. drbd-9
267321
// reconnect storms can burst dozens of events; 256 is a comfortable
268322
// cushion for the satellite-side translation goroutine.

0 commit comments

Comments
 (0)