mirror of https://github.com/k3s-io/k3s
glusterfs: add MinMaxAllocator
An allocator of integers that allows for changing the range. Previously allocated numbers are not lost, and can be released later even if they have fallen outside of the range. Signed-off-by: Michael Adam <obnox@redhat.com>pull/6/head
parent
51458a0ef0
commit
11a5e84aca
|
@ -15,6 +15,7 @@ go_library(
|
|||
srcs = [
|
||||
"doc.go",
|
||||
"glusterfs.go",
|
||||
"glusterfs_minmax.go",
|
||||
"glusterfs_util.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
|
@ -37,7 +38,10 @@ go_library(
|
|||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["glusterfs_test.go"],
|
||||
srcs = [
|
||||
"glusterfs_minmax_test.go",
|
||||
"glusterfs_test.go",
|
||||
],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
//
|
||||
// This implementation is space-efficient for a sparse
|
||||
// allocation over a big range. Could be optimized
|
||||
// for high absolute allocation number with a bitmap.
|
||||
//
|
||||
|
||||
package glusterfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"k8s.io/kubernetes/pkg/registry/core/service/allocator"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("number not allocated")
|
||||
ErrConflict = errors.New("number already allocated")
|
||||
ErrInvalidRange = errors.New("invalid range")
|
||||
ErrOutOfRange = errors.New("out of range")
|
||||
ErrRangeFull = errors.New("range full")
|
||||
ErrInternal = errors.New("internal error")
|
||||
)
|
||||
|
||||
type MinMaxAllocator struct {
|
||||
lock sync.Mutex
|
||||
min int
|
||||
max int
|
||||
free int
|
||||
used map[int]bool
|
||||
}
|
||||
|
||||
var _ Rangeable = &MinMaxAllocator{}
|
||||
|
||||
// Rangeable is an Interface that can adjust its min/max range.
|
||||
// Rangeable should be threadsafe
|
||||
type Rangeable interface {
|
||||
allocator.Interface
|
||||
SetRange(min, max int) error
|
||||
}
|
||||
|
||||
func NewMinMaxAllocator(min, max int) (*MinMaxAllocator, error) {
|
||||
if min > max {
|
||||
return nil, ErrInvalidRange
|
||||
}
|
||||
return &MinMaxAllocator{
|
||||
min: min,
|
||||
max: max,
|
||||
free: 1 + max - min,
|
||||
used: map[int]bool{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) SetRange(min, max int) error {
|
||||
if min > max {
|
||||
return ErrInvalidRange
|
||||
}
|
||||
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
// Check if we need to change
|
||||
if a.min == min && a.max == max {
|
||||
return nil
|
||||
}
|
||||
|
||||
a.min = min
|
||||
a.max = max
|
||||
|
||||
// Recompute how many free we have in the range
|
||||
num_used := 0
|
||||
for i := range a.used {
|
||||
if a.inRange(i) {
|
||||
num_used++
|
||||
}
|
||||
}
|
||||
a.free = 1 + max - min - num_used
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) Allocate(i int) (bool, error) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if !a.inRange(i) {
|
||||
return false, ErrOutOfRange
|
||||
}
|
||||
|
||||
if a.has(i) {
|
||||
return false, ErrConflict
|
||||
}
|
||||
|
||||
a.used[i] = true
|
||||
a.free--
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) AllocateNext() (int, bool, error) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
// Fast check if we're out of items
|
||||
if a.free <= 0 {
|
||||
return 0, false, ErrRangeFull
|
||||
}
|
||||
|
||||
// Scan from the minimum until we find a free item
|
||||
for i := a.min; i <= a.max; i++ {
|
||||
if !a.has(i) {
|
||||
a.used[i] = true
|
||||
a.free--
|
||||
return i, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// no free item found, but a.free != 0
|
||||
return 0, false, ErrInternal
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) Release(i int) error {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if !a.has(i) {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(a.used, i)
|
||||
|
||||
if a.inRange(i) {
|
||||
a.free++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) has(i int) bool {
|
||||
_, ok := a.used[i]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) Has(i int) bool {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
return a.has(i)
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) Free() int {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
return a.free
|
||||
}
|
||||
|
||||
func (a *MinMaxAllocator) inRange(i int) bool {
|
||||
return a.min <= i && i <= a.max
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package glusterfs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewFree(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
if f := m.Free(); f != (max - min + 1) {
|
||||
t.Errorf("expect to get %d free, but got %d", (max - min + 1), f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInvalidRange(t *testing.T) {
|
||||
if _, err := NewMinMaxAllocator(10, 1); err != ErrInvalidRange {
|
||||
t.Errorf("expect to get Error '%v', got '%v'", ErrInvalidRange, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRange(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
if err = m.SetRange(10, 1); err != ErrInvalidRange {
|
||||
t.Errorf("expected to get error '%v', got '%v'", ErrInvalidRange, err)
|
||||
}
|
||||
|
||||
if err = m.SetRange(1, 2); err != nil {
|
||||
t.Errorf("error setting range: '%v'", err)
|
||||
}
|
||||
|
||||
if f := m.Free(); f != 2 {
|
||||
t.Errorf("expect to get %d free, but got %d", 2, f)
|
||||
}
|
||||
|
||||
if ok, _ := m.Allocate(1); !ok {
|
||||
t.Errorf("error allocate offset %v", 1)
|
||||
}
|
||||
|
||||
if f := m.Free(); f != 1 {
|
||||
t.Errorf("expect to get 1 free, but got %d", f)
|
||||
}
|
||||
|
||||
if err = m.SetRange(1, 1); err != nil {
|
||||
t.Errorf("error setting range: '%v'", err)
|
||||
}
|
||||
|
||||
if f := m.Free(); f != 0 {
|
||||
t.Errorf("expect to get 0 free, but got %d", f)
|
||||
}
|
||||
|
||||
if err = m.SetRange(2, 2); err != nil {
|
||||
t.Errorf("error setting range: '%v'", err)
|
||||
}
|
||||
|
||||
if f := m.Free(); f != 1 {
|
||||
t.Errorf("expect to get 1 free, but got %d", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateNext(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
el, ok, _ := m.AllocateNext()
|
||||
if !ok {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
|
||||
if !m.Has(el) {
|
||||
t.Errorf("expect element %v allocated", el)
|
||||
}
|
||||
|
||||
if f := m.Free(); f != (max-min+1)-1 {
|
||||
t.Errorf("expect to get %d free, but got %d", (max-min+1)-1, f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateMax(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
for i := 1; i <= max; i++ {
|
||||
if _, ok, _ := m.AllocateNext(); !ok {
|
||||
t.Fatalf("unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok, _ := m.AllocateNext(); ok {
|
||||
t.Errorf("unexpected success")
|
||||
}
|
||||
|
||||
if f := m.Free(); f != 0 {
|
||||
t.Errorf("expect to get %d free, but got %d", 0, f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocate(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
offset := 3
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
if ok, err := m.Allocate(offset); !ok {
|
||||
t.Errorf("error allocate offset %v: %v", offset, err)
|
||||
}
|
||||
|
||||
if !m.Has(offset) {
|
||||
t.Errorf("expect element %v allocated", offset)
|
||||
}
|
||||
|
||||
if f := m.Free(); f != (max-min+1)-1 {
|
||||
t.Errorf("expect to get %d free, but got %d", (max-min+1)-1, f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateConflict(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
offset := 3
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
if ok, err := m.Allocate(offset); !ok {
|
||||
t.Errorf("error allocate offset %v: %v", offset, err)
|
||||
}
|
||||
|
||||
ok, err := m.Allocate(offset)
|
||||
if ok {
|
||||
t.Errorf("unexpected success")
|
||||
}
|
||||
if err != ErrConflict {
|
||||
t.Errorf("expected error '%v', got '%v'", ErrConflict, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocateOutOfRange(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
offset := 11
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
ok, err := m.Allocate(offset)
|
||||
if ok {
|
||||
t.Errorf("unexpected success")
|
||||
}
|
||||
if err != ErrOutOfRange {
|
||||
t.Errorf("expected error '%v', got '%v'", ErrOutOfRange, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRelease(t *testing.T) {
|
||||
min := 1
|
||||
max := 10
|
||||
offset := 3
|
||||
|
||||
m, err := NewMinMaxAllocator(min, max)
|
||||
if err != nil {
|
||||
t.Errorf("error creating new allocator: '%v'", err)
|
||||
}
|
||||
|
||||
if ok, err := m.Allocate(offset); !ok {
|
||||
t.Errorf("error allocate offset %v: %v", offset, err)
|
||||
}
|
||||
|
||||
if !m.Has(offset) {
|
||||
t.Errorf("expect offset %v allocated", offset)
|
||||
}
|
||||
|
||||
if err = m.Release(offset); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if m.Has(offset) {
|
||||
t.Errorf("expect offset %v not allocated", offset)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue