Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,9 @@ simplify-dashboards:
run-perf-test:
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}

run-e2e-test:
go test -v ./test/e2e/ -timeout 1h -tags=e2e -count=1 -args -image-tag=${TAG} -image-registry=${IMAGE_REGISTRY} -image-namespace=${IMAGE_NAMESPACE}

.PHONY: update-hubble
update-hubble:
@echo "Checking for Hubble updates..."
Expand Down
4 changes: 3 additions & 1 deletion test/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
)

const (
RetinaPort int = 10093
// netObsRGtag is used to tag resources created by this test suite
NetObsRGtag = "-e2e-netobs-"
KubeSystemNamespace = "kube-system"
Expand Down Expand Up @@ -48,6 +47,9 @@ var (
RetinaChartPath = func(rootDir string) string {
return filepath.Join(rootDir, "deploy", "standard", "manifests", "controller", "helm", "retina")
}
HubbleChartPath = func(rootDir string) string {
return filepath.Join(rootDir, "deploy", "hubble", "manifests", "controller", "helm", "retina")
}
RetinaAdvancedProfilePath = func(rootDir string) string {
return filepath.Join(rootDir, "test", "profiles", "advanced", "values.yaml")
}
Expand Down
52 changes: 52 additions & 0 deletions test/e2e/common/validate-metric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//nolint:revive // package name "common" is used across the E2E test suite
package common

import (
"errors"
"fmt"
"log"

prom "github.com/microsoft/retina/test/e2e/framework/prometheus"
)

var ErrMetricFound = errors.New("unexpected metric found")

type ValidateMetric struct {
ForwardedPort string
MetricName string
ValidMetrics []map[string]string
ExpectMetric bool
PartialMatch bool // If true, only the specified labels need to match (metric can have additional labels)
}

func (v *ValidateMetric) Run() error {
promAddress := fmt.Sprintf("http://localhost:%s/metrics", v.ForwardedPort)

for _, validMetric := range v.ValidMetrics {
err := prom.CheckMetric(promAddress, v.MetricName, validMetric, v.PartialMatch)
if err != nil {
// If we expect the metric not to be found, return nil if it's not found.
if !v.ExpectMetric && errors.Is(err, prom.ErrNoMetricFound) {
log.Printf("metric %s not found, as expected\n", v.MetricName)
return nil
}
return fmt.Errorf("failed to verify prometheus metrics: %w", err)
}

// if we expect the metric not to be found, return an error if it is found
if !v.ExpectMetric {
return fmt.Errorf("did not expect to find metric %s matching %+v: %w", v.MetricName, validMetric, ErrMetricFound)
}

log.Printf("found metric %s matching %+v\n", v.MetricName, validMetric)
}
return nil
}

func (v *ValidateMetric) Prevalidate() error {
return nil
}

func (v *ValidateMetric) Stop() error {
return nil
}
15 changes: 15 additions & 0 deletions test/e2e/framework/constants/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package constants

const (
MetricsEndpoint = "metrics"

TCP = "TCP"
UDP = "UDP"
IPV4 = "IPv4"
IPTableRuleDrop = "IPTABLE_RULE_DROP"
SYN = "SYN"
SYNACK = "SYN-ACK"
ACK = "ACK"
FIN = "FIN"
RST = "RST"
)
31 changes: 31 additions & 0 deletions test/e2e/framework/constants/hubble.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package constants

const (
// Metrics Port
HubbleMetricsPort = "9965"

// MetricsName
HubbleDNSQueryMetricName = "hubble_dns_queries_total"
HubbleDNSResponseMetricName = "hubble_dns_responses_total"
HubbleFlowMetricName = "hubble_flows_processed_total"
HubbleDropMetricName = "hubble_drop_total"
HubbleTCPFlagsMetricName = "hubble_tcp_flags_total"

// Labels
HubbleDestinationLabel = "destination"
HubbleSourceLabel = "source"
HubbleIPsRetunedLabel = "ips_returned"
HubbleQTypesLabel = "qtypes"
HubbleRCodeLabel = "rcode"
HubbleQueryLabel = "query"

HubbleProtocolLabel = "protocol"
HubbleReasonLabel = "reason"

HubbleSubtypeLabel = "subtype"
HubbleTypeLabel = "type"
HubbleVerdictLabel = "verdict"

HubbleFamilyLabel = "family"
HubbleFlagLabel = "flag"
)
17 changes: 17 additions & 0 deletions test/e2e/framework/constants/retina.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package constants

const (
// Metrics Port
RetinaMetricsPort = "10093"

// MetricsName
RetinaDropMetricName = "networkobservability_drop_count"
RetinaForwardMetricName = "networkobservability_forward_count"

// Labels
RetinaSourceLabel = "source"
RetinaDestinationLabel = "destination"
RetinaProtocolLabel = "protocol"
RetinaReasonLabel = "reason"
RetinaDirectionLabel = "direction"
)
17 changes: 13 additions & 4 deletions test/e2e/framework/kubernetes/create-agnhost-statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ var ErrLabelMissingFromPod = fmt.Errorf("label missing from pod")

const (
AgnhostHTTPPort = 80
AgnhostReplicas = 1
AgnhostArchAmd64 = "amd64"
AgnhostArchArm64 = "arm64"
)
Expand All @@ -29,6 +28,7 @@ type CreateAgnhostStatefulSet struct {
ScheduleOnSameNode bool
KubeConfigFilePath string
AgnhostArch string
AgnhostReplicas *int
}

func (c *CreateAgnhostStatefulSet) Run() error {
Expand All @@ -50,7 +50,13 @@ func (c *CreateAgnhostStatefulSet) Run() error {
c.AgnhostArch = AgnhostArchAmd64
}

agnhostStatefulSet := c.getAgnhostDeployment(c.AgnhostArch)
// set default replicas to 1
replicas := 1
if c.AgnhostReplicas != nil {
replicas = *c.AgnhostReplicas
}

agnhostStatefulSet := c.getAgnhostDeployment(c.AgnhostArch, replicas)

err = CreateResource(ctx, agnhostStatefulSet, clientset)
if err != nil {
Expand Down Expand Up @@ -79,8 +85,11 @@ func (c *CreateAgnhostStatefulSet) Stop() error {
return nil
}

func (c *CreateAgnhostStatefulSet) getAgnhostDeployment(arch string) *appsv1.StatefulSet {
reps := int32(AgnhostReplicas)
func (c *CreateAgnhostStatefulSet) getAgnhostDeployment(arch string, replicas int) *appsv1.StatefulSet {
if replicas < 1 {
replicas = 1
}
reps := int32(replicas) //nolint:gosec // replicas controlled by test code

var affinity *v1.Affinity
if c.ScheduleOnSameNode {
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/framework/kubernetes/create-network-policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func (c *CreateDenyAllNetworkPolicy) Run() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

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

agnhostStatefulSet := getNetworkPolicy(d.NetworkPolicyNamespace, d.DenyAllLabelSelector)
err = DeleteResource(ctx, agnhostStatefulSet, clientset)
networkPolicy := getNetworkPolicy(d.NetworkPolicyNamespace, d.DenyAllLabelSelector)
err = DeleteResource(ctx, networkPolicy, clientset)
if err != nil {
return fmt.Errorf("error creating simple deny-all network policy: %w", err)
}
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/framework/kubernetes/install-hubble-helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ const (
HubbleRelayApp = "hubble-relay"
)

type ValidateHubbleStep struct {
type InstallHubbleHelmChart struct {
Namespace string
ReleaseName string
KubeConfigFilePath string
ChartPath string
TagEnv string
}

func (v *ValidateHubbleStep) Run() error {
func (v *InstallHubbleHelmChart) Run() error {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeoutSeconds*time.Second)
defer cancel()

Expand Down Expand Up @@ -146,10 +146,10 @@ func (v *ValidateHubbleStep) Run() error {
return nil
}

func (v *ValidateHubbleStep) Prevalidate() error {
func (v *InstallHubbleHelmChart) Prevalidate() error {
return nil
}

func (v *ValidateHubbleStep) Stop() error {
func (v *InstallHubbleHelmChart) Stop() error {
return nil
}
48 changes: 46 additions & 2 deletions test/e2e/framework/prometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ var (
defaultRetryAttempts = 60
)

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

ctx := context.Background()
pctx, cancel := context.WithCancel(ctx)
defer cancel()

// Default partial to false if not provided
usePartial := len(partial) > 0 && partial[0]

metrics := map[string]*promclient.MetricFamily{}
scrapeMetricsFn := func() error {
log.Printf("checking for metrics on %s", promAddress)
Expand All @@ -42,7 +45,11 @@ func CheckMetric(promAddress, metricName string, validMetric map[string]string)

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

// verifyValidMetricPresentPartial checks if a metric exists with labels that contain
// all the key-value pairs in validMetric (partial matching - the metric can have additional labels)
func verifyValidMetricPresentPartial(metricName string, data map[string]*promclient.MetricFamily, validMetric map[string]string) error {
for _, metric := range data {
if metric.GetName() == metricName {
for _, metric := range metric.GetMetric() {

// get all labels and values on the metric
metricLabels := map[string]string{}
for _, label := range metric.GetLabel() {
metricLabels[label.GetName()] = label.GetValue()
}

// if valid metric is empty, then we just need to make sure the metric and value is present
if len(validMetric) == 0 && len(metricLabels) > 0 {
return nil
}

// Check if all key-value pairs in validMetric exist in metricLabels
allMatch := true
for key, value := range validMetric {
if metricLabels[key] != value {
allMatch = false
break
}
}

if allMatch {
return nil
}
}
}
}

return fmt.Errorf("failed to find metric matching: %+v: %w", validMetric, ErrNoMetricFound)
}

func getAllPrometheusMetricsFromBuffer(buf []byte) (map[string]*promclient.MetricFamily, error) {
var parser expfmt.TextParser
reader := strings.NewReader(string(buf))
Expand Down
30 changes: 25 additions & 5 deletions test/e2e/jobs/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import (
"github.com/microsoft/retina/test/e2e/framework/generic"
"github.com/microsoft/retina/test/e2e/framework/kubernetes"
"github.com/microsoft/retina/test/e2e/framework/types"
"github.com/microsoft/retina/test/e2e/hubble"
"github.com/microsoft/retina/test/e2e/scenarios/capture"
"github.com/microsoft/retina/test/e2e/scenarios/dns"
"github.com/microsoft/retina/test/e2e/scenarios/drop"
hubble_dns "github.com/microsoft/retina/test/e2e/scenarios/hubble/dns"
hubble_drop "github.com/microsoft/retina/test/e2e/scenarios/hubble/drop"
hubble_flow "github.com/microsoft/retina/test/e2e/scenarios/hubble/flow"
hubble_service "github.com/microsoft/retina/test/e2e/scenarios/hubble/service"
hubble_tcp "github.com/microsoft/retina/test/e2e/scenarios/hubble/tcp"
"github.com/microsoft/retina/test/e2e/scenarios/latency"
tcp "github.com/microsoft/retina/test/e2e/scenarios/tcp"
"github.com/microsoft/retina/test/e2e/scenarios/windows"
Expand Down Expand Up @@ -273,20 +277,36 @@ func UpgradeAndTestRetinaAdvancedMetrics(kubeConfigFilePath, chartPath, valuesFi
return job
}

func ValidateHubble(kubeConfigFilePath, chartPath string, testPodNamespace string) *types.Job {
func InstallAndTestHubbleMetrics(kubeConfigFilePath, chartPath string) *types.Job {
job := types.NewJob("Validate Hubble")

job.AddStep(&kubernetes.ValidateHubbleStep{
job.AddStep(&kubernetes.InstallHubbleHelmChart{
Namespace: common.KubeSystemNamespace,
ReleaseName: "retina",
KubeConfigFilePath: kubeConfigFilePath,
ChartPath: chartPath,
TagEnv: generic.DefaultTagEnv,
}, nil)

job.AddScenario(hubble.ValidateHubbleRelayService())
hubbleScenarios := []*types.Scenario{
hubble_service.ValidateHubbleRelayService(),
hubble_service.ValidateHubbleUIService(kubeConfigFilePath),
}

job.AddScenario(hubble.ValidateHubbleUIService(kubeConfigFilePath))
for _, arch := range common.Architectures {
hubbleScenarios = append(hubbleScenarios,
hubble_dns.ValidateDNSMetric(arch),
hubble_flow.ValidatePodToPodIntraNodeHubbleFlowMetric(arch),
hubble_flow.ValidatePodToPodInterNodeHubbleFlowMetric(arch),
hubble_flow.ValidatePodToWorldHubbleFlowMetric(arch),
hubble_drop.ValidateDropMetric(arch),
hubble_tcp.ValidateTCPMetric(arch),
)
}

for _, scenario := range hubbleScenarios {
job.AddScenario(scenario)
}

job.AddStep(&kubernetes.EnsureStableComponent{
PodNamespace: common.KubeSystemNamespace,
Expand Down
Loading
Loading