Skip to content
Open
Show file tree
Hide file tree
Changes from all 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