Skip to content

Commit f9b5e60

Browse files
author
Xuewei Zhang
committed
Add e2e test for NPD
The first test is a very simple test. It installs NPD on a VM, and then verifies that NPD reports metric host_uptime in Prometheus format.
1 parent db2dbd1 commit f9b5e60

File tree

15 files changed

+1051
-20
lines changed

15 files changed

+1051
-20
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/*.tar.gz
44
ci.env
55
pr.env
6+
junit*.xml

Makefile

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ ifneq ($(BUILD_TAGS), "")
7575
BUILD_TAGS:=-tags "$(BUILD_TAGS)"
7676
endif
7777

78-
7978
vet:
8079
GO111MODULE=on go list -mod vendor $(BUILD_TAGS) ./... | \
8180
grep -v "./vendor/*" | \
@@ -107,15 +106,24 @@ Dockerfile: Dockerfile.in
107106
sed -e 's|@BASEIMAGE@|$(BASEIMAGE)|g' $< >$@
108107

109108
test: vet fmt
110-
GO111MODULE=on go test -mod vendor -timeout=1m -v -race $(BUILD_TAGS) ./...
109+
GO111MODULE=on go test -mod vendor -timeout=1m -v -race -short $(BUILD_TAGS) ./...
110+
111+
e2e-test: vet fmt build-tar
112+
GO111MODULE=on go test -mod vendor -timeout=10m -v $(BUILD_TAGS) \
113+
./test/e2e/metriconly/... \
114+
-project=$(PROJECT) -zone=$(ZONE) \
115+
-image=$(VM_IMAGE) -image-project=$(IMAGE_PROJECT) \
116+
-ssh-user=$(SSH_USER) -ssh-key=$(SSH_KEY) \
117+
-npd-build-tar=`pwd`/$(TARBALL) \
118+
-artifacts-dir=$(ARTIFACTS)
111119

112120
build-binaries: ./bin/node-problem-detector ./bin/log-counter
113121

114122
build-container: build-binaries Dockerfile
115123
docker build -t $(IMAGE) .
116124

117125
build-tar: ./bin/node-problem-detector ./bin/log-counter
118-
tar -zcvf $(TARBALL) bin/ config/
126+
tar -zcvf $(TARBALL) bin/ config/ test/e2e-install.sh
119127
sha1sum $(TARBALL)
120128
md5sum $(TARBALL)
121129

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[Unit]
2+
Description=Node problem detector
3+
Wants=local-fs.target
4+
After=local-fs.target
5+
6+
[Service]
7+
Restart=always
8+
RestartSec=10
9+
ExecStart=/home/kubernetes/bin/node-problem-detector --v=2 --logtostderr --enable-k8s-exporter=false \
10+
--config.system-log-monitor=/home/kubernetes/node-problem-detector/config/kernel-monitor.json,/home/kubernetes/node-problem-detector/config/docker-monitor.json,/home/kubernetes/node-problem-detector/config/systemd-monitor.json \
11+
--config.custom-plugin-monitor=/home/kubernetes/node-problem-detector/config/kernel-monitor-counter.json,/home/kubernetes/node-problem-detector/config/systemd-monitor-counter.json \
12+
--config.system-stats-monitor=/home/kubernetes/node-problem-detector/config/system-stats-monitor.json
13+
14+
[Install]
15+
WantedBy=multi-user.target

pkg/util/metrics/fakes.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,6 @@ import (
2121
"reflect"
2222
)
2323

24-
// Int64MetricRepresentation represents a snapshot of an int64 metrics.
25-
// This is used for inspecting fake metrics.
26-
type Int64MetricRepresentation struct {
27-
// Name is the metric name.
28-
Name string
29-
// Labels contains all metric labels in key-value pair format.
30-
Labels map[string]string
31-
// Value is the value of the metric.
32-
Value int64
33-
}
34-
3524
// Int64MetricInterface is used to create test double for Int64Metric.
3625
type Int64MetricInterface interface {
3726
// Record records a measurement for the metric, with provided tags as metric labels.

pkg/util/metrics/helpers.go

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ package metrics
1818
import (
1919
"context"
2020
"fmt"
21+
"strings"
2122
"sync"
2223

24+
pcm "github.com/prometheus/client_model/go"
25+
"github.com/prometheus/common/expfmt"
2326
"go.opencensus.io/stats"
2427
"go.opencensus.io/stats/view"
2528
"go.opencensus.io/tag"
@@ -34,12 +37,6 @@ func init() {
3437
tagMapMutex.Unlock()
3538
}
3639

37-
// Int64Metric represents an int64 metric.
38-
type Int64Metric struct {
39-
name string
40-
measure *stats.Int64Measure
41-
}
42-
4340
// Aggregation defines how measurements should be aggregated into data points.
4441
type Aggregation string
4542

@@ -50,6 +47,23 @@ const (
5047
Sum Aggregation = "Sum"
5148
)
5249

50+
// Int64MetricRepresentation represents a snapshot of an int64 metrics.
51+
// This is used for inspecting metric internals.
52+
type Int64MetricRepresentation struct {
53+
// Name is the metric name.
54+
Name string
55+
// Labels contains all metric labels in key-value pair format.
56+
Labels map[string]string
57+
// Value is the value of the metric.
58+
Value int64
59+
}
60+
61+
// Int64Metric represents an int64 metric.
62+
type Int64Metric struct {
63+
name string
64+
measure *stats.Int64Measure
65+
}
66+
5367
// NewInt64Metric create a Int64Metric metric, returns nil when name is empty.
5468
func NewInt64Metric(name string, description string, unit string, aggregation Aggregation, tagNames []string) (*Int64Metric, error) {
5569
if name == "" {
@@ -106,6 +120,17 @@ func (metric *Int64Metric) Record(tags map[string]string, measurement int64) err
106120
metric.measure.M(measurement))
107121
}
108122

123+
// Float64MetricRepresentation represents a snapshot of a float64 metrics.
124+
// This is used for inspecting metric internals.
125+
type Float64MetricRepresentation struct {
126+
// Name is the metric name.
127+
Name string
128+
// Labels contains all metric labels in key-value pair format.
129+
Labels map[string]string
130+
// Value is the value of the metric.
131+
Value float64
132+
}
133+
109134
// Float64Metric represents an float64 metric.
110135
type Float64Metric struct {
111136
name string
@@ -187,3 +212,66 @@ func getTagKeysFromNames(tagNames []string) ([]tag.Key, error) {
187212
}
188213
return tagKeys, nil
189214
}
215+
216+
// ParsePrometheusMetrics parses Prometheus formatted metrics into metrics under Float64MetricRepresentation.
217+
//
218+
// Note: Prometheus's go library stores all counter/gauge-typed metric values under float64.
219+
func ParsePrometheusMetrics(metricsText string) ([]Float64MetricRepresentation, error) {
220+
var metrics []Float64MetricRepresentation
221+
222+
var textParser expfmt.TextParser
223+
metricFamilies, err := textParser.TextToMetricFamilies(strings.NewReader(metricsText))
224+
if err != nil {
225+
return metrics, err
226+
}
227+
228+
for _, metricFamily := range metricFamilies {
229+
for _, metric := range metricFamily.Metric {
230+
labels := make(map[string]string)
231+
for _, labelPair := range metric.Label {
232+
labels[*labelPair.Name] = *labelPair.Value
233+
}
234+
235+
var value float64
236+
if *metricFamily.Type == pcm.MetricType_COUNTER {
237+
value = *metric.Counter.Value
238+
} else if *metricFamily.Type == pcm.MetricType_GAUGE {
239+
value = *metric.Gauge.Value
240+
} else {
241+
return metrics, fmt.Errorf("unexpected MetricType %s for metric %s",
242+
pcm.MetricType_name[int32(*metricFamily.Type)], *metricFamily.Name)
243+
}
244+
245+
metrics = append(metrics, Float64MetricRepresentation{*metricFamily.Name, labels, value})
246+
}
247+
}
248+
249+
return metrics, nil
250+
}
251+
252+
// GetFloat64Metric finds the metric matching provided name and labels.
253+
// When strictLabelMatching is set to true, the founded metric labels are identical to the provided labels;
254+
// when strictLabelMatching is set to false, the founded metric labels are a superset of the provided labels.
255+
func GetFloat64Metric(metrics []Float64MetricRepresentation, name string, labels map[string]string,
256+
strictLabelMatching bool) (Float64MetricRepresentation, error) {
257+
for _, metric := range metrics {
258+
if metric.Name != name {
259+
continue
260+
}
261+
if strictLabelMatching && len(metric.Labels) != len(labels) {
262+
continue
263+
}
264+
sameLabels := true
265+
for key, value := range labels {
266+
if metric.Labels[key] != value {
267+
sameLabels = false
268+
break
269+
}
270+
}
271+
if !sameLabels {
272+
continue
273+
}
274+
return metric, nil
275+
}
276+
return Float64MetricRepresentation{}, fmt.Errorf("no matching metric found")
277+
}

pkg/util/metrics/helpers_test.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package metrics
17+
18+
import (
19+
"io/ioutil"
20+
"testing"
21+
)
22+
23+
// TestPrometheusMetricsParsingAndMatching verifies the behavior of ParsePrometheusMetrics() and GetFloat64Metric().
24+
func TestPrometheusMetricsParsingAndMatching(t *testing.T) {
25+
testCases := []struct {
26+
name string
27+
metricsTextPath string
28+
expectedMetrics []Float64MetricRepresentation
29+
notExpectedMetrics []Float64MetricRepresentation
30+
strictLabelMatching bool
31+
}{
32+
{
33+
name: "Relaxed label matching",
34+
metricsTextPath: "testdata/sample_metrics.txt",
35+
expectedMetrics: []Float64MetricRepresentation{
36+
// Metric with no label.
37+
{
38+
Name: "host_uptime",
39+
Labels: map[string]string{},
40+
},
41+
// Metric with partial label.
42+
{
43+
Name: "host_uptime",
44+
Labels: map[string]string{"kernel_version": "4.14.127+"},
45+
},
46+
{
47+
Name: "disk_avg_queue_len",
48+
Labels: map[string]string{"device": "sda1"},
49+
},
50+
{
51+
Name: "disk_avg_queue_len",
52+
Labels: map[string]string{"device": "sda8"},
53+
},
54+
},
55+
notExpectedMetrics: []Float64MetricRepresentation{
56+
// Metric with non-existant label.
57+
{
58+
Name: "host_uptime",
59+
Labels: map[string]string{"non-existant-version": "0.0.1"},
60+
},
61+
// Metric with incorrect label.
62+
{
63+
Name: "host_uptime",
64+
Labels: map[string]string{"kernel_version": "mismatched-version"},
65+
},
66+
// Non-exsistant metric.
67+
{
68+
Name: "host_downtime",
69+
Labels: map[string]string{},
70+
},
71+
},
72+
strictLabelMatching: false,
73+
},
74+
{
75+
name: "Strict label matching",
76+
metricsTextPath: "testdata/sample_metrics.txt",
77+
expectedMetrics: []Float64MetricRepresentation{
78+
{
79+
Name: "host_uptime",
80+
Labels: map[string]string{"kernel_version": "4.14.127+", "os_version": "cos 73-11647.217.0"},
81+
},
82+
{
83+
Name: "problem_counter",
84+
Labels: map[string]string{"reason": "DockerHung"},
85+
},
86+
{
87+
Name: "problem_counter",
88+
Labels: map[string]string{"reason": "OOMKilling"},
89+
},
90+
},
91+
notExpectedMetrics: []Float64MetricRepresentation{
92+
// Metric with incomplete label.
93+
{
94+
Name: "host_uptime",
95+
Labels: map[string]string{"kernel_version": "4.14.127+"},
96+
},
97+
// Metric with missing label.
98+
{
99+
Name: "host_uptime",
100+
Labels: map[string]string{},
101+
},
102+
// Metric with non-existant label.
103+
{
104+
Name: "host_uptime",
105+
Labels: map[string]string{"non-existant-version": "0.0.1"},
106+
},
107+
// Metric with incorrect label.
108+
{
109+
Name: "host_uptime",
110+
Labels: map[string]string{"kernel_version": "mismatched-version"},
111+
},
112+
// Non-exsistant metric.
113+
{
114+
Name: "host_downtime",
115+
Labels: map[string]string{},
116+
},
117+
},
118+
strictLabelMatching: true,
119+
},
120+
}
121+
122+
for _, test := range testCases {
123+
t.Run(test.name, func(t *testing.T) {
124+
b, err := ioutil.ReadFile(test.metricsTextPath)
125+
if err != nil {
126+
t.Errorf("Unexpected error reading file %s: %v", test.metricsTextPath, err)
127+
}
128+
metricsText := string(b)
129+
130+
metrics, err := ParsePrometheusMetrics(metricsText)
131+
if err != nil {
132+
t.Errorf("Unexpected error parsing NPD metrics: %v\nMetrics text: %s\n", err, metricsText)
133+
}
134+
135+
for _, expectedMetric := range test.expectedMetrics {
136+
_, err = GetFloat64Metric(metrics, expectedMetric.Name, expectedMetric.Labels, test.strictLabelMatching)
137+
if err != nil {
138+
t.Errorf("Failed to find metric %v in these metrics %v.\nMetrics text: %s\n",
139+
expectedMetric, metrics, metricsText)
140+
}
141+
}
142+
143+
for _, notExpectedMetric := range test.notExpectedMetrics {
144+
_, err = GetFloat64Metric(metrics, notExpectedMetric.Name, notExpectedMetric.Labels, test.strictLabelMatching)
145+
if err == nil {
146+
t.Errorf("Unexpected metric %v found in these metrics %v.\nMetrics text: %s\n",
147+
notExpectedMetric, metrics, metricsText)
148+
}
149+
}
150+
})
151+
}
152+
}

0 commit comments

Comments
 (0)