prometheus/cmd/promtool/metrics_test.go
Minh Nguyen 9f93c2d2e1
promtool: Add Remote Write 2.0 support to push metrics command (#17417)
* add feature flag for remote write v2

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>

* change from number to protobuf_message

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>

* fix test

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>

* fix name

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>

* run make cli-documentation

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>

* fix help

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>

* run make cli-documentation

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>

---------

Signed-off-by: pipiland2612 <nguyen.t.dang.minh@gmail.com>
2025-10-31 11:38:40 +00:00

163 lines
4.6 KiB
Go

// Copyright 2025 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
remoteapi "github.com/prometheus/client_golang/exp/api/remote"
"github.com/stretchr/testify/require"
)
// setupTestServer creates a test server with a mock storage for remote write testing.
func setupTestServer(t *testing.T, supportedTypes remoteapi.MessageTypes) (*httptest.Server, *mockStorage, *url.URL) {
t.Helper()
store := &mockStorage{}
handler := remoteapi.NewWriteHandler(store, supportedTypes)
server := httptest.NewServer(handler)
t.Cleanup(server.Close)
serverURL, err := url.Parse(server.URL)
require.NoError(t, err)
return server, store, serverURL
}
// createMetricsFile creates a temporary file with the given metrics data.
func createMetricsFile(t *testing.T, metricsData string) string {
t.Helper()
tmpFile := t.TempDir() + "/metrics.txt"
err := os.WriteFile(tmpFile, []byte(metricsData), 0o644)
require.NoError(t, err)
return tmpFile
}
func TestPushMetrics(t *testing.T) {
tests := []struct {
name string
metricsData string
protoMsg string
serverTypes remoteapi.MessageTypes
}{
{
name: "successful push with gauge metrics (V1)",
metricsData: `# HELP test_metric A test metric
# TYPE test_metric gauge
test_metric{label="value1"} 42.0
test_metric{label="value2"} 43.0
`,
protoMsg: "prometheus.WriteRequest",
serverTypes: remoteapi.MessageTypes{remoteapi.WriteV1MessageType},
},
{
name: "successful push with counter metrics (V1)",
metricsData: `# HELP test_counter A test counter
# TYPE test_counter counter
test_counter 100
`,
protoMsg: "prometheus.WriteRequest",
serverTypes: remoteapi.MessageTypes{remoteapi.WriteV1MessageType},
},
{
name: "successful push with gauge metrics (V2)",
metricsData: `# HELP test_metric A test metric
# TYPE test_metric gauge
test_metric{label="value1"} 42.0
test_metric{label="value2"} 43.0
`,
protoMsg: "io.prometheus.write.v2.Request",
serverTypes: remoteapi.MessageTypes{remoteapi.WriteV2MessageType},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, store, serverURL := setupTestServer(t, tc.serverTypes)
tmpFile := createMetricsFile(t, tc.metricsData)
status := PushMetrics(
serverURL,
http.DefaultTransport,
map[string]string{},
30*time.Second,
tc.protoMsg,
map[string]string{"job": "test"},
tmpFile,
)
require.Equal(t, successExitCode, status)
require.True(t, store.called, "Handler should have been called")
require.NoError(t, store.lastErr, "Handler should not have returned an error")
require.NotEmpty(t, store.receivedData, "Request should contain data (compression and decompression successful)")
require.Contains(t, store.receivedContentType, "application/x-protobuf", "Content-Type should be protobuf")
})
}
}
// mockStorage is a simple mock for testing the remote write handler.
type mockStorage struct {
called bool
lastErr error
receivedData []byte
receivedContentType string
}
func (m *mockStorage) Store(req *http.Request, _ remoteapi.WriteMessageType) (*remoteapi.WriteResponse, error) {
m.called = true
// Capture content-type header.
m.receivedContentType = req.Header.Get("Content-Type")
if req.Body != nil {
data, err := io.ReadAll(req.Body)
if err == nil {
m.receivedData = data
}
}
if m.lastErr != nil {
return nil, m.lastErr
}
resp := remoteapi.NewWriteResponse()
resp.SetStatusCode(http.StatusNoContent)
return resp, nil
}
func TestPushMetricsInvalidProtoMsg(t *testing.T) {
_, store, serverURL := setupTestServer(t, remoteapi.MessageTypes{remoteapi.WriteV1MessageType})
tmpFile := createMetricsFile(t, "test_metric 1\n")
status := PushMetrics(
serverURL,
http.DefaultTransport,
map[string]string{},
30*time.Second,
"blablalba", // Invalid protoMsg.
map[string]string{"job": "test"},
tmpFile,
)
require.Equal(t, failureExitCode, status)
require.False(t, store.called, "Handler should not have been called for invalid protoMsg")
}