github.com/google/cloudprober@v0.11.3/rds/kubernetes/pods.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  	"sync"
    22  	"time"
    23  
    24  	"github.com/golang/protobuf/proto"
    25  	"github.com/google/cloudprober/logger"
    26  	configpb "github.com/google/cloudprober/rds/kubernetes/proto"
    27  	pb "github.com/google/cloudprober/rds/proto"
    28  	"github.com/google/cloudprober/rds/server/filter"
    29  )
    30  
    31  type podsLister struct {
    32  	c         *configpb.Pods
    33  	namespace string
    34  	kClient   *client
    35  
    36  	mu    sync.RWMutex // Mutex for names and cache
    37  	keys  []resourceKey
    38  	cache map[resourceKey]*podInfo
    39  	l     *logger.Logger
    40  }
    41  
    42  func podsURL(ns string) string {
    43  	if ns == "" {
    44  		return "api/v1/pods"
    45  	}
    46  	return fmt.Sprintf("api/v1/namespaces/%s/pods", ns)
    47  }
    48  
    49  func (pl *podsLister) listResources(req *pb.ListResourcesRequest) ([]*pb.Resource, error) {
    50  	var resources []*pb.Resource
    51  
    52  	allFilters, err := filter.ParseFilters(req.GetFilter(), SupportedFilters.RegexFilterKeys, "")
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	nameFilter, nsFilter, labelsFilter := allFilters.RegexFilters["name"], allFilters.RegexFilters["namespace"], allFilters.LabelsFilter
    58  
    59  	pl.mu.RLock()
    60  	defer pl.mu.RUnlock()
    61  
    62  	for _, key := range pl.keys {
    63  		if nameFilter != nil && !nameFilter.Match(key.name, pl.l) {
    64  			continue
    65  		}
    66  
    67  		pod := pl.cache[key]
    68  		if nsFilter != nil && !nsFilter.Match(pod.Metadata.Namespace, pl.l) {
    69  			continue
    70  		}
    71  		if labelsFilter != nil && !labelsFilter.Match(pod.Metadata.Labels, pl.l) {
    72  			continue
    73  		}
    74  
    75  		resources = append(resources, &pb.Resource{
    76  			Name:   proto.String(key.name),
    77  			Ip:     proto.String(pod.Status.PodIP),
    78  			Labels: pod.Metadata.Labels,
    79  		})
    80  	}
    81  
    82  	pl.l.Infof("kubernetes.listResources: returning %d pods", len(resources))
    83  	return resources, nil
    84  }
    85  
    86  type podInfo struct {
    87  	Metadata kMetadata
    88  	Status   struct {
    89  		Phase string
    90  		PodIP string
    91  	}
    92  }
    93  
    94  func parsePodsJSON(resp []byte) (keys []resourceKey, pods map[resourceKey]*podInfo, err error) {
    95  	var itemList struct {
    96  		Items []*podInfo
    97  	}
    98  
    99  	if err = json.Unmarshal(resp, &itemList); err != nil {
   100  		return
   101  	}
   102  
   103  	keys = make([]resourceKey, len(itemList.Items))
   104  	pods = make(map[resourceKey]*podInfo)
   105  	for i, item := range itemList.Items {
   106  		if item.Status.Phase != "Running" {
   107  			continue
   108  		}
   109  		keys[i] = resourceKey{item.Metadata.Namespace, item.Metadata.Name}
   110  		pods[keys[i]] = item
   111  	}
   112  
   113  	return
   114  }
   115  
   116  func (pl *podsLister) expand() {
   117  	resp, err := pl.kClient.getURL(podsURL(pl.namespace))
   118  	if err != nil {
   119  		pl.l.Warningf("podsLister.expand(): error while getting pods list from API: %v", err)
   120  	}
   121  
   122  	keys, pods, err := parsePodsJSON(resp)
   123  	if err != nil {
   124  		pl.l.Warningf("podsLister.expand(): error while parsing pods API response (%s): %v", string(resp), err)
   125  	}
   126  
   127  	pl.l.Infof("podsLister.expand(): got %d pods", len(keys))
   128  
   129  	pl.mu.Lock()
   130  	defer pl.mu.Unlock()
   131  	pl.keys = keys
   132  	pl.cache = pods
   133  }
   134  
   135  func newPodsLister(c *configpb.Pods, namespace string, reEvalInterval time.Duration, kc *client, l *logger.Logger) (*podsLister, error) {
   136  	pl := &podsLister{
   137  		c:         c,
   138  		namespace: namespace,
   139  		kClient:   kc,
   140  		l:         l,
   141  	}
   142  
   143  	go func() {
   144  		pl.expand()
   145  		// Introduce a random delay between 0-reEvalInterval before
   146  		// starting the refresh loop. If there are multiple cloudprober
   147  		// gceInstances, this will make sure that each instance calls GCE
   148  		// API at a different point of time.
   149  		rand.Seed(time.Now().UnixNano())
   150  		randomDelaySec := rand.Intn(int(reEvalInterval.Seconds()))
   151  		time.Sleep(time.Duration(randomDelaySec) * time.Second)
   152  		for range time.Tick(reEvalInterval) {
   153  			pl.expand()
   154  		}
   155  	}()
   156  
   157  	return pl, nil
   158  }