github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/api/endpoints/util.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors All rights reserved. 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/kubernetes/pkg/api" 27 "k8s.io/kubernetes/pkg/types" 28 "k8s.io/kubernetes/pkg/util" 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 []api.EndpointSubset) []api.EndpointSubset { 36 // First map each unique port definition to the sets of hosts that 37 // offer it. 38 allAddrs := map[addressKey]*api.EndpointAddress{} 39 portToAddrReadyMap := map[api.EndpointPort]addressSet{} 40 for i := range subsets { 41 for _, port := range subsets[i].Ports { 42 for k := range subsets[i].Addresses { 43 mapAddressByPort(&subsets[i].Addresses[k], port, true, allAddrs, portToAddrReadyMap) 44 } 45 for k := range subsets[i].NotReadyAddresses { 46 mapAddressByPort(&subsets[i].NotReadyAddresses[k], port, false, 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][]api.EndpointPort{} 58 for port, addrs := range portToAddrReadyMap { 59 key := keyString(hashAddresses(addrs)) 60 keyToAddrReadyMap[key] = addrs 61 addrReadyMapKeyToPorts[key] = append(addrReadyMapKeyToPorts[key], port) 62 } 63 64 // Next, build the N-to-M association the API wants. 65 final := []api.EndpointSubset{} 66 for key, ports := range addrReadyMapKeyToPorts { 67 var readyAddrs, notReadyAddrs []api.EndpointAddress 68 for addr, ready := range keyToAddrReadyMap[key] { 69 if ready { 70 readyAddrs = append(readyAddrs, *addr) 71 } else { 72 notReadyAddrs = append(notReadyAddrs, *addr) 73 } 74 } 75 final = append(final, api.EndpointSubset{Addresses: readyAddrs, NotReadyAddresses: notReadyAddrs, Ports: ports}) 76 } 77 78 // Finally, sort it. 79 return SortSubsets(final) 80 } 81 82 // The sets of hosts must be de-duped, using IP+UID as the key. 83 type addressKey struct { 84 ip string 85 uid types.UID 86 } 87 88 // mapAddressByPort adds an address into a map by its ports, registering the address with a unique pointer, and preserving 89 // any existing ready state. 90 func mapAddressByPort(addr *api.EndpointAddress, port api.EndpointPort, ready bool, allAddrs map[addressKey]*api.EndpointAddress, portToAddrReadyMap map[api.EndpointPort]addressSet) *api.EndpointAddress { 91 // use addressKey to distinguish between two endpoints that are identical addresses 92 // but may have come from different hosts, for attribution. For instance, Mesos 93 // assigns pods the node IP, but the pods are distinct. 94 key := addressKey{ip: addr.IP} 95 if addr.TargetRef != nil { 96 key.uid = addr.TargetRef.UID 97 } 98 99 // Accumulate the address. The full EndpointAddress structure is preserved for use when 100 // we rebuild the subsets so that the final TargetRef has all of the necessary data. 101 existingAddress := allAddrs[key] 102 if existingAddress == nil { 103 // Make a copy so we don't write to the 104 // input args of this function. 105 existingAddress = &api.EndpointAddress{} 106 *existingAddress = *addr 107 allAddrs[key] = existingAddress 108 } 109 110 // Remember that this port maps to this address. 111 if _, found := portToAddrReadyMap[port]; !found { 112 portToAddrReadyMap[port] = addressSet{} 113 } 114 // if we have not yet recorded this port for this address, or if the previous 115 // state was ready, write the current ready state. not ready always trumps 116 // ready. 117 if wasReady, found := portToAddrReadyMap[port][existingAddress]; !found || wasReady { 118 portToAddrReadyMap[port][existingAddress] = ready 119 } 120 return existingAddress 121 } 122 123 type addressSet map[*api.EndpointAddress]bool 124 125 type addrReady struct { 126 addr *api.EndpointAddress 127 ready bool 128 } 129 130 func hashAddresses(addrs addressSet) string { 131 // Flatten the list of addresses into a string so it can be used as a 132 // map key. Unfortunately, DeepHashObject is implemented in terms of 133 // spew, and spew does not handle non-primitive map keys well. So 134 // first we collapse it into a slice, sort the slice, then hash that. 135 slice := make([]addrReady, 0, len(addrs)) 136 for k, ready := range addrs { 137 slice = append(slice, addrReady{k, ready}) 138 } 139 sort.Sort(addrsReady(slice)) 140 hasher := md5.New() 141 util.DeepHashObject(hasher, slice) 142 return hex.EncodeToString(hasher.Sum(nil)[0:]) 143 } 144 145 func lessAddrReady(a, b addrReady) bool { 146 // ready is not significant to hashing since we can't have duplicate addresses 147 return LessEndpointAddress(a.addr, b.addr) 148 } 149 150 type addrsReady []addrReady 151 152 func (sl addrsReady) Len() int { return len(sl) } 153 func (sl addrsReady) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 154 func (sl addrsReady) Less(i, j int) bool { 155 return lessAddrReady(sl[i], sl[j]) 156 } 157 158 func LessEndpointAddress(a, b *api.EndpointAddress) bool { 159 ipComparison := bytes.Compare([]byte(a.IP), []byte(b.IP)) 160 if ipComparison != 0 { 161 return ipComparison < 0 162 } 163 if b.TargetRef == nil { 164 return false 165 } 166 if a.TargetRef == nil { 167 return true 168 } 169 return a.TargetRef.UID < b.TargetRef.UID 170 } 171 172 type addrPtrsByIpAndUID []*api.EndpointAddress 173 174 func (sl addrPtrsByIpAndUID) Len() int { return len(sl) } 175 func (sl addrPtrsByIpAndUID) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 176 func (sl addrPtrsByIpAndUID) Less(i, j int) bool { 177 return LessEndpointAddress(sl[i], sl[j]) 178 } 179 180 // SortSubsets sorts an array of EndpointSubset objects in place. For ease of 181 // use it returns the input slice. 182 func SortSubsets(subsets []api.EndpointSubset) []api.EndpointSubset { 183 for i := range subsets { 184 ss := &subsets[i] 185 sort.Sort(addrsByIpAndUID(ss.Addresses)) 186 sort.Sort(addrsByIpAndUID(ss.NotReadyAddresses)) 187 sort.Sort(portsByHash(ss.Ports)) 188 } 189 sort.Sort(subsetsByHash(subsets)) 190 return subsets 191 } 192 193 func hashObject(hasher hash.Hash, obj interface{}) []byte { 194 util.DeepHashObject(hasher, obj) 195 return hasher.Sum(nil) 196 } 197 198 type subsetsByHash []api.EndpointSubset 199 200 func (sl subsetsByHash) Len() int { return len(sl) } 201 func (sl subsetsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 202 func (sl subsetsByHash) Less(i, j int) bool { 203 hasher := md5.New() 204 h1 := hashObject(hasher, sl[i]) 205 h2 := hashObject(hasher, sl[j]) 206 return bytes.Compare(h1, h2) < 0 207 } 208 209 type addrsByIpAndUID []api.EndpointAddress 210 211 func (sl addrsByIpAndUID) Len() int { return len(sl) } 212 func (sl addrsByIpAndUID) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 213 func (sl addrsByIpAndUID) Less(i, j int) bool { 214 return LessEndpointAddress(&sl[i], &sl[j]) 215 } 216 217 type portsByHash []api.EndpointPort 218 219 func (sl portsByHash) Len() int { return len(sl) } 220 func (sl portsByHash) Swap(i, j int) { sl[i], sl[j] = sl[j], sl[i] } 221 func (sl portsByHash) Less(i, j int) bool { 222 hasher := md5.New() 223 h1 := hashObject(hasher, sl[i]) 224 h2 := hashObject(hasher, sl[j]) 225 return bytes.Compare(h1, h2) < 0 226 }