github.com/google/cloudprober@v0.11.3/probes/options/labels.go (about)

     1  // Copyright 2017-2021 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 options
    16  
    17  import (
    18  	"regexp"
    19  	"strconv"
    20  	"strings"
    21  	"sync"
    22  
    23  	"github.com/google/cloudprober/targets/endpoint"
    24  
    25  	configpb "github.com/google/cloudprober/probes/proto"
    26  )
    27  
    28  // targetLabelType for target based additional labels
    29  type targetLabelType int
    30  
    31  // TargetLabelType enum values.
    32  const (
    33  	notTargetLabel targetLabelType = iota
    34  	label
    35  	name
    36  	port
    37  )
    38  
    39  var targetLabelRegex = regexp.MustCompile(`target.label.(.*)`)
    40  
    41  type targetToken struct {
    42  	tokenType targetLabelType
    43  	labelKey  string // target's label key.
    44  }
    45  
    46  // AdditionalLabel encapsulates additional labels to attach to probe results.
    47  type AdditionalLabel struct {
    48  	mu  sync.RWMutex
    49  	Key string
    50  
    51  	// If non-empty, additional label's value is independent of the target/
    52  	staticValue string
    53  
    54  	// This map will allow for quick value lookup for a target. It will be
    55  	// updated by the probe while updating targets.
    56  	valueForTarget map[string]string
    57  
    58  	// At the time of parsing we split the label value at the delimiters ('@').
    59  	// When we update an additional label for a target, we update the value
    60  	// parts that correspond to the substitution tokens and join them back.
    61  	valueParts []string
    62  
    63  	// Target based substitution tokens.
    64  	tokens []targetToken
    65  }
    66  
    67  // UpdateForTarget updates addtional label based on target's name and labels.
    68  func (al *AdditionalLabel) UpdateForTarget(ep endpoint.Endpoint) {
    69  	al.mu.Lock()
    70  	defer al.mu.Unlock()
    71  
    72  	// Return early if this label has a static value.
    73  	if al.staticValue != "" {
    74  		return
    75  	}
    76  
    77  	if al.valueForTarget == nil {
    78  		al.valueForTarget = make(map[string]string)
    79  	}
    80  
    81  	parts := append([]string{}, al.valueParts...)
    82  	for i, tok := range al.tokens {
    83  		switch tok.tokenType {
    84  		case name:
    85  			parts[2*i+1] = ep.Name
    86  		case port:
    87  			parts[2*i+1] = strconv.Itoa(ep.Port)
    88  		case label:
    89  			parts[2*i+1] = ep.Labels[tok.labelKey]
    90  		}
    91  	}
    92  	al.valueForTarget[ep.Name] = strings.Join(parts, "")
    93  }
    94  
    95  // KeyValueForTarget returns key, value pair for the given target.
    96  func (al *AdditionalLabel) KeyValueForTarget(targetName string) (key, val string) {
    97  	al.mu.RLock()
    98  	defer al.mu.RUnlock()
    99  
   100  	if al.staticValue != "" {
   101  		return al.Key, al.staticValue
   102  	}
   103  	return al.Key, al.valueForTarget[targetName]
   104  }
   105  
   106  // ParseAdditionalLabel parses an additional label proto message into an
   107  // AdditionalLabel struct.
   108  func ParseAdditionalLabel(alpb *configpb.AdditionalLabel) *AdditionalLabel {
   109  	al := &AdditionalLabel{
   110  		Key: alpb.GetKey(),
   111  	}
   112  
   113  	al.valueParts = strings.Split(alpb.GetValue(), "@")
   114  
   115  	// No tokens
   116  	if len(al.valueParts) == 1 {
   117  		al.staticValue = alpb.GetValue()
   118  		return al
   119  	}
   120  
   121  	// If there are even number of parts after the split above, that means we
   122  	// don't have an even number of delimiters ('@'). Assume that the last
   123  	// token is incomplete and attach '@' to the front of the last part.
   124  	// e.g. @target.name@:@target.port
   125  	//   valueParts: ["", "target.name", ":", "@target.port"]
   126  	lenParts := len(al.valueParts)
   127  	if lenParts%2 == 0 {
   128  		al.valueParts[lenParts-1] = "@" + al.valueParts[lenParts-1]
   129  	}
   130  	// tokens[i] -> parts[2*i+1]
   131  	// e.g. proto:@target.name@/@target.label.url@ -->
   132  	//   valueParts: ["proto:", "target.name", "/", "target.label.url", ""]
   133  	//   tokens:     ["target.name", "target.label.url"]
   134  	numTokens := (len(al.valueParts) - 1) / 2
   135  	for i := 0; i < numTokens; i++ {
   136  		tokStr := al.valueParts[2*i+1]
   137  		if tokStr == "target.name" {
   138  			al.tokens = append(al.tokens, targetToken{tokenType: name})
   139  			continue
   140  		}
   141  		if tokStr == "target.port" {
   142  			al.tokens = append(al.tokens, targetToken{tokenType: port})
   143  			continue
   144  		}
   145  		matches := targetLabelRegex.FindStringSubmatch(tokStr)
   146  		if len(matches) == 2 {
   147  			al.tokens = append(al.tokens, targetToken{tokenType: label, labelKey: matches[1]})
   148  		}
   149  	}
   150  
   151  	// if no valid tokens found, assign the value as it is.
   152  	if len(al.tokens) == 0 {
   153  		al.staticValue = alpb.GetValue()
   154  	}
   155  
   156  	return al
   157  }
   158  
   159  func parseAdditionalLabels(p *configpb.ProbeDef) []*AdditionalLabel {
   160  	var aLabels []*AdditionalLabel
   161  
   162  	for _, pb := range p.GetAdditionalLabel() {
   163  		aLabels = append(aLabels, ParseAdditionalLabel(pb))
   164  	}
   165  
   166  	return aLabels
   167  }