Skip to content

Commit 41cf719

Browse files
committed
Add e2e tests for logs, metrics, traces
1 parent 993ae74 commit 41cf719

File tree

4 files changed

+483
-0
lines changed

4 files changed

+483
-0
lines changed

tests/consts.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package tests
2+
3+
const (
4+
BaseLogzioApiUrl = "https://api.logz.io/v1"
5+
LogsQuery = `{
6+
"query": {
7+
"query_string": {
8+
"query": "env_id:multi-env-test AND type:agent-k8s"
9+
}
10+
},
11+
"from": 0,
12+
"size": 100,
13+
"sort": [
14+
{
15+
"@timestamp": {
16+
"order": "desc"
17+
}
18+
}
19+
],
20+
"_source": true,
21+
"docvalue_fields": [
22+
"@timestamp"
23+
],
24+
"version": true,
25+
"stored_fields": [
26+
"*"
27+
],
28+
"highlight": {},
29+
"aggregations": {
30+
"byType": {
31+
"terms": {
32+
"field": "type",
33+
"size": 5
34+
}
35+
}
36+
}
37+
}`
38+
39+
TracesQuery = `{
40+
"query": {
41+
"query_string": {
42+
"query": "JaegerTag.env_id:multi-env-test AND type:jaegerSpan"
43+
}
44+
},
45+
"from": 0,
46+
"size": 100,
47+
"sort": [
48+
{
49+
"@timestamp": {
50+
"order": "desc"
51+
}
52+
}
53+
],
54+
"_source": true,
55+
"docvalue_fields": [
56+
"@timestamp"
57+
],
58+
"version": true,
59+
"stored_fields": [
60+
"*"
61+
],
62+
"highlight": {},
63+
"aggregations": {
64+
"byType": {
65+
"terms": {
66+
"field": "type",
67+
"size": 5
68+
}
69+
}
70+
}
71+
}`
72+
)

tests/logs_e2e_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package tests
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"os"
10+
"testing"
11+
)
12+
13+
// LogResponse represents the structure of the logs API response
14+
type LogResponse struct {
15+
Hits struct {
16+
Total int `json:"total"`
17+
Hits []struct {
18+
Source struct {
19+
Kubernetes struct {
20+
ContainerImageID string `json:"container_image_id"`
21+
ContainerName string `json:"container_name"`
22+
ContainerImage string `json:"container_image"`
23+
NamespaceName string `json:"namespace_name"`
24+
PodName string `json:"pod_name"`
25+
PodID string `json:"pod_id"`
26+
Host string `json:"host"`
27+
} `json:"kubernetes"`
28+
} `json:"_source"`
29+
} `json:"hits"`
30+
} `json:"hits"`
31+
}
32+
33+
func TestLogzioMonitoringLogs(t *testing.T) {
34+
logsApiKey := os.Getenv("LOGZIO_LOGS_API_KEY")
35+
if logsApiKey == "" {
36+
t.Fatalf("LOGZIO_LOGS_API_KEY environment variable not set")
37+
}
38+
39+
logResponse, err := fetchLogs(logsApiKey)
40+
if err != nil {
41+
t.Fatalf("Failed to fetch logs: %v", err)
42+
}
43+
44+
if logResponse.Hits.Total == 0 {
45+
t.Errorf("No logs found")
46+
}
47+
// Verify required fields
48+
requiredFields := []string{"container_image_id", "container_name", "container_image", "namespace_name", "pod_name", "pod_id", "host"}
49+
missingFields := verifyLogs(logResponse, requiredFields)
50+
if len(missingFields) > 0 {
51+
t.Errorf("Missing log fields: %v", missingFields)
52+
}
53+
}
54+
55+
// fetchLogs fetches the logs from the logz.io API
56+
func fetchLogs(logsApiKey string) (*LogResponse, error) {
57+
url := fmt.Sprintf("%s/search", BaseLogzioApiUrl)
58+
client := &http.Client{}
59+
req, err := http.NewRequest("POST", url, bytes.NewBufferString(LogsQuery))
60+
if err != nil {
61+
return nil, err
62+
}
63+
req.Header.Set("Accept", "application/json")
64+
req.Header.Set("Content-Type", "application/json")
65+
req.Header.Set("X-API-TOKEN", logsApiKey)
66+
67+
resp, err := client.Do(req)
68+
if err != nil {
69+
return nil, err
70+
}
71+
defer resp.Body.Close()
72+
73+
if resp.StatusCode != http.StatusOK {
74+
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
75+
}
76+
77+
body, err := io.ReadAll(resp.Body)
78+
if err != nil {
79+
return nil, err
80+
}
81+
82+
var logResponse LogResponse
83+
err = json.Unmarshal(body, &logResponse)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
return &logResponse, nil
89+
}
90+
91+
// verifyLogs checks if the logs contain the required Kubernetes fields
92+
func verifyLogs(logResponse *LogResponse, requiredFields []string) []string {
93+
missingFieldsMap := make(map[string]bool, len(requiredFields))
94+
for _, field := range requiredFields {
95+
missingFieldsMap[field] = false
96+
}
97+
98+
for _, hit := range logResponse.Hits.Hits {
99+
kubernetes := hit.Source.Kubernetes
100+
if kubernetes.ContainerImageID == "" {
101+
missingFieldsMap["container_image_id"] = true
102+
break
103+
}
104+
if kubernetes.ContainerName == "" {
105+
missingFieldsMap["container_name"] = true
106+
break
107+
}
108+
if kubernetes.ContainerImage == "" {
109+
missingFieldsMap["container_image"] = true
110+
break
111+
}
112+
if kubernetes.NamespaceName == "" {
113+
missingFieldsMap["namespace_name"] = true
114+
break
115+
}
116+
if kubernetes.PodName == "" {
117+
missingFieldsMap["pod_name"] = true
118+
break
119+
}
120+
if kubernetes.PodID == "" {
121+
missingFieldsMap["pod_id"] = true
122+
break
123+
}
124+
if kubernetes.Host == "" {
125+
missingFieldsMap["host"] = true
126+
break
127+
}
128+
}
129+
130+
var missingFields []string
131+
for field, value := range missingFieldsMap {
132+
if value == true {
133+
missingFields = append(missingFields, field)
134+
}
135+
}
136+
137+
return missingFields
138+
}

tests/metrics_e2e_test.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package tests
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"strings"
10+
"testing"
11+
)
12+
13+
// MetricResponse represents the structure of the API response
14+
type MetricResponse struct {
15+
Status string `json:"status"`
16+
Data struct {
17+
ResultType string `json:"resultType"`
18+
Result []struct {
19+
Metric map[string]string `json:"metric"`
20+
Value []interface{} `json:"value"`
21+
} `json:"result"`
22+
} `json:"data"`
23+
}
24+
25+
func TestLogzioMonitoringMetrics(t *testing.T) {
26+
metricsApiKey := os.Getenv("LOGZIO_METRICS_API_KEY")
27+
if metricsApiKey == "" {
28+
t.Fatalf("LOGZIO_METRICS_API_KEY environment variable not set")
29+
}
30+
31+
metricResponse, err := fetchMetrics(metricsApiKey)
32+
if err != nil {
33+
t.Fatalf("Failed to fetch metrics: %v", err)
34+
}
35+
36+
requiredMetrics := map[string][]string{
37+
"kube_pod_status_phase": {"p8s_logzio_name", "namespace", "pod", "phase", "uid"},
38+
"kube_pod_info": {"p8s_logzio_name", "namespace", "host_ip", "node", "pod"},
39+
"container_cpu_usage_seconds_total": {"p8s_logzio_name", "namespace", "pod", "region", "topology_kubernetes_io_region", "container"},
40+
"kube_pod_container_resource_limits": {"p8s_logzio_name", "namespace", "pod", "resource"},
41+
"container_memory_working_set_bytes": {"p8s_logzio_name", "namespace", "pod", "container"},
42+
"kube_pod_container_info": {"p8s_logzio_name", "namespace", "pod"},
43+
"container_network_transmit_bytes_total": {"p8s_logzio_name", "namespace", "pod"},
44+
"container_network_receive_bytes_total": {"p8s_logzio_name", "namespace", "pod"},
45+
"kube_pod_created": {"p8s_logzio_name", "namespace", "pod"},
46+
"kube_pod_owner": {"p8s_logzio_name", "namespace", "pod", "owner_kind", "owner_name"},
47+
"kube_pod_container_status_restarts_total": {"p8s_logzio_name", "namespace", "pod"},
48+
"kube_pod_status_reason": {"p8s_logzio_name", "namespace", "pod", "reason"},
49+
"kube_pod_container_status_waiting_reason": {"p8s_logzio_name", "namespace", "pod", "reason"},
50+
"node_cpu_seconds_total": {"p8s_logzio_name", "instance", "kubernetes_node"},
51+
"kube_node_status_allocatable": {"p8s_logzio_name", "node", "resource"},
52+
"node_memory_MemAvailable_bytes": {"p8s_logzio_name", "instance", "kubernetes_node"},
53+
"node_memory_MemTotal_bytes": {"p8s_logzio_name", "instance", "kubernetes_node"},
54+
"kube_node_role": {"p8s_logzio_name", "status", "role", "node"},
55+
"kube_node_status_condition": {"p8s_logzio_name", "status", "role", "node"},
56+
"kube_node_created": {"p8s_logzio_name", "node"},
57+
"node_filesystem_avail_bytes": {"p8s_logzio_name", "instance", "kubernetes_node"},
58+
"node_filesystem_size_bytes": {"p8s_logzio_name", "instance", "kubernetes_node"},
59+
"kube_replicaset_owner": {"p8s_logzio_name", "namespace", "owner_kind", "owner_name", "replicaset"},
60+
"kube_deployment_created": {"p8s_logzio_name", "namespace", "deployment"},
61+
"kube_deployment_status_condition": {"p8s_logzio_name", "namespace", "deployment", "status"},
62+
"calls_total": {"k8s_node_name", "k8s_namespace_name", "k8s_pod_name", "span_kind", "operation"},
63+
"latency_sum": {"k8s_node_name", "k8s_namespace_name", "k8s_pod_name", "span_kind", "operation"},
64+
"latency_count": {"k8s_node_name", "k8s_namespace_name", "k8s_pod_name", "span_kind", "operation"},
65+
"latency_bucket": {"k8s_node_name", "k8s_namespace_name", "k8s_pod_name", "span_kind", "operation"},
66+
}
67+
68+
if metricResponse.Status != "success" {
69+
t.Errorf("No metrics found")
70+
}
71+
// Verify required metrics
72+
missingMetrics := verifyMetrics(metricResponse, requiredMetrics)
73+
if len(missingMetrics) > 0 {
74+
var sb strings.Builder
75+
for _, metric := range missingMetrics {
76+
sb.WriteString(metric + "\n")
77+
}
78+
t.Errorf("Missing metrics or labels:\n%s", sb.String())
79+
}
80+
}
81+
82+
// fetchMetrics fetches the metrics from the logz.io API
83+
func fetchMetrics(metricsApiKey string) (*MetricResponse, error) {
84+
url := fmt.Sprintf("%s/metrics/prometheus/api/v1/query?query={env_id='multi-env-test'}", BaseLogzioApiUrl)
85+
client := &http.Client{}
86+
req, err := http.NewRequest("GET", url, nil)
87+
if err != nil {
88+
return nil, err
89+
}
90+
req.Header.Set("Accept", "application/json")
91+
req.Header.Set("X-API-TOKEN", metricsApiKey)
92+
93+
resp, err := client.Do(req)
94+
if err != nil {
95+
return nil, err
96+
}
97+
defer resp.Body.Close()
98+
99+
if resp.StatusCode != http.StatusOK {
100+
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
101+
}
102+
103+
body, err := io.ReadAll(resp.Body)
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
var metricResponse MetricResponse
109+
err = json.Unmarshal(body, &metricResponse)
110+
if err != nil {
111+
return nil, err
112+
}
113+
114+
return &metricResponse, nil
115+
}
116+
117+
// verifyMetrics checks if the required metrics and their labels are present in the response
118+
func verifyMetrics(metricResponse *MetricResponse, requiredMetrics map[string][]string) []string {
119+
missingMetrics := []string{}
120+
121+
for metricName, requiredLabels := range requiredMetrics {
122+
found := false
123+
for _, result := range metricResponse.Data.Result {
124+
if result.Metric["__name__"] == metricName {
125+
found = true
126+
for _, label := range requiredLabels {
127+
if _, exists := result.Metric[label]; !exists {
128+
missingMetrics = append(missingMetrics, fmt.Sprintf("%s (missing label: %s)", metricName, label))
129+
}
130+
}
131+
}
132+
}
133+
if !found {
134+
missingMetrics = append(missingMetrics, metricName+" (not found)")
135+
}
136+
}
137+
return deduplicate(missingMetrics)
138+
}
139+
140+
// deduplicate removes duplicate strings from the input array.
141+
func deduplicate(data []string) []string {
142+
uniqueMap := make(map[string]bool)
143+
var uniqueList []string
144+
145+
for _, item := range data {
146+
trimmedItem := strings.TrimSpace(item)
147+
if _, exists := uniqueMap[trimmedItem]; !exists {
148+
uniqueMap[trimmedItem] = true
149+
uniqueList = append(uniqueList, trimmedItem)
150+
}
151+
}
152+
153+
return uniqueList
154+
}

0 commit comments

Comments
 (0)