Skip to content

Commit af712f0

Browse files
authored
feat(hubble-test): Add Hubble E2E test scenarios (#1962)
# Description Adding multi-arch Hubble CP test scenarios. ## Related Issue #1056 If this pull request is related to any issue, please mention it here. Additionally, make sure that the issue is assigned to you before submitting this pull request. ## Checklist - [x] I have read the [contributing documentation](https://retina.sh/docs/Contributing/overview). - [x] I signed and signed-off the commits (`git commit -S -s ...`). See [this documentation](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) on signing commits. - [x] I have correctly attributed the author(s) of the code. - [x] I have tested the changes locally. - [x] I have followed the project's style guidelines. - [x] I have updated the documentation, if necessary. - [x] I have added tests, if applicable. ## Screenshots (if applicable) or Testing Completed All Tests pass locally. On ran on AMD64 node. Excluded: job.AddScenario(windows.ValidateWindowsBasicMetric()) <img width="1083" height="825" alt="image" src="https://github.com/user-attachments/assets/a21eee9b-6486-461f-96f0-ace60b9fc488" /> ## Additional Notes Add any additional notes or context about the pull request here. --- Please refer to the [CONTRIBUTING.md](../CONTRIBUTING.md) file for more information on how to contribute to this project. --------- Signed-off-by: mereta <mereta.degutyte@hotmail.co.uk>
1 parent 9c93fec commit af712f0

28 files changed

Lines changed: 1111 additions & 45 deletions

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,9 @@ simplify-dashboards:
601601
run-perf-test:
602602
go test -v ./test/e2e/retina_perf_test.go -timeout 2h -tags=perf -count=1 -args -image-tag=${TAG} -image-registry=${IMAGE_REGISTRY} -image-namespace=${IMAGE_NAMESPACE}
603603

604+
run-e2e-test:
605+
go test -v ./test/e2e/ -timeout 1h -tags=e2e -count=1 -args -image-tag=${TAG} -image-registry=${IMAGE_REGISTRY} -image-namespace=${IMAGE_NAMESPACE}
606+
604607
.PHONY: update-hubble
605608
update-hubble:
606609
@echo "Checking for Hubble updates..."

test/e2e/common/common.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
)
1818

1919
const (
20-
RetinaPort int = 10093
2120
// netObsRGtag is used to tag resources created by this test suite
2221
NetObsRGtag = "-e2e-netobs-"
2322
KubeSystemNamespace = "kube-system"
@@ -48,6 +47,9 @@ var (
4847
RetinaChartPath = func(rootDir string) string {
4948
return filepath.Join(rootDir, "deploy", "standard", "manifests", "controller", "helm", "retina")
5049
}
50+
HubbleChartPath = func(rootDir string) string {
51+
return filepath.Join(rootDir, "deploy", "hubble", "manifests", "controller", "helm", "retina")
52+
}
5153
RetinaAdvancedProfilePath = func(rootDir string) string {
5254
return filepath.Join(rootDir, "test", "profiles", "advanced", "values.yaml")
5355
}

test/e2e/common/validate-metric.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//nolint:revive // package name "common" is used across the E2E test suite
2+
package common
3+
4+
import (
5+
"errors"
6+
"fmt"
7+
"log"
8+
9+
prom "github.com/microsoft/retina/test/e2e/framework/prometheus"
10+
)
11+
12+
var ErrMetricFound = errors.New("unexpected metric found")
13+
14+
type ValidateMetric struct {
15+
ForwardedPort string
16+
MetricName string
17+
ValidMetrics []map[string]string
18+
ExpectMetric bool
19+
PartialMatch bool // If true, only the specified labels need to match (metric can have additional labels)
20+
}
21+
22+
func (v *ValidateMetric) Run() error {
23+
promAddress := fmt.Sprintf("http://localhost:%s/metrics", v.ForwardedPort)
24+
25+
for _, validMetric := range v.ValidMetrics {
26+
err := prom.CheckMetric(promAddress, v.MetricName, validMetric, v.PartialMatch)
27+
if err != nil {
28+
// If we expect the metric not to be found, return nil if it's not found.
29+
if !v.ExpectMetric && errors.Is(err, prom.ErrNoMetricFound) {
30+
log.Printf("metric %s not found, as expected\n", v.MetricName)
31+
return nil
32+
}
33+
return fmt.Errorf("failed to verify prometheus metrics: %w", err)
34+
}
35+
36+
// if we expect the metric not to be found, return an error if it is found
37+
if !v.ExpectMetric {
38+
return fmt.Errorf("did not expect to find metric %s matching %+v: %w", v.MetricName, validMetric, ErrMetricFound)
39+
}
40+
41+
log.Printf("found metric %s matching %+v\n", v.MetricName, validMetric)
42+
}
43+
return nil
44+
}
45+
46+
func (v *ValidateMetric) Prevalidate() error {
47+
return nil
48+
}
49+
50+
func (v *ValidateMetric) Stop() error {
51+
return nil
52+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package constants
2+
3+
const (
4+
MetricsEndpoint = "metrics"
5+
6+
TCP = "TCP"
7+
UDP = "UDP"
8+
IPV4 = "IPv4"
9+
IPTableRuleDrop = "IPTABLE_RULE_DROP"
10+
SYN = "SYN"
11+
SYNACK = "SYN-ACK"
12+
ACK = "ACK"
13+
FIN = "FIN"
14+
RST = "RST"
15+
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package constants
2+
3+
const (
4+
// Metrics Port
5+
HubbleMetricsPort = "9965"
6+
7+
// MetricsName
8+
HubbleDNSQueryMetricName = "hubble_dns_queries_total"
9+
HubbleDNSResponseMetricName = "hubble_dns_responses_total"
10+
HubbleFlowMetricName = "hubble_flows_processed_total"
11+
HubbleDropMetricName = "hubble_drop_total"
12+
HubbleTCPFlagsMetricName = "hubble_tcp_flags_total"
13+
14+
// Labels
15+
HubbleDestinationLabel = "destination"
16+
HubbleSourceLabel = "source"
17+
HubbleIPsRetunedLabel = "ips_returned"
18+
HubbleQTypesLabel = "qtypes"
19+
HubbleRCodeLabel = "rcode"
20+
HubbleQueryLabel = "query"
21+
22+
HubbleProtocolLabel = "protocol"
23+
HubbleReasonLabel = "reason"
24+
25+
HubbleSubtypeLabel = "subtype"
26+
HubbleTypeLabel = "type"
27+
HubbleVerdictLabel = "verdict"
28+
29+
HubbleFamilyLabel = "family"
30+
HubbleFlagLabel = "flag"
31+
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package constants
2+
3+
const (
4+
// Metrics Port
5+
RetinaMetricsPort = "10093"
6+
7+
// MetricsName
8+
RetinaDropMetricName = "networkobservability_drop_count"
9+
RetinaForwardMetricName = "networkobservability_forward_count"
10+
11+
// Labels
12+
RetinaSourceLabel = "source"
13+
RetinaDestinationLabel = "destination"
14+
RetinaProtocolLabel = "protocol"
15+
RetinaReasonLabel = "reason"
16+
RetinaDirectionLabel = "direction"
17+
)

test/e2e/framework/kubernetes/create-agnhost-statefulset.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ var ErrLabelMissingFromPod = fmt.Errorf("label missing from pod")
1818

1919
const (
2020
AgnhostHTTPPort = 80
21-
AgnhostReplicas = 1
2221
AgnhostArchAmd64 = "amd64"
2322
AgnhostArchArm64 = "arm64"
2423
)
@@ -29,6 +28,7 @@ type CreateAgnhostStatefulSet struct {
2928
ScheduleOnSameNode bool
3029
KubeConfigFilePath string
3130
AgnhostArch string
31+
AgnhostReplicas *int
3232
}
3333

3434
func (c *CreateAgnhostStatefulSet) Run() error {
@@ -50,7 +50,13 @@ func (c *CreateAgnhostStatefulSet) Run() error {
5050
c.AgnhostArch = AgnhostArchAmd64
5151
}
5252

53-
agnhostStatefulSet := c.getAgnhostDeployment(c.AgnhostArch)
53+
// set default replicas to 1
54+
replicas := 1
55+
if c.AgnhostReplicas != nil {
56+
replicas = *c.AgnhostReplicas
57+
}
58+
59+
agnhostStatefulSet := c.getAgnhostDeployment(c.AgnhostArch, replicas)
5460

5561
err = CreateResource(ctx, agnhostStatefulSet, clientset)
5662
if err != nil {
@@ -79,8 +85,11 @@ func (c *CreateAgnhostStatefulSet) Stop() error {
7985
return nil
8086
}
8187

82-
func (c *CreateAgnhostStatefulSet) getAgnhostDeployment(arch string) *appsv1.StatefulSet {
83-
reps := int32(AgnhostReplicas)
88+
func (c *CreateAgnhostStatefulSet) getAgnhostDeployment(arch string, replicas int) *appsv1.StatefulSet {
89+
if replicas < 1 {
90+
replicas = 1
91+
}
92+
reps := int32(replicas) //nolint:gosec // replicas controlled by test code
8493

8594
var affinity *v1.Affinity
8695
if c.ScheduleOnSameNode {

test/e2e/framework/kubernetes/create-network-policy.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func (c *CreateDenyAllNetworkPolicy) Run() error {
3636
ctx, cancel := context.WithCancel(context.Background())
3737
defer cancel()
3838

39-
agnhostStatefulSet := getNetworkPolicy(c.NetworkPolicyNamespace, c.DenyAllLabelSelector)
40-
err = CreateResource(ctx, agnhostStatefulSet, clientset)
39+
networkPolicy := getNetworkPolicy(c.NetworkPolicyNamespace, c.DenyAllLabelSelector)
40+
err = CreateResource(ctx, networkPolicy, clientset)
4141
if err != nil {
4242
return fmt.Errorf("error creating simple deny-all network policy: %w", err)
4343
}
@@ -96,8 +96,8 @@ func (d *DeleteDenyAllNetworkPolicy) Run() error {
9696
ctx, cancel := context.WithCancel(context.Background())
9797
defer cancel()
9898

99-
agnhostStatefulSet := getNetworkPolicy(d.NetworkPolicyNamespace, d.DenyAllLabelSelector)
100-
err = DeleteResource(ctx, agnhostStatefulSet, clientset)
99+
networkPolicy := getNetworkPolicy(d.NetworkPolicyNamespace, d.DenyAllLabelSelector)
100+
err = DeleteResource(ctx, networkPolicy, clientset)
101101
if err != nil {
102102
return fmt.Errorf("error creating simple deny-all network policy: %w", err)
103103
}

test/e2e/framework/kubernetes/install-hubble-helm.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ const (
2323
HubbleRelayApp = "hubble-relay"
2424
)
2525

26-
type ValidateHubbleStep struct {
26+
type InstallHubbleHelmChart struct {
2727
Namespace string
2828
ReleaseName string
2929
KubeConfigFilePath string
3030
ChartPath string
3131
TagEnv string
3232
}
3333

34-
func (v *ValidateHubbleStep) Run() error {
34+
func (v *InstallHubbleHelmChart) Run() error {
3535
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeoutSeconds*time.Second)
3636
defer cancel()
3737

@@ -146,10 +146,10 @@ func (v *ValidateHubbleStep) Run() error {
146146
return nil
147147
}
148148

149-
func (v *ValidateHubbleStep) Prevalidate() error {
149+
func (v *InstallHubbleHelmChart) Prevalidate() error {
150150
return nil
151151
}
152152

153-
func (v *ValidateHubbleStep) Stop() error {
153+
func (v *InstallHubbleHelmChart) Stop() error {
154154
return nil
155155
}

test/e2e/framework/prometheus/prometheus.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ var (
2222
defaultRetryAttempts = 60
2323
)
2424

25-
func CheckMetric(promAddress, metricName string, validMetric map[string]string) error {
25+
func CheckMetric(promAddress, metricName string, validMetric map[string]string, partial ...bool) error {
2626
defaultRetrier := retry.Retrier{Attempts: defaultRetryAttempts, Delay: defaultRetryDelay}
2727

2828
ctx := context.Background()
2929
pctx, cancel := context.WithCancel(ctx)
3030
defer cancel()
3131

32+
// Default partial to false if not provided
33+
usePartial := len(partial) > 0 && partial[0]
34+
3235
metrics := map[string]*promclient.MetricFamily{}
3336
scrapeMetricsFn := func() error {
3437
log.Printf("checking for metrics on %s", promAddress)
@@ -42,7 +45,11 @@ func CheckMetric(promAddress, metricName string, validMetric map[string]string)
4245

4346
// loop through each metric to check for a match,
4447
// if none is found then log and return an error which will trigger a retry
45-
err = verifyValidMetricPresent(metricName, metrics, validMetric)
48+
if usePartial {
49+
err = verifyValidMetricPresentPartial(metricName, metrics, validMetric)
50+
} else {
51+
err = verifyValidMetricPresent(metricName, metrics, validMetric)
52+
}
4653
if err != nil {
4754
log.Printf("failed to find metric matching %s: %+v\n", metricName, validMetric)
4855
return ErrNoMetricFound
@@ -119,6 +126,43 @@ func getAllPrometheusMetricsFromURL(url string) (map[string]*promclient.MetricFa
119126
return metrics, nil
120127
}
121128

129+
// verifyValidMetricPresentPartial checks if a metric exists with labels that contain
130+
// all the key-value pairs in validMetric (partial matching - the metric can have additional labels)
131+
func verifyValidMetricPresentPartial(metricName string, data map[string]*promclient.MetricFamily, validMetric map[string]string) error {
132+
for _, metric := range data {
133+
if metric.GetName() == metricName {
134+
for _, metric := range metric.GetMetric() {
135+
136+
// get all labels and values on the metric
137+
metricLabels := map[string]string{}
138+
for _, label := range metric.GetLabel() {
139+
metricLabels[label.GetName()] = label.GetValue()
140+
}
141+
142+
// if valid metric is empty, then we just need to make sure the metric and value is present
143+
if len(validMetric) == 0 && len(metricLabels) > 0 {
144+
return nil
145+
}
146+
147+
// Check if all key-value pairs in validMetric exist in metricLabels
148+
allMatch := true
149+
for key, value := range validMetric {
150+
if metricLabels[key] != value {
151+
allMatch = false
152+
break
153+
}
154+
}
155+
156+
if allMatch {
157+
return nil
158+
}
159+
}
160+
}
161+
}
162+
163+
return fmt.Errorf("failed to find metric matching: %+v: %w", validMetric, ErrNoMetricFound)
164+
}
165+
122166
func getAllPrometheusMetricsFromBuffer(buf []byte) (map[string]*promclient.MetricFamily, error) {
123167
var parser expfmt.TextParser
124168
reader := strings.NewReader(string(buf))

0 commit comments

Comments
 (0)