k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/service/portallocator/allocator.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package portallocator
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  
    23  	"k8s.io/apimachinery/pkg/util/net"
    24  	"k8s.io/klog/v2"
    25  	api "k8s.io/kubernetes/pkg/apis/core"
    26  	"k8s.io/kubernetes/pkg/registry/core/service/allocator"
    27  )
    28  
    29  // Interface manages the allocation of ports out of a range. Interface
    30  // should be threadsafe.
    31  type Interface interface {
    32  	Allocate(int) error
    33  	AllocateNext() (int, error)
    34  	Release(int) error
    35  	ForEach(func(int))
    36  	Has(int) bool
    37  	Destroy()
    38  	EnableMetrics()
    39  }
    40  
    41  var (
    42  	ErrFull              = errors.New("range is full")
    43  	ErrAllocated         = errors.New("provided port is already allocated")
    44  	ErrMismatchedNetwork = errors.New("the provided port range does not match the current port range")
    45  )
    46  
    47  type ErrNotInRange struct {
    48  	ValidPorts string
    49  }
    50  
    51  func (e *ErrNotInRange) Error() string {
    52  	return fmt.Sprintf("provided port is not in the valid range. The range of valid ports is %s", e.ValidPorts)
    53  }
    54  
    55  type PortAllocator struct {
    56  	portRange net.PortRange
    57  
    58  	alloc allocator.Interface
    59  
    60  	// metrics is a metrics recorder that can be disabled
    61  	metrics metricsRecorderInterface
    62  }
    63  
    64  // PortAllocator implements Interface and Snapshottable
    65  var _ Interface = &PortAllocator{}
    66  
    67  // New creates a PortAllocator over a net.PortRange, calling allocatorFactory to construct the backing store.
    68  func New(pr net.PortRange, allocatorFactory allocator.AllocatorWithOffsetFactory) (*PortAllocator, error) {
    69  	max := pr.Size
    70  	rangeSpec := pr.String()
    71  
    72  	a := &PortAllocator{
    73  		portRange: pr,
    74  		metrics:   &emptyMetricsRecorder{},
    75  	}
    76  
    77  	var err error
    78  	a.alloc, err = allocatorFactory(max, rangeSpec, calculateRangeOffset(pr))
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	return a, err
    84  }
    85  
    86  // NewInMemory creates an in-memory allocator.
    87  func NewInMemory(pr net.PortRange) (*PortAllocator, error) {
    88  	return New(pr, func(max int, rangeSpec string, offset int) (allocator.Interface, error) {
    89  		return allocator.NewAllocationMapWithOffset(max, rangeSpec, offset), nil
    90  	})
    91  }
    92  
    93  // NewFromSnapshot allocates a PortAllocator and initializes it from a snapshot.
    94  func NewFromSnapshot(snap *api.RangeAllocation) (*PortAllocator, error) {
    95  	pr, err := net.ParsePortRange(snap.Range)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	r, err := NewInMemory(*pr)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	if err := r.Restore(*pr, snap.Data); err != nil {
   104  		return nil, err
   105  	}
   106  	return r, nil
   107  }
   108  
   109  // Free returns the count of port left in the range.
   110  func (r *PortAllocator) Free() int {
   111  	return r.alloc.Free()
   112  }
   113  
   114  // Used returns the count of ports used in the range.
   115  func (r *PortAllocator) Used() int {
   116  	return r.portRange.Size - r.alloc.Free()
   117  }
   118  
   119  // Allocate attempts to reserve the provided port. ErrNotInRange or
   120  // ErrAllocated will be returned if the port is not valid for this range
   121  // or has already been reserved.  ErrFull will be returned if there
   122  // are no ports left.
   123  func (r *PortAllocator) Allocate(port int) error {
   124  	ok, offset := r.contains(port)
   125  	if !ok {
   126  		// update metrics
   127  		r.metrics.incrementAllocationErrors("static")
   128  
   129  		// include valid port range in error
   130  		validPorts := r.portRange.String()
   131  		return &ErrNotInRange{validPorts}
   132  	}
   133  
   134  	allocated, err := r.alloc.Allocate(offset)
   135  	if err != nil {
   136  		// update metrics
   137  		r.metrics.incrementAllocationErrors("static")
   138  		return err
   139  	}
   140  	if !allocated {
   141  		// update metrics
   142  		r.metrics.incrementAllocationErrors("static")
   143  		return ErrAllocated
   144  	}
   145  
   146  	// update metrics
   147  	r.metrics.incrementAllocations("static")
   148  	r.metrics.setAllocated(r.Used())
   149  	r.metrics.setAvailable(r.Free())
   150  
   151  	return nil
   152  }
   153  
   154  // AllocateNext reserves one of the ports from the pool. ErrFull may
   155  // be returned if there are no ports left.
   156  func (r *PortAllocator) AllocateNext() (int, error) {
   157  	offset, ok, err := r.alloc.AllocateNext()
   158  	if err != nil {
   159  		r.metrics.incrementAllocationErrors("dynamic")
   160  		return 0, err
   161  	}
   162  	if !ok {
   163  		r.metrics.incrementAllocationErrors("dynamic")
   164  		return 0, ErrFull
   165  	}
   166  
   167  	// update metrics
   168  	r.metrics.incrementAllocations("dynamic")
   169  	r.metrics.setAllocated(r.Used())
   170  	r.metrics.setAvailable(r.Free())
   171  
   172  	return r.portRange.Base + offset, nil
   173  }
   174  
   175  // ForEach calls the provided function for each allocated port.
   176  func (r *PortAllocator) ForEach(fn func(int)) {
   177  	r.alloc.ForEach(func(offset int) {
   178  		fn(r.portRange.Base + offset)
   179  	})
   180  }
   181  
   182  // Release releases the port back to the pool. Releasing an
   183  // unallocated port or a port out of the range is a no-op and
   184  // returns no error.
   185  func (r *PortAllocator) Release(port int) error {
   186  	ok, offset := r.contains(port)
   187  	if !ok {
   188  		klog.Warningf("port is not in the range when release it. port: %v", port)
   189  		return nil
   190  	}
   191  
   192  	err := r.alloc.Release(offset)
   193  	if err == nil {
   194  		// update metrics
   195  		r.metrics.setAllocated(r.Used())
   196  		r.metrics.setAvailable(r.Free())
   197  	}
   198  	return err
   199  }
   200  
   201  // Has returns true if the provided port is already allocated and a call
   202  // to Allocate(port) would fail with ErrAllocated.
   203  func (r *PortAllocator) Has(port int) bool {
   204  	ok, offset := r.contains(port)
   205  	if !ok {
   206  		return false
   207  	}
   208  
   209  	return r.alloc.Has(offset)
   210  }
   211  
   212  // Snapshot saves the current state of the pool.
   213  func (r *PortAllocator) Snapshot(dst *api.RangeAllocation) error {
   214  	snapshottable, ok := r.alloc.(allocator.Snapshottable)
   215  	if !ok {
   216  		return fmt.Errorf("not a snapshottable allocator")
   217  	}
   218  	rangeString, data := snapshottable.Snapshot()
   219  	dst.Range = rangeString
   220  	dst.Data = data
   221  	return nil
   222  }
   223  
   224  // Restore restores the pool to the previously captured state. ErrMismatchedNetwork
   225  // is returned if the provided port range doesn't exactly match the previous range.
   226  func (r *PortAllocator) Restore(pr net.PortRange, data []byte) error {
   227  	if pr.String() != r.portRange.String() {
   228  		return ErrMismatchedNetwork
   229  	}
   230  	snapshottable, ok := r.alloc.(allocator.Snapshottable)
   231  	if !ok {
   232  		return fmt.Errorf("not a snapshottable allocator")
   233  	}
   234  	return snapshottable.Restore(pr.String(), data)
   235  }
   236  
   237  // contains returns true and the offset if the port is in the range, and false
   238  // and nil otherwise.
   239  func (r *PortAllocator) contains(port int) (bool, int) {
   240  	if !r.portRange.Contains(port) {
   241  		return false, 0
   242  	}
   243  
   244  	offset := port - r.portRange.Base
   245  	return true, offset
   246  }
   247  
   248  // Destroy shuts down internal allocator.
   249  func (r *PortAllocator) Destroy() {
   250  	r.alloc.Destroy()
   251  }
   252  
   253  // EnableMetrics enables metrics recording.
   254  func (r *PortAllocator) EnableMetrics() {
   255  	registerMetrics()
   256  	r.metrics = &metricsRecorder{}
   257  }
   258  
   259  // calculateRangeOffset estimates the offset used on the range for statically allocation based on
   260  // the following formula `min(max($min, rangeSize/$step), $max)`, described as ~never less than
   261  // $min or more than $max, with a graduated step function between them~. The function returns 0
   262  // if any of the parameters is invalid.
   263  func calculateRangeOffset(pr net.PortRange) int {
   264  	// default values for min(max($min, rangeSize/$step), $max)
   265  	const (
   266  		min  = 16
   267  		max  = 128
   268  		step = 32
   269  	)
   270  
   271  	rangeSize := pr.Size
   272  	// offset should always be smaller than the range size
   273  	if rangeSize <= min {
   274  		return 0
   275  	}
   276  
   277  	offset := rangeSize / step
   278  	if offset < min {
   279  		return min
   280  	}
   281  	if offset > max {
   282  		return max
   283  	}
   284  	return int(offset)
   285  }