k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/service/portallocator/operation.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 // Encapsulates the semantics of a port allocation 'transaction': 20 // It is better to leak ports than to double-allocate them, 21 // so we allocate immediately, but defer release. 22 // On commit we best-effort release the deferred releases. 23 // On rollback we best-effort release any allocations we did. 24 // 25 // Pattern for use: 26 // 27 // op := StartPortAllocationOperation(...) 28 // defer op.Finish 29 // ... 30 // write(updatedOwner) 31 // 32 // / op.Commit() 33 type PortAllocationOperation struct { 34 pa Interface 35 allocated []int 36 releaseDeferred []int 37 shouldRollback bool 38 dryRun bool 39 } 40 41 // Creates a portAllocationOperation, tracking a set of allocations & releases 42 // If dryRun is specified, never actually allocate or release anything 43 func StartOperation(pa Interface, dryRun bool) *PortAllocationOperation { 44 op := &PortAllocationOperation{} 45 op.pa = pa 46 op.allocated = []int{} 47 op.releaseDeferred = []int{} 48 op.shouldRollback = true 49 op.dryRun = dryRun 50 return op 51 } 52 53 // Will rollback unless marked as shouldRollback = false by a Commit(). Call from a defer block 54 func (op *PortAllocationOperation) Finish() { 55 if op.shouldRollback { 56 op.Rollback() 57 } 58 } 59 60 // (Try to) undo any operations we did 61 func (op *PortAllocationOperation) Rollback() []error { 62 if op.dryRun { 63 return nil 64 } 65 66 errors := []error{} 67 68 for _, allocated := range op.allocated { 69 err := op.pa.Release(allocated) 70 if err != nil { 71 errors = append(errors, err) 72 } 73 } 74 75 if len(errors) == 0 { 76 return nil 77 } 78 return errors 79 } 80 81 // (Try to) perform any deferred operations. 82 // Note that even if this fails, we don't rollback; we always want to err on the side of over-allocation, 83 // and Commit should be called _after_ the owner is written 84 func (op *PortAllocationOperation) Commit() []error { 85 if op.dryRun { 86 return nil 87 } 88 89 errors := []error{} 90 91 for _, release := range op.releaseDeferred { 92 err := op.pa.Release(release) 93 if err != nil { 94 errors = append(errors, err) 95 } 96 } 97 98 // Even on error, we don't rollback 99 // Problems should be fixed by an eventual reconciliation / restart 100 op.shouldRollback = false 101 102 if len(errors) == 0 { 103 return nil 104 } 105 106 return errors 107 } 108 109 // Allocates a port, and record it for future rollback 110 func (op *PortAllocationOperation) Allocate(port int) error { 111 if op.dryRun { 112 if op.pa.Has(port) { 113 return ErrAllocated 114 } 115 for _, a := range op.allocated { 116 if port == a { 117 return ErrAllocated 118 } 119 } 120 op.allocated = append(op.allocated, port) 121 return nil 122 } 123 124 err := op.pa.Allocate(port) 125 if err == nil { 126 op.allocated = append(op.allocated, port) 127 } 128 return err 129 } 130 131 // Allocates a port, and record it for future rollback 132 func (op *PortAllocationOperation) AllocateNext() (int, error) { 133 if op.dryRun { 134 // Find the max element of the allocated ports array. 135 // If no ports are already being allocated by this operation, 136 // then choose a sensible guess for a dummy port number 137 var lastPort int 138 for _, allocatedPort := range op.allocated { 139 if allocatedPort > lastPort { 140 lastPort = allocatedPort 141 } 142 } 143 if len(op.allocated) == 0 { 144 lastPort = 32768 145 } 146 147 // Try to find the next non allocated port. 148 // If too many ports are full, just reuse one, 149 // since this is just a dummy value. 150 for port := lastPort + 1; port < 100; port++ { 151 err := op.Allocate(port) 152 if err == nil { 153 return port, nil 154 } 155 } 156 op.allocated = append(op.allocated, lastPort+1) 157 return lastPort + 1, nil 158 } 159 160 port, err := op.pa.AllocateNext() 161 if err == nil { 162 op.allocated = append(op.allocated, port) 163 } 164 return port, err 165 } 166 167 // Marks a port so that it will be released if this operation Commits 168 func (op *PortAllocationOperation) ReleaseDeferred(port int) { 169 op.releaseDeferred = append(op.releaseDeferred, port) 170 }