package mcsallocator import ( "errors" "fmt" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/registry/service/allocator" "github.com/openshift/origin/pkg/security/mcs" ) // Interface manages the allocation of ports out of a range. Interface // should be threadsafe. type Interface interface { Allocate(*mcs.Label) error AllocateNext() (*mcs.Label, error) Release(*mcs.Label) error } var ( ErrFull = errors.New("range is full") ErrNotInRange = errors.New("provided label is not in the valid range") ErrAllocated = errors.New("provided label is already allocated") ErrMismatchedRange = errors.New("the provided label does not match the current label range") ) type Allocator struct { r *mcs.Range alloc allocator.Interface } // Allocator implements Interface and Snapshottable var _ Interface = &Allocator{} // New creates a Allocator over a UID range, calling factory to construct the backing store. func New(r *mcs.Range, factory allocator.AllocatorFactory) *Allocator { return &Allocator{ r: r, alloc: factory(int(r.Size()), r.String()), } } // NewInMemory creates an in-memory Allocator func NewInMemory(r *mcs.Range) *Allocator { factory := func(max int, rangeSpec string) allocator.Interface { return allocator.NewContiguousAllocationMap(max, rangeSpec) } return New(r, factory) } // Free returns the count of port left in the range. func (r *Allocator) Free() int { return r.alloc.Free() } // Allocate attempts to reserve the provided label. ErrNotInRange or // ErrAllocated will be returned if the label is not valid for this range // or has already been reserved. ErrFull will be returned if there // are no labels left. func (r *Allocator) Allocate(label *mcs.Label) error { ok, offset := r.contains(label) if !ok { return ErrNotInRange } allocated, err := r.alloc.Allocate(int(offset)) if err != nil { return err } if !allocated { return ErrAllocated } return nil } // AllocateNext reserves one of the labels from the pool. ErrFull may // be returned if there are no labels left. func (r *Allocator) AllocateNext() (*mcs.Label, error) { offset, ok, err := r.alloc.AllocateNext() if err != nil { return nil, err } if !ok { return nil, ErrFull } label, ok := r.r.LabelAt(uint64(offset)) if !ok { return nil, ErrNotInRange } return label, nil } // Release releases the port back to the pool. Releasing an // unallocated port or a port out of the range is a no-op and // returns no error. func (r *Allocator) Release(label *mcs.Label) error { ok, offset := r.contains(label) if !ok { // TODO: log a warning return nil } return r.alloc.Release(int(offset)) } // Has returns true if the provided port is already allocated and a call // to Allocate(label) would fail with ErrAllocated. func (r *Allocator) Has(label *mcs.Label) bool { ok, offset := r.contains(label) if !ok { return false } return r.alloc.Has(int(offset)) } // Snapshot saves the current state of the pool. func (r *Allocator) Snapshot(dst *api.RangeAllocation) error { snapshottable, ok := r.alloc.(allocator.Snapshottable) if !ok { return fmt.Errorf("not a snapshottable allocator") } rangeString, data := snapshottable.Snapshot() dst.Range = rangeString dst.Data = data return nil } // Restore restores the pool to the previously captured state. ErrMismatchedNetwork // is returned if the provided port range doesn't exactly match the previous range. func (r *Allocator) Restore(into *mcs.Range, data []byte) error { if into.String() != r.r.String() { return ErrMismatchedRange } snapshottable, ok := r.alloc.(allocator.Snapshottable) if !ok { return fmt.Errorf("not a snapshottable allocator") } return snapshottable.Restore(into.String(), data) } // contains returns true and the offset if the label is in the range (and aligned), and false // and nil otherwise. func (r *Allocator) contains(label *mcs.Label) (bool, uint64) { return r.r.Offset(label) }