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  }