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 }