diff --git a/weed/topology/volume_layout.go b/weed/topology/volume_layout.go index feb7934dc..1a0a60ed6 100644 --- a/weed/topology/volume_layout.go +++ b/weed/topology/volume_layout.go @@ -788,9 +788,15 @@ func (vl *VolumeLayout) SetVolumeUnavailable(dn *DataNode, vid needle.VolumeId) } return false } -func (vl *VolumeLayout) SetVolumeAvailable(dn *DataNode, vid needle.VolumeId, isReadOnly, isFullCapacity bool) bool { +func (vl *VolumeLayout) SetVolumeAvailable(dn *DataNode, vid needle.VolumeId, isReadOnly, isFullCapacity bool) (becameWritable bool) { + restoreActiveVolumeCount := false vl.accessLock.Lock() - defer vl.accessLock.Unlock() + defer func() { + vl.accessLock.Unlock() + if restoreActiveVolumeCount { + vl.adjustActiveVolumeCount(vid, +1) + } + }() vInfo, err := dn.GetVolumesById(vid) if err != nil { @@ -804,9 +810,17 @@ func (vl *VolumeLayout) SetVolumeAvailable(dn *DataNode, vid needle.VolumeId, is } if vl.enoughCopies(vid) { - return vl.setVolumeWritable(vid) + becameWritable = vl.setVolumeWritable(vid) + if becameWritable { + if st := vl.sizeTracking[vid]; st != nil && !st.fullSince.IsZero() { + // fullSince marks a prior capacity-full removal that already + // decremented activeVolumeCount. Re-adding must pair it once. + st.fullSince = time.Time{} + restoreActiveVolumeCount = true + } + } } - return false + return becameWritable } func (vl *VolumeLayout) enoughCopies(vid needle.VolumeId) bool { diff --git a/weed/topology/volume_layout_capacity_recovery_test.go b/weed/topology/volume_layout_capacity_recovery_test.go index 2e998c1ed..cd617e17e 100644 --- a/weed/topology/volume_layout_capacity_recovery_test.go +++ b/weed/topology/volume_layout_capacity_recovery_test.go @@ -67,3 +67,52 @@ func TestSetVolumeCapacityFullStampsFullSinceAndRecovers(t *testing.T) { t.Fatalf("expected fullSince cleared after recovery") } } + +func TestSetVolumeAvailableRestoresActiveCountForCapacityFullVolume(t *testing.T) { + layout := ` +{ + "dc1":{ + "rack1":{ + "server1":{ + "volumes":[ + {"id":1, "size":4000, "replication":"000"} + ], + "limit":10 + } + } + } +} +` + topo, vl := setupPickTest(t, layout, 10000) + + initialActive := topo.diskUsages.usages[types.HardDriveType].activeVolumeCount + if !vl.SetVolumeCapacityFull(1) { + t.Fatalf("SetVolumeCapacityFull should report the volume was writable") + } + vl.AdjustActiveVolumeCountForFull(1) + if got := topo.diskUsages.usages[types.HardDriveType].activeVolumeCount; got != initialActive-1 { + t.Fatalf("expected activeVolumeCount decremented to %d, got %d", initialActive-1, got) + } + + vl.accessLock.RLock() + dn := vl.vid2location[1].list[0] + vl.accessLock.RUnlock() + + if !vl.SetVolumeAvailable(dn, 1, false, false) { + t.Fatalf("SetVolumeAvailable should report the volume became writable") + } + if got := topo.diskUsages.usages[types.HardDriveType].activeVolumeCount; got != initialActive { + t.Fatalf("expected SetVolumeAvailable to restore activeVolumeCount to %d, got %d", initialActive, got) + } + if !vl.sizeTracking[1].fullSince.IsZero() { + t.Fatalf("expected SetVolumeAvailable to clear fullSince after restoring activeVolumeCount") + } + + advanceSizeTrackingClock(vl, 1, capacityRecoveryDelay+time.Second) + if vl.UpdateVolumeSize(1, 4000, 0) { + t.Fatalf("heartbeat recovery should not fire after SetVolumeAvailable already restored the volume") + } + if got := topo.diskUsages.usages[types.HardDriveType].activeVolumeCount; got != initialActive { + t.Fatalf("expected activeVolumeCount to remain %d, got %d", initialActive, got) + } +}