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  }