From f75bed868260d1e7c14bd3d47fb5addaee5e287d Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Fri, 23 Dec 2016 00:34:11 -0800 Subject: [PATCH] Add a ForEach() to bitmap allocator --- pkg/registry/core/service/allocator/BUILD | 2 +- pkg/registry/core/service/allocator/bitmap.go | 27 +++++++++++++++ .../core/service/allocator/bitmap_test.go | 33 +++++++++++++++++++ .../core/service/allocator/etcd/etcd.go | 6 ++++ .../core/service/allocator/interfaces.go | 1 + pkg/volume/glusterfs/BUILD | 1 - pkg/volume/glusterfs/glusterfs_minmax.go | 8 +++-- 7 files changed, 73 insertions(+), 5 deletions(-) diff --git a/pkg/registry/core/service/allocator/BUILD b/pkg/registry/core/service/allocator/BUILD index 49e201c2967..868f9119310 100644 --- a/pkg/registry/core/service/allocator/BUILD +++ b/pkg/registry/core/service/allocator/BUILD @@ -26,5 +26,5 @@ go_test( ], library = "go_default_library", tags = ["automanaged"], - deps = [], + deps = ["//vendor:k8s.io/client-go/pkg/util/sets"], ) diff --git a/pkg/registry/core/service/allocator/bitmap.go b/pkg/registry/core/service/allocator/bitmap.go index 9394d59fc3c..225e8597407 100644 --- a/pkg/registry/core/service/allocator/bitmap.go +++ b/pkg/registry/core/service/allocator/bitmap.go @@ -124,6 +124,33 @@ func (r *AllocationBitmap) Release(offset int) error { return nil } +const ( + // Find the size of a big.Word in bytes. + notZero = uint64(^big.Word(0)) + wordPower = (notZero>>8)&1 + (notZero>>16)&1 + (notZero>>32)&1 + wordSize = 1 << wordPower +) + +// ForEach calls the provided function for each allocated bit. The +// AllocationBitmap may not be modified while this loop is running. +func (r *AllocationBitmap) ForEach(fn func(int)) { + r.lock.Lock() + defer r.lock.Unlock() + + words := r.allocated.Bits() + for wordIdx, word := range words { + bit := 0 + for word > 0 { + if (word & 1) != 0 { + fn((wordIdx * wordSize * 8) + bit) + word = word &^ 1 + } + bit++ + word = word >> 1 + } + } +} + // Has returns true if the provided item is already allocated and a call // to Allocate(offset) would fail. func (r *AllocationBitmap) Has(offset int) bool { diff --git a/pkg/registry/core/service/allocator/bitmap_test.go b/pkg/registry/core/service/allocator/bitmap_test.go index 14139b2b3ed..e17055a975c 100644 --- a/pkg/registry/core/service/allocator/bitmap_test.go +++ b/pkg/registry/core/service/allocator/bitmap_test.go @@ -18,6 +18,8 @@ package allocator import ( "testing" + + "k8s.io/client-go/pkg/util/sets" ) func TestAllocate(t *testing.T) { @@ -82,6 +84,37 @@ func TestRelease(t *testing.T) { } } +func TestForEach(t *testing.T) { + testCases := []sets.Int{ + sets.NewInt(), + sets.NewInt(0), + sets.NewInt(0, 2, 5, 9), + sets.NewInt(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), + } + + for i, tc := range testCases { + m := NewAllocationMap(10, "test") + for offset := range tc { + if ok, _ := m.Allocate(offset); !ok { + t.Errorf("[%d] error allocate offset %v", i, offset) + } + if !m.Has(offset) { + t.Errorf("[%d] expect offset %v allocated", i, offset) + } + } + calls := sets.NewInt() + m.ForEach(func(i int) { + calls.Insert(i) + }) + if len(calls) != len(tc) { + t.Errorf("[%d] expected %d calls, got %d", i, len(tc), len(calls)) + } + if !calls.Equal(tc) { + t.Errorf("[%d] expected calls to equal testcase: %v vs %v", i, calls.List(), tc.List()) + } + } +} + func TestSnapshotAndRestore(t *testing.T) { offset := 3 m := NewAllocationMap(10, "test") diff --git a/pkg/registry/core/service/allocator/etcd/etcd.go b/pkg/registry/core/service/allocator/etcd/etcd.go index 509b116d696..2400f7fd17e 100644 --- a/pkg/registry/core/service/allocator/etcd/etcd.go +++ b/pkg/registry/core/service/allocator/etcd/etcd.go @@ -144,6 +144,12 @@ func (e *Etcd) Release(item int) error { }) } +func (e *Etcd) ForEach(fn func(int)) { + e.lock.Lock() + defer e.lock.Unlock() + e.alloc.ForEach(fn) +} + // tryUpdate performs a read-update to persist the latest snapshot state of allocation. func (e *Etcd) tryUpdate(fn func() error) error { err := e.storage.GuaranteedUpdate(context.TODO(), e.baseKey, &api.RangeAllocation{}, true, nil, diff --git a/pkg/registry/core/service/allocator/interfaces.go b/pkg/registry/core/service/allocator/interfaces.go index 88231dafc12..0d18feff203 100644 --- a/pkg/registry/core/service/allocator/interfaces.go +++ b/pkg/registry/core/service/allocator/interfaces.go @@ -22,6 +22,7 @@ type Interface interface { Allocate(int) (bool, error) AllocateNext() (int, bool, error) Release(int) error + ForEach(func(int)) // For testing Has(int) bool diff --git a/pkg/volume/glusterfs/BUILD b/pkg/volume/glusterfs/BUILD index 5a27ef9c256..55fed5f58e9 100644 --- a/pkg/volume/glusterfs/BUILD +++ b/pkg/volume/glusterfs/BUILD @@ -25,7 +25,6 @@ go_library( "//pkg/apis/storage/v1beta1/util:go_default_library", "//pkg/client/clientset_generated/clientset:go_default_library", "//pkg/labels:go_default_library", - "//pkg/registry/core/service/allocator:go_default_library", "//pkg/types:go_default_library", "//pkg/util/exec:go_default_library", "//pkg/util/mount:go_default_library", diff --git a/pkg/volume/glusterfs/glusterfs_minmax.go b/pkg/volume/glusterfs/glusterfs_minmax.go index 72da43e7a34..d504e3fe083 100644 --- a/pkg/volume/glusterfs/glusterfs_minmax.go +++ b/pkg/volume/glusterfs/glusterfs_minmax.go @@ -25,8 +25,6 @@ package glusterfs import ( "errors" "sync" - - "k8s.io/kubernetes/pkg/registry/core/service/allocator" ) var ( @@ -51,7 +49,11 @@ var _ Rangeable = &MinMaxAllocator{} // Rangeable is an Interface that can adjust its min/max range. // Rangeable should be threadsafe type Rangeable interface { - allocator.Interface + Allocate(int) (bool, error) + AllocateNext() (int, bool, error) + Release(int) error + Has(int) bool + Free() int SetRange(min, max int) error }