Repository: NetApp/harvest
Pull request #735 (2021-12-09 15:27:45)

feat: include plugin label agent


Patch:
diff --git a/cmd/poller/plugin/README.md b/cmd/poller/plugin/README.md
index d4e2ca93..2509c03f 100644
--- a/cmd/poller/plugin/README.md
+++ b/cmd/poller/plugin/README.md
@@ -120,6 +120,9 @@ Notice that the rules are executed in the same order as you've added them. List
   - [exclude_equals](#exclude_equals)
   - [exclude_contains](#exclude_contains)
   - [exclude_regex](#exclude_regex)
+  - [include_equals](#include_equals)
+  - [include_contains](#include_contains)
+  - [include_regex](#include_regex)
   - [value_mapping](#value_mapping)
   - [value_to_num](#value_to_num)
 
@@ -275,7 +278,7 @@ exclude_contains: LABEL `VALUE`
 Example:
 
 ```yaml
-exclude_equals: vol_type `flexgroup_`
+exclude_contains: vol_type `flexgroup_`
 # all instances, which have label "vol_type" which contain
 # "flexgroup_" will not be exported
 ```
@@ -287,18 +290,74 @@ Same as `exclude_equals`, but will use a regular expression and all matching ins
 Rule syntax:
 
 ```yaml
-exclude_contains: LABEL `REGEX`
+exclude_regex: LABEL `REGEX`
 # label name - regular expression
 ```
 
 Example:
 
 ```yaml
-exclude_equals: vol_type `flexgroup_`
+exclude_regex: vol_type `^flex`
+# all instances, which have label "vol_type" which starts with
+# "flex" will not be exported
+```
+
+## include_equals
+
+Include each instance, if the value of `LABEL` is exactly `VALUE`. Include means that metrics for this instance will be exported and instances that do not match will not be exported.
+
+Rule syntax:
+
+```yaml
+include_equals: LABEL `VALUE`
+# label name - label value
+```
+
+Example:
+
+```yaml
+include_equals: vol_type `flexgroup_constituent`
+# all instances, which have label "vol_type" with value
+# "flexgroup_constituent" will be exported
+```
+
+## include_contains
+
+Same as `include_equals`, but all labels that *contain* `VALUE` will be included
+
+Rule syntax:
+
+```yaml
+include_contains: LABEL `VALUE`
+# label name - label value
+```
+
+Example:
+
+```yaml
+include_contains: vol_type `flexgroup_`
 # all instances, which have label "vol_type" which contain
-# "flexgroup_" will not be exported
+# "flexgroup_" will be exported
+```
+
+## include_regex
+
+Same as `include_equals`, but a regular expression will be used for inclusion. Similar to the other includes, all matching instances will be included and all non-matching will not be exported.
+
+Rule syntax:
+
+```yaml
+include_regex: LABEL `REGEX`
+# label name - regular expression
 ```
 
+Example:
+
+```yaml
+include_regex: vol_type `^flex`
+# all instances, which have label "vol_type" which starts with
+# "flex" will be exported
+```
 
 ## value_mapping
 
diff --git a/cmd/poller/plugin/label_agent/label_agent.go b/cmd/poller/plugin/label_agent/label_agent.go
index 7897d934..926f8c3a 100644
--- a/cmd/poller/plugin/label_agent/label_agent.go
+++ b/cmd/poller/plugin/label_agent/label_agent.go
@@ -24,6 +24,9 @@ type LabelAgent struct {
 	excludeEqualsRules   []excludeEqualsRule
 	excludeContainsRules []excludeContainsRule
 	excludeRegexRules    []excludeRegexRule
+	includeEqualsRules   []includeEqualsRule
+	includeContainsRules []includeContainsRule
+	includeRegexRules    []includeRegexRule
 	valueToNumRules      []valueToNumRule
 }
 
@@ -170,7 +173,10 @@ func (me *LabelAgent) excludeEquals(instance *matrix.Instance) {
 	for _, r := range me.excludeEqualsRules {
 		if instance.GetLabel(r.label) == r.value {
 			instance.SetExportable(false)
-			me.Logger.Trace().Msgf("excludeEquals: (%s) [%s] instance with labels [%s] => excluded", r.label, r.value, instance.GetLabels().String())
+			me.Logger.Trace().Str("label", r.label).
+				Str("value", r.value).
+				Str("instance labels", instance.GetLabels().String()).
+				Msg("excludeEquals: excluded")
 			break
 		}
 	}
@@ -181,7 +187,10 @@ func (me *LabelAgent) excludeContains(instance *matrix.Instance) {
 	for _, r := range me.excludeContainsRules {
 		if strings.Contains(instance.GetLabel(r.label), r.value) {
 			instance.SetExportable(false)
-			me.Logger.Trace().Msgf("excludeContains: (%s) [%s] instance with labels [%s] => excluded", r.label, r.value, instance.GetLabels().String())
+			me.Logger.Trace().Str("label", r.label).
+				Str("value", r.value).
+				Str("instance labels", instance.GetLabels().String()).
+				Msg("excludeContains: excluded")
 			break
 		}
 	}
@@ -192,12 +201,65 @@ func (me *LabelAgent) excludeRegex(instance *matrix.Instance) {
 	for _, r := range me.excludeRegexRules {
 		if r.reg.MatchString(instance.GetLabel(r.label)) {
 			instance.SetExportable(false)
-			me.Logger.Trace().Msgf("excludeEquals: (%s) [%s] instance with labels [%s] => excluded", r.label, r.reg.String(), instance.GetLabels().String())
+			me.Logger.Trace().Str("label", r.label).
+				Str("regex", r.reg.String()).
+				Str("instance labels", instance.GetLabels().String()).
+				Msg("excludeRegex: excluded")
 			break
 		}
 	}
 }
 
+// if label is not equal to value, set instance as non-exportable
+func (me *LabelAgent) includeEquals(instance *matrix.Instance) {
+	isExport := false
+	for _, r := range me.includeEqualsRules {
+		if instance.GetLabel(r.label) == r.value {
+			isExport = true
+			me.Logger.Trace().Str("label", r.label).
+				Str("value", r.value).
+				Str("instance labels", instance.GetLabels().String()).
+				Msg("includeEquals: included")
+			break
+		}
+	}
+	instance.SetExportable(isExport)
+}
+
+// if label does not contains value, set instance as non-exportable
+func (me *LabelAgent) includeContains(instance *matrix.Instance) {
+	isExport := false
+	for _, r := range me.includeContainsRules {
+		if strings.Contains(instance.GetLabel(r.label), r.value) {
+			isExport = true
+			me.Logger.Trace().Str("label", r.label).
+				Str("value", r.value).
+				Str("instance labels", instance.GetLabels().String()).
+				Msg("includeContains: included")
+			break
+		}
+	}
+	instance.SetExportable(isExport)
+}
+
+// if label does not match regex, do not export the instance or with fewer negatives
+// only export instances with a matching (regex) label
+// if an instance does not match the regex label it will not be exported
+func (me *LabelAgent) includeRegex(instance *matrix.Instance) {
+	isExport := false
+	for _, r := range me.includeRegexRules {
+		if r.reg.MatchString(instance.GetLabel(r.label)) {
+			isExport = true
+			me.Logger.Trace().Str("label", r.label).
+				Str("regex", r.reg.String()).
+				Str("instance labels", instance.GetLabels().String()).
+				Msg("includeRegex: included")
+			break
+		}
+	}
+	instance.SetExportable(isExport)
+}
+
 func (me *LabelAgent) mapValues(m *matrix.Matrix) error {
 
 	var (
diff --git a/cmd/poller/plugin/label_agent/parse_rules.go b/cmd/poller/plugin/label_agent/parse_rules.go
index d4cb8a18..967638a9 100644
--- a/cmd/poller/plugin/label_agent/parse_rules.go
+++ b/cmd/poller/plugin/label_agent/parse_rules.go
@@ -52,6 +52,12 @@ func (me *LabelAgent) parseRules() int {
 				me.parseExcludeContainsRule(rule)
 			case "exclude_regex":
 				me.parseExcludeRegexRule(rule)
+			case "include_equals":
+				me.parseIncludeEqualsRule(rule)
+			case "include_contains":
+				me.parseIncludeContainsRule(rule)
+			case "include_regex":
+				me.parseIncludeRegexRule(rule)
 			case "value_to_num":
 				me.parseValueToNumRule(rule)
 			default:
@@ -110,6 +116,21 @@ func (me *LabelAgent) parseRules() int {
 		count += len(me.excludeRegexRules)
 	}
 
+	if len(me.includeEqualsRules) != 0 {
+		me.actions = append(me.actions, me.includeEquals)
+		count += len(me.includeEqualsRules)
+	}
+
+	if len(me.includeContainsRules) != 0 {
+		me.actions = append(me.actions, me.includeContains)
+		count += len(me.includeContainsRules)
+	}
+
+	if len(me.includeRegexRules) != 0 {
+		me.actions = append(me.actions, me.includeRegex)
+		count += len(me.includeRegexRules)
+	}
+
 	count += len(me.valueToNumRules)
 
 	return count
@@ -375,6 +396,63 @@ func (me *LabelAgent) parseExcludeRegexRule(rule string) {
 	}
 }
 
+type includeEqualsRule struct {
+	label string
+	value string
+}
+
+// example rule
+// vol_type `flexgroup_constituent`
+// all instances with matching label type, will be exported
+
+func (me *LabelAgent) parseIncludeEqualsRule(rule string) {
+	if fields := strings.SplitN(rule, " `", 2); len(fields) == 2 {
+		r := includeEqualsRule{label: fields[0]}
+		r.value = strings.TrimSuffix(fields[1], "`")
+		me.includeEqualsRules = append(me.includeEqualsRules, r)
+		me.Logger.Debug().Msgf("(include_equals) parsed rule [%v]", r)
+	} else {
+		me.Logger.Warn().Str("rule", rule).Msg("(include_equals) rule definition should have two fields")
+	}
+}
+
+type includeContainsRule struct {
+	label string
+	value string
+}
+
+func (me *LabelAgent) parseIncludeContainsRule(rule string) {
+	if fields := strings.SplitN(rule, " `", 2); len(fields) == 2 {
+		r := includeContainsRule{label: fields[0]}
+		r.value = strings.TrimSuffix(fields[1], "`")
+		me.includeContainsRules = append(me.includeContainsRules, r)
+		me.Logger.Debug().Msgf("(include_contains) parsed rule [%v]", r)
+	} else {
+		me.Logger.Error().Stack().Str("rule", rule).Err(nil).Msg("(include_contains) rule definition should have two fields")
+	}
+}
+
+type includeRegexRule struct {
+	label string
+	reg   *regexp.Regexp
+}
+
+func (me *LabelAgent) parseIncludeRegexRule(rule string) {
+	if fields := strings.SplitN(rule, " `", 2); len(fields) == 2 {
+		r := includeRegexRule{label: fields[0]}
+		var err error
+		if r.reg, err = regexp.Compile(strings.TrimSuffix(fields[1], "`")); err == nil {
+			me.includeRegexRules = append(me.includeRegexRules, r)
+			me.Logger.Debug().Str("regex", r.reg.String()).Msg("(include_regex) compiled regex")
+			me.Logger.Debug().Msgf("(include_regex) parsed rule [%v]", r)
+		} else {
+			me.Logger.Error().Stack().Err(err).Msgf("(include_regex) compile regex:")
+		}
+	} else {
+		me.Logger.Error().Stack().Str("rule", rule).Err(nil).Msg("(include_regex) rule definition should have two fields")
+	}
+}
+
 type valueToNumRule struct {
 	metric       string
 	label        string


Test patch:
diff --git a/cmd/poller/plugin/label_agent/label_agent_test.go b/cmd/poller/plugin/label_agent/label_agent_test.go
index d16502dd..ca9008ee 100644
--- a/cmd/poller/plugin/label_agent/label_agent_test.go
+++ b/cmd/poller/plugin/label_agent/label_agent_test.go
@@ -41,6 +41,12 @@ func TestInitPlugin(t *testing.T) {
 	params.NewChildS("exclude_contains", "").AddChild(params.NewChildS("", "A `_aaa_`"))
 	// exclude instance of label value has prefix "aaa_" followed by at least one digit
 	params.NewChildS("exclude_regex", "").AddChild(params.NewChildS("", "A `^aaa_\\d+$`"))
+	// include instance if label "A" has value "aaa bbb ccc"
+	params.NewChildS("include_equals", "").AddChild(params.NewChildS("", "A `aaa bbb ccc`"))
+	// include instance if label "A" contains "_aaa_"
+	params.NewChildS("include_contains", "").AddChild(params.NewChildS("", "A `_aaa_`"))
+	// include instance of label value has prefix "aaa_" followed by at least one digit
+	params.NewChildS("include_regex", "").AddChild(params.NewChildS("", "A `^aaa_\\d+$`"))
 	// create metric "new_status", if label "state" is one of the up/ok[zapi/rest], map metric value to respective index
 	params.NewChildS("value_to_num", "").AddChild(params.NewChildS("", "new_status state up ok"))
 	// create metric "new_stage", but if none of the values is matching, use default value "4"
@@ -211,6 +217,70 @@ func TestExcludeRegexRule(t *testing.T) {
 	}
 }
 
+func TestIncludeEqualsRule(t *testing.T) {
+	// should match
+	instanceYes := matrix.NewInstance(0)
+	instanceYes.SetLabel("A", "aaa bbb ccc")
+
+	// should not match
+	instanceNo := matrix.NewInstance(1)
+	instanceNo.SetLabel("A", "aaa bbb")
+	instanceNo.SetLabel("B", "aaa bbb ccc")
+
+	p.includeEquals(instanceYes)
+	p.includeEquals(instanceNo)
+
+	if !instanceYes.IsExportable() {
+		t.Error("InstanceYes should have been included")
+	}
+
+	if instanceNo.IsExportable() {
+		t.Error("instanceNo should not have been included")
+	}
+}
+
+func TestIncludeContainsRule(t *testing.T) {
+	// should match
+	instanceYes := matrix.NewInstance(0)
+	instanceYes.SetLabel("A", "xxx_aaa_xxx")
+
+	// should not match
+	instanceNo := matrix.NewInstance(1)
+	instanceNo.SetLabel("A", "_aaa")
+
+	p.includeContains(instanceYes)
+	p.includeContains(instanceNo)
+
+	if !instanceYes.IsExportable() {
+		t.Error("InstanceYes should have been included")
+	}
+
+	if instanceNo.IsExportable() {
+		t.Error("instanceNo should not have been included")
+	}
+}
+
+func TestIncludeRegexRule(t *testing.T) {
+	// should match
+	instanceYes := matrix.NewInstance(0)
+	instanceYes.SetLabel("A", "aaa_123")
+
+	// should not match
+	instanceNo := matrix.NewInstance(1)
+	instanceNo.SetLabel("A", "aaa_123!")
+
+	p.includeRegex(instanceYes)
+	p.includeRegex(instanceNo)
+
+	if !instanceYes.IsExportable() {
+		t.Error("InstanceYes should have been included")
+	}
+
+	if instanceNo.IsExportable() {
+		t.Error("instanceNo should not have been included")
+	}
+}
+
 func TestValueToNumRule(t *testing.T) {
 
 	var (