github.com/google/cloudprober@v0.11.3/rds/kubernetes/services.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 servicesLister struct { 34 c *configpb.Services 35 namespace string 36 kClient *client 37 38 mu sync.RWMutex // Mutex for names and cache 39 keys []resourceKey 40 cache map[resourceKey]*serviceInfo 41 l *logger.Logger 42 } 43 44 func servicesURL(ns string) string { 45 if ns == "" { 46 return "api/v1/services" 47 } 48 return fmt.Sprintf("api/v1/namespaces/%s/services", ns) 49 } 50 51 func (lister *servicesLister) listResources(req *pb.ListResourcesRequest) ([]*pb.Resource, error) { 52 var resources []*pb.Resource 53 54 var svcName string 55 tok := strings.SplitN(req.GetResourcePath(), "/", 2) 56 if len(tok) == 2 { 57 svcName = 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 svcName != "" && key.name != svcName { 72 continue 73 } 74 75 if nameFilter != nil && !nameFilter.Match(key.name, lister.l) { 76 continue 77 } 78 79 svc := lister.cache[key] 80 if nsFilter != nil && !nsFilter.Match(svc.Metadata.Namespace, lister.l) { 81 continue 82 } 83 if labelsFilter != nil && !labelsFilter.Match(svc.Metadata.Labels, lister.l) { 84 continue 85 } 86 87 resources = append(resources, svc.resources(allFilters.RegexFilters["port"], req.GetIpConfig().GetIpType(), lister.l)...) 88 } 89 90 lister.l.Infof("kubernetes.listResources: returning %d services", len(resources)) 91 return resources, nil 92 } 93 94 type loadBalancerStatus struct { 95 Ingress []struct { 96 IP string 97 Hostname string 98 } 99 } 100 101 type serviceInfo struct { 102 Metadata kMetadata 103 Spec struct { 104 ClusterIP string 105 Ports []struct { 106 Name string 107 Port int 108 } 109 } 110 Status struct { 111 LoadBalancer loadBalancerStatus 112 } 113 } 114 115 func (si *serviceInfo) matchPorts(portFilter *filter.RegexFilter, l *logger.Logger) ([]int, map[int]string) { 116 ports, portNameMap := []int{}, make(map[int]string) 117 for _, port := range si.Spec.Ports { 118 // For unnamed ports, use port number. 119 portName := port.Name 120 if portName == "" { 121 portName = strconv.FormatInt(int64(port.Port), 10) 122 } 123 124 if portFilter != nil && !portFilter.Match(portName, l) { 125 continue 126 } 127 ports = append(ports, port.Port) 128 portNameMap[port.Port] = portName 129 } 130 return ports, portNameMap 131 } 132 133 // resources returns RDS resources corresponding to a service resource. Each 134 // service object can have multiple ports. 135 // 136 // a) If service has only 1 port or there is a port filter and only one port 137 // matches the port filter, we return only one RDS resource with same name as 138 // service name. 139 // b) If there are multiple ports, we create one RDS resource for each port and 140 // name each resource as: <service_name>_<port_name> 141 func (si *serviceInfo) resources(portFilter *filter.RegexFilter, reqIPType pb.IPConfig_IPType, l *logger.Logger) (resources []*pb.Resource) { 142 ports, portNameMap := si.matchPorts(portFilter, l) 143 for _, port := range ports { 144 resName := si.Metadata.Name 145 if len(ports) != 1 { 146 resName = fmt.Sprintf("%s_%s", si.Metadata.Name, portNameMap[port]) 147 } 148 149 res := &pb.Resource{ 150 Name: proto.String(resName), 151 Port: proto.Int32(int32(port)), 152 Labels: si.Metadata.Labels, 153 } 154 155 if reqIPType == pb.IPConfig_PUBLIC { 156 // If there is no ingress IP, skip the resource. 157 if len(si.Status.LoadBalancer.Ingress) == 0 { 158 continue 159 } 160 ingress := si.Status.LoadBalancer.Ingress[0] 161 162 res.Ip = proto.String(ingress.IP) 163 if ingress.IP == "" && ingress.Hostname != "" { 164 res.Ip = proto.String(ingress.Hostname) 165 } 166 } else { 167 res.Ip = proto.String(si.Spec.ClusterIP) 168 } 169 170 resources = append(resources, res) 171 } 172 return 173 } 174 175 func parseServicesJSON(resp []byte) (keys []resourceKey, services map[resourceKey]*serviceInfo, err error) { 176 var itemList struct { 177 Items []*serviceInfo 178 } 179 180 if err = json.Unmarshal(resp, &itemList); err != nil { 181 return 182 } 183 184 keys = make([]resourceKey, len(itemList.Items)) 185 services = make(map[resourceKey]*serviceInfo) 186 for i, item := range itemList.Items { 187 keys[i] = resourceKey{item.Metadata.Namespace, item.Metadata.Name} 188 services[keys[i]] = item 189 } 190 191 return 192 } 193 194 func (lister *servicesLister) expand() { 195 resp, err := lister.kClient.getURL(servicesURL(lister.namespace)) 196 if err != nil { 197 lister.l.Warningf("servicesLister.expand(): error while getting services list from API: %v", err) 198 } 199 200 keys, services, err := parseServicesJSON(resp) 201 if err != nil { 202 lister.l.Warningf("servicesLister.expand(): error while parsing services API response (%s): %v", string(resp), err) 203 } 204 205 lister.l.Infof("servicesLister.expand(): got %d services", len(keys)) 206 207 lister.mu.Lock() 208 defer lister.mu.Unlock() 209 lister.keys = keys 210 lister.cache = services 211 } 212 213 func newServicesLister(c *configpb.Services, namespace string, reEvalInterval time.Duration, kc *client, l *logger.Logger) (*servicesLister, error) { 214 lister := &servicesLister{ 215 c: c, 216 kClient: kc, 217 namespace: namespace, 218 l: l, 219 } 220 221 go func() { 222 lister.expand() 223 // Introduce a random delay between 0-reEvalInterval before 224 // starting the refresh loop. If there are multiple cloudprober 225 // gceInstances, this will make sure that each instance calls GCE 226 // API at a different point of time. 227 rand.Seed(time.Now().UnixNano()) 228 randomDelaySec := rand.Intn(int(reEvalInterval.Seconds())) 229 time.Sleep(time.Duration(randomDelaySec) * time.Second) 230 for range time.Tick(reEvalInterval) { 231 lister.expand() 232 } 233 }() 234 235 return lister, nil 236 }