k8s.io/kubernetes@v1.29.3/pkg/api/v1/endpoints/util.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 endpoints 18 19 import ( 20 "bytes" 21 "crypto/md5" 22 "encoding/hex" 23 "hash" 24 "sort" 25 26 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/types" 28 hashutil "k8s.io/kubernetes/pkg/util/hash" 29 ) 30 31 // RepackSubsets takes a slice of EndpointSubset objects, expands it to the full 32 // representation, and then repacks that into the canonical layout. This 33 // ensures that code which operates on these objects can rely on the common 34 // form for things like comparison. The result is a newly allocated slice. 35 func RepackSubsets(subsets []v1.EndpointSubset) []v1.EndpointSubset { 36 // First map each unique port definition to the sets of hosts that 37 // offer it. 38 allAddrs := map[addressKey]*v1.EndpointAddress{} 39 portToAddrReadyMap := map[v1.EndpointPort]addressSet{} 40 for i := range subsets { 41 if len(subsets[i].Ports) == 0 { 42 // Don't discard endpoints with no ports defined, use a sentinel. 43 mapAddressesByPort(&subsets[i], v1.EndpointPort{Port: -1}, allAddrs, portToAddrReadyMap) 44 } else { 45 for _, port := range subsets[i].Ports { 46 mapAddressesByPort(&subsets[i], port, allAddrs, portToAddrReadyMap) 47 } 48 } 49 } 50 51 // Next, map the sets of hosts to the sets of ports they offer. 52 // Go does not allow maps or slices as keys to maps, so we have 53 // to synthesize an artificial key and do a sort of 2-part 54 // associative entity. 55 type keyString string 56 keyToAddrReadyMap := map[keyString]addressSet{} 57 addrReadyMapKeyToPorts := map[keyString][]v1.EndpointPort{} 58 for port, addrs := range portToAddrReadyMap { 59 key := keyString(hashAddresses(addrs)) 60 keyToAddrReadyMap[key] = addrs 61 if port.Port > 0 { // avoid sentinels 62 addrReadyMapKeyToPorts[key] = append(addrReadyMapKeyToPorts[key], port) 63 } else { 64 if _, found := addrReadyMapKeyToPorts[key]; !found { 65 // Force it to be present in the map 66 addrReadyMapKeyToPorts[key] = nil 67 } 68 } 69 } 70 71 // Next, build the N-to-M association the API wants. 72 final := []v1.EndpointSubset{} 73 for key, ports := range addrReadyMapKeyToPorts { 74 var readyAddrs, notReadyAddrs []v1.EndpointAddress 75 for addr, ready := range keyToAddrReadyMap[key] { 76 if ready { 77 readyAddrs = append(readyAddrs, *addr) 78 } else { 79 notReadyAddrs = append(notReadyAddrs, *addr) 80 } 81 } 82 final = append(final, v1.EndpointSubset{Addresses: readyAddrs, NotReadyAddresses: notReadyAddrs, Ports: ports}) 83 } 84 85 // Finally, sort it. 86 return SortSubsets(final) 87 } 88 89 // The sets of hosts must be de-duped, using IP+UID as the key. 90 type addressKey struct { 91 ip string 92 uid types.UID 93 } 94 95 // mapAddressesByPort adds all ready and not-ready addresses into a map by a single port. 96 func mapAddressesByPort(subset *v1.EndpointSubset, port v1.EndpointPort, allAddrs map[addressKey]*v1.EndpointAddress, portToAddrReadyMap map[v1.EndpointPort]addressSet) { 97 for k := range subset.Addresses { 98 mapAddressByPort(&subset.Addresses[k], port, true, allAddrs, portToAddrReadyMap) 99 } 100 for k := range subset.NotReadyAddresses { 101 mapAddressByPort(&subset.NotReadyAddresses[k], port, false, allAddrs, portToAddrReadyMap) 102 } 103 } 104 105 // mapAddressByPort adds one address into a map by port, registering the address with a unique pointer, and preserving 106 // any existing ready state. 107 func mapAddressByPort(addr *v1.EndpointAddress, port v1.EndpointPort, ready bool, allAddrs map[addressKey]*v1.EndpointAddress, portToAddrReadyMap map[v1.EndpointPort]addressSet) *v1.EndpointAddress { 108 // use addressKey to distinguish between two endpoints that are identical addresses 109 // but may have come from different hosts, for attribution. For instance, Mesos 110 // assigns pods the node IP, but the pods are distinct. 111 key := addressKey{ip: addr.IP} 112 if addr.TargetRef != nil { 113 key.uid = addr.TargetRef.UID 114 } 115 116 // Accumulate the address. The full EndpointAddress structure is preserved for use when 117 // we rebuild the subsets so that the final TargetRef has all of the necessary data. 118 existingAddress := allAddrs[key] 119 if existingAddress == nil { 120 // Make a copy so we don't write to the 121 // input args of this function. 122 existingAddress = &v1.EndpointAddress{} 123 *existingAddress = *addr 124 allAddrs[key] = existingAddress 125 } 126 127 // Remember that this port maps to this address. 128 if _, found := portToAddrReadyMap[port]; !found { 129 portToAddrReadyMap[port] = addressSet{} 130 } 131 // if we have not yet recorded this port for this address, or if the previous 132 // state was ready, write the current ready state. not ready always trumps 133 // ready. 134 if wasReady, found := portToAddrReadyMap[port][existingAddress]; !found || wasReady { 135 portToAddrReadyMap[port][existingAddress] = ready 136 } 137 return existingAddress 138 } 139 140 type addressSet map[*v1.EndpointAddress]bool 141 142 type addrReady struct { 143 addr *v1.EndpointAddress 144 ready bool 145 } 146 147 func hashAddresses(addrs addressSet) string { 148 // Flatten the list of addresses into a string so it can be used as a 149 // map key. Unfortunately, DeepHashObject is implemented in terms of 150 // spew, and spew does not handle non-primitive map keys well. So 151 // first we collapse it into a slice, sort the slice, then hash that. 152 slice := make([]addrReady, 0, len(addrs)) 153 for k, ready := range addrs { 154 slice = append(slice, addrReady{k, ready}) 155 } 156 sort.Sort(addrsReady(slice)) 157 hasher := md5.New() 158 hashutil.DeepHashObject(hasher, slice) 159 return hex.EncodeToString(hasher.Sum(nil)[0:]) 160 } 161 162 func lessAddrReady(a, b addrReady) bool { 163 // ready is not significant to hashing since we can't have duplicate addresses 164 return LessEndpointAddress(a.addr, b.addr) 165 } 166 167 type addrsReady []addrReady 168 169 func (sl addrsReady) Len() int { return len(sl) } 170 func (sl addrsReady) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 171 func (sl addrsReady) Less(i, j int) bool { 172 return lessAddrReady(sl[i], sl[j]) 173 } 174 175 // LessEndpointAddress compares IP addresses lexicographically and returns true if first argument is lesser than second 176 func LessEndpointAddress(a, b *v1.EndpointAddress) bool { 177 ipComparison := bytes.Compare([]byte(a.IP), []byte(b.IP)) 178 if ipComparison != 0 { 179 return ipComparison < 0 180 } 181 if b.TargetRef == nil { 182 return false 183 } 184 if a.TargetRef == nil { 185 return true 186 } 187 return a.TargetRef.UID < b.TargetRef.UID 188 } 189 190 // SortSubsets sorts an array of EndpointSubset objects in place. For ease of 191 // use it returns the input slice. 192 func SortSubsets(subsets []v1.EndpointSubset) []v1.EndpointSubset { 193 for i := range subsets { 194 ss := &subsets[i] 195 sort.Sort(addrsByIPAndUID(ss.Addresses)) 196 sort.Sort(addrsByIPAndUID(ss.NotReadyAddresses)) 197 sort.Sort(portsByHash(ss.Ports)) 198 } 199 sort.Sort(subsetsByHash(subsets)) 200 return subsets 201 } 202 203 func hashObject(hasher hash.Hash, obj interface{}) []byte { 204 hashutil.DeepHashObject(hasher, obj) 205 return hasher.Sum(nil) 206 } 207 208 type subsetsByHash []v1.EndpointSubset 209 210 func (sl subsetsByHash) Len() int { return len(sl) } 211 func (sl subsetsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 212 func (sl subsetsByHash) Less(i, j int) bool { 213 hasher := md5.New() 214 h1 := hashObject(hasher, sl[i]) 215 h2 := hashObject(hasher, sl[j]) 216 return bytes.Compare(h1, h2) < 0 217 } 218 219 type addrsByIPAndUID []v1.EndpointAddress 220 221 func (sl addrsByIPAndUID) Len() int { return len(sl) } 222 func (sl addrsByIPAndUID) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 223 func (sl addrsByIPAndUID) Less(i, j int) bool { 224 return LessEndpointAddress(&sl[i], &sl[j]) 225 } 226 227 type portsByHash []v1.EndpointPort 228 229 func (sl portsByHash) Len() int { return len(sl) } 230 func (sl portsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 231 func (sl portsByHash) Less(i, j int) bool { 232 hasher := md5.New() 233 h1 := hashObject(hasher, sl[i]) 234 h2 := hashObject(hasher, sl[j]) 235 return bytes.Compare(h1, h2) < 0 236 }