github.com/google/cloudprober@v0.11.3/rds/kubernetes/endpoints.go (about) 1 // Copyright 2019 The Cloudprober Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kubernetes 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "math/rand" 21 "strconv" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/golang/protobuf/proto" 27 "github.com/google/cloudprober/logger" 28 configpb "github.com/google/cloudprober/rds/kubernetes/proto" 29 pb "github.com/google/cloudprober/rds/proto" 30 "github.com/google/cloudprober/rds/server/filter" 31 ) 32 33 type epLister struct { 34 c *configpb.Endpoints 35 namespace string 36 kClient *client 37 38 mu sync.RWMutex // Mutex for names and cache 39 keys []resourceKey 40 cache map[resourceKey]*epInfo 41 l *logger.Logger 42 } 43 44 func epURL(ns string) string { 45 if ns == "" { 46 return "api/v1/endpoints" 47 } 48 return fmt.Sprintf("api/v1/namespaces/%s/endpoints", ns) 49 } 50 51 func (lister *epLister) listResources(req *pb.ListResourcesRequest) ([]*pb.Resource, error) { 52 var resources []*pb.Resource 53 54 var epName string 55 tok := strings.SplitN(req.GetResourcePath(), "/", 2) 56 if len(tok) == 2 { 57 epName = tok[1] 58 } 59 60 allFilters, err := filter.ParseFilters(req.GetFilter(), SupportedFilters.RegexFilterKeys, "") 61 if err != nil { 62 return nil, err 63 } 64 65 nameFilter, nsFilter, labelsFilter := allFilters.RegexFilters["name"], allFilters.RegexFilters["namespace"], allFilters.LabelsFilter 66 67 lister.mu.RLock() 68 defer lister.mu.RUnlock() 69 70 for _, key := range lister.keys { 71 if epName != "" && key.name != epName { 72 continue 73 } 74 75 if nameFilter != nil && !nameFilter.Match(key.name, lister.l) { 76 continue 77 } 78 79 epi := lister.cache[key] 80 if nsFilter != nil && !nsFilter.Match(epi.Metadata.Namespace, lister.l) { 81 continue 82 } 83 if labelsFilter != nil && !labelsFilter.Match(epi.Metadata.Labels, lister.l) { 84 continue 85 } 86 87 resources = append(resources, epi.resources(allFilters.RegexFilters["port"], lister.l)...) 88 } 89 90 lister.l.Infof("kubernetes.endpoints.listResources: returning %d resources", len(resources)) 91 return resources, nil 92 } 93 94 type epSubset struct { 95 Addresses []struct { 96 IP string 97 NodeName string 98 TargetRef struct { 99 Kind string 100 Name string 101 } 102 } 103 Ports []struct { 104 Name string 105 Port int 106 } 107 } 108 109 type epInfo struct { 110 Metadata kMetadata 111 Subsets []epSubset 112 } 113 114 // resources returns RDS resources corresponding to an endpoints resource. Each 115 // endpoints object can have multiple endpoint subsets and each subset in turn 116 // is composed of multiple addresses and ports. If an endpoint subset as 3 117 // addresses and 2 ports, there will be 6 resources corresponding to that 118 // subset. 119 func (epi *epInfo) resources(portFilter *filter.RegexFilter, l *logger.Logger) (resources []*pb.Resource) { 120 for _, eps := range epi.Subsets { 121 // There is usually one port, but there can be multiple ports, e.g. 9313 122 // and 9314. 123 for _, port := range eps.Ports { 124 // For unnamed ports, use port number. 125 portName := port.Name 126 if portName == "" { 127 portName = strconv.FormatInt(int64(port.Port), 10) 128 } 129 130 if portFilter != nil && !portFilter.Match(portName, l) { 131 continue 132 } 133 134 for _, addr := range eps.Addresses { 135 // We name the resource as <endpoints_name>_<IP>_<port> 136 resName := fmt.Sprintf("%s_%s_%s", epi.Metadata.Name, addr.IP, portName) 137 138 labels := make(map[string]string) 139 for k, v := range epi.Metadata.Labels { 140 labels[k] = v 141 } 142 labels["node"] = addr.NodeName 143 // If adding labels, make a copy of the metadata labels. 144 if addr.TargetRef.Kind == "Pod" { 145 labels["pod"] = addr.TargetRef.Name 146 } 147 148 resources = append(resources, &pb.Resource{ 149 Name: proto.String(resName), 150 Ip: proto.String(addr.IP), 151 Port: proto.Int(port.Port), 152 Labels: labels, 153 }) 154 } 155 } 156 } 157 return 158 } 159 160 func parseEndpointsJSON(resp []byte) (keys []resourceKey, endpoints map[resourceKey]*epInfo, err error) { 161 var itemList struct { 162 Items []*epInfo 163 } 164 165 if err = json.Unmarshal(resp, &itemList); err != nil { 166 return 167 } 168 169 keys = make([]resourceKey, len(itemList.Items)) 170 endpoints = make(map[resourceKey]*epInfo) 171 for i, item := range itemList.Items { 172 keys[i] = resourceKey{item.Metadata.Namespace, item.Metadata.Name} 173 endpoints[keys[i]] = item 174 } 175 176 return 177 } 178 179 func (lister *epLister) expand() { 180 resp, err := lister.kClient.getURL(epURL(lister.namespace)) 181 if err != nil { 182 lister.l.Warningf("epLister.expand(): error while getting endpoints list from API: %v", err) 183 } 184 185 keys, endpoints, err := parseEndpointsJSON(resp) 186 if err != nil { 187 lister.l.Warningf("epLister.expand(): error while parsing endpoints API response (%s): %v", string(resp), err) 188 } 189 190 lister.l.Infof("epLister.expand(): got %d endpoints", len(keys)) 191 192 lister.mu.Lock() 193 defer lister.mu.Unlock() 194 lister.keys = keys 195 lister.cache = endpoints 196 } 197 198 func newEndpointsLister(c *configpb.Endpoints, namespace string, reEvalInterval time.Duration, kc *client, l *logger.Logger) (*epLister, error) { 199 lister := &epLister{ 200 c: c, 201 namespace: namespace, 202 kClient: kc, 203 l: l, 204 } 205 206 go func() { 207 lister.expand() 208 // Introduce a random delay between 0-reEvalInterval before 209 // starting the refresh loop. If there are multiple cloudprober 210 // gceInstances, this will make sure that each instance calls GCE 211 // API at a different point of time. 212 rand.Seed(time.Now().UnixNano()) 213 randomDelaySec := rand.Intn(int(reEvalInterval.Seconds())) 214 time.Sleep(time.Duration(randomDelaySec) * time.Second) 215 for range time.Tick(reEvalInterval) { 216 lister.expand() 217 } 218 }() 219 220 return lister, nil 221 }