Skip to content

Commit

Permalink
Adds metrics e2e tests for HelmApps
Browse files Browse the repository at this point in the history
Signed-off-by: Xavi Garcia <xavi.garcia@suse.com>
  • Loading branch information
0xavi0 committed Dec 5, 2024
1 parent acc2d99 commit fc68a6c
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 16 deletions.
14 changes: 14 additions & 0 deletions e2e/assets/helmapp-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: fleet.cattle.io/v1alpha1
kind: HelmApp
metadata:
name: {{ .Name }}
{{- if ne .Shard "" }}
labels:
fleet.cattle.io/shard-ref: {{ .Shard }}
{{- end }}
namespace: {{.Namespace}}
spec:
helm:
chart: {{.Chart}}
version: {{.Version}}
namespace: {{.Namespace}}
161 changes: 161 additions & 0 deletions e2e/metrics/helmapp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package metrics_test

import (
"fmt"
"math/rand"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/rancher/fleet/e2e/metrics"
"github.com/rancher/fleet/e2e/testenv"
"github.com/rancher/fleet/e2e/testenv/kubectl"
"github.com/rancher/fleet/e2e/testenv/zothelper"
)

var _ = Describe("HelmApp Metrics", Label("helmapp"), func() {

var (
// kw is the kubectl command for namespace the workload is deployed to
kw kubectl.Command
namespace string
objName = "metrics"
version = "0.1.0"
)

BeforeEach(func() {
k = env.Kubectl.Namespace(env.Namespace)
namespace = testenv.NewNamespaceName(
objName,
rand.New(rand.NewSource(time.Now().UnixNano())),
)
kw = k.Namespace(namespace)

out, err := k.Create("ns", namespace)
Expect(err).ToNot(HaveOccurred(), out)

err = testenv.CreateHelmApp(
kw,
namespace,
objName,
"oci://ghcr.io/rancher/fleet-test-configmap-chart",
version,
shard,
)
Expect(err).ToNot(HaveOccurred())

DeferCleanup(func() {
out, err = k.Delete("ns", namespace)
Expect(err).ToNot(HaveOccurred(), out)
})
})

When("testing HelmApp metrics", func() {
helmappMetricNames := []string{
"fleet_helmapp_desired_ready_clusters",
"fleet_helmapp_ready_clusters",
"fleet_helmapp_resources_desired_ready",
"fleet_helmapp_resources_missing",
"fleet_helmapp_resources_modified",
"fleet_helmapp_resources_not_ready",
"fleet_helmapp_resources_orphaned",
"fleet_helmapp_resources_ready",
"fleet_helmapp_resources_unknown",
"fleet_helmapp_resources_wait_applied",
}

It("should have exactly one metric of each type for the helmapp", func() {
Eventually(func() error {
metrics, err := etHelmApp.Get()
Expect(err).ToNot(HaveOccurred())
for _, metricName := range helmappMetricNames {
metric, err := etHelmApp.FindOneMetric(
metrics,
metricName,
map[string]string{
"name": objName,
"namespace": namespace,
},
)
if err != nil {
GinkgoWriter.Printf("ERROR Getting metric: %s: %v\n", metricName, err)
return err
}
Expect(metric.Gauge.GetValue()).To(Equal(float64(0)))
}
return nil
}).ShouldNot(HaveOccurred())
})

When("the HelmApp is changed", func() {
It("it should not duplicate metrics", func() {
ociRef, err := zothelper.GetOCIReference(k)
Expect(err).ToNot(HaveOccurred(), ociRef)

chartPath := fmt.Sprintf("%s/sleeper-chart", ociRef)

out, err := kw.Patch(
"helmapp", objName,
"--type=json",
"-p", fmt.Sprintf(`[{"op": "replace", "path": "/spec/helm/chart", "value": %s}]`, chartPath),
)
Expect(err).ToNot(HaveOccurred(), out)
Expect(out).To(ContainSubstring("helmapp.fleet.cattle.io/metrics patched"))

// Wait for it to be changed.
Eventually(func() (string, error) {
return kw.Get("helmapp", objName, "-o", "jsonpath={.spec.helm.chart}")
}).Should(Equal(chartPath))

var metric *metrics.Metric
// Expect still no metrics to be duplicated.
Eventually(func() error {
metrics, err := etHelmApp.Get()
Expect(err).ToNot(HaveOccurred())
for _, metricName := range helmappMetricNames {
metric, err = etHelmApp.FindOneMetric(
metrics,
metricName,
map[string]string{
"name": objName,
"namespace": namespace,
},
)
if err != nil {
return err
}
if metric.LabelValue("chart") != chartPath {
return fmt.Errorf("path for metric %s unchanged", metricName)
}
}
return nil
}).ShouldNot(HaveOccurred())
})

It("should not keep metrics if HelmApp is deleted", Label("helmapp-delete"), func() {
out, err := kw.Delete("helmapp", objName)
Expect(err).ToNot(HaveOccurred(), out)

Eventually(func() error {
metrics, err := etHelmApp.Get()
Expect(err).ToNot(HaveOccurred())
for _, metricName := range helmappMetricNames {
_, err := etHelmApp.FindOneMetric(
metrics,
metricName,
map[string]string{
"name": objName,
"namespace": namespace,
},
)
if err == nil {
return fmt.Errorf("metric %s found", metricName)
}
}
return nil
}).ShouldNot(HaveOccurred())
})
})
})
})
14 changes: 9 additions & 5 deletions e2e/metrics/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ func TestE2E(t *testing.T) {
var (
env *testenv.Env
// k is the kubectl command for the cluster registration namespace
k kubectl.Command
et metrics.ExporterTest
etGitjob metrics.ExporterTest
shard string
k kubectl.Command
et metrics.ExporterTest
etGitjob metrics.ExporterTest
etHelmApp metrics.ExporterTest
shard string
)

type ServiceData struct {
Expand All @@ -42,7 +43,7 @@ type ServiceData struct {
// controller.
// Valid app values are: fleet-controller, gitjob
func setupLoadBalancer(shard string, app string) (metricsURL string) {
Expect(app).To(Or(Equal("fleet-controller"), Equal("gitjob")))
Expect(app).To(Or(Equal("fleet-controller"), Equal("gitjob"), Equal("helmops")))
rs := rand.NewSource(time.Now().UnixNano())
port := rs.Int63()%1000 + 30000
loadBalancerName := testenv.AddRandomSuffix(app, rs)
Expand Down Expand Up @@ -101,5 +102,8 @@ var _ = BeforeSuite(func() {
gitjobMetricsURL := setupLoadBalancer(shard, "gitjob")
etGitjob = metrics.NewExporterTest(gitjobMetricsURL)

helmopsMetricsURL := setupLoadBalancer(shard, "helmops")
etHelmApp = metrics.NewExporterTest(helmopsMetricsURL)

env = testenv.New()
})
14 changes: 5 additions & 9 deletions e2e/single-cluster/helmapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package singlecluster_test
import (
"fmt"
"math/rand"
"net"
"os"
"strings"
"time"

"github.com/rancher/fleet/e2e/testenv"
"github.com/rancher/fleet/e2e/testenv/kubectl"
"github.com/rancher/fleet/e2e/testenv/zothelper"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
Expand Down Expand Up @@ -115,6 +115,9 @@ var _ = Describe("HelmApp resource tests with oci registry", Label("infra-setup"
)
Expect(err).ToNot(HaveOccurred(), out)

ociRef, err := zothelper.GetOCIReference(k)
Expect(err).ToNot(HaveOccurred(), ociRef)

err = testenv.ApplyTemplate(k, testenv.AssetPath("helmapp/helmapp.yaml"), struct {
Name string
Namespace string
Expand All @@ -127,7 +130,7 @@ var _ = Describe("HelmApp resource tests with oci registry", Label("infra-setup"
name,
namespace,
"",
fmt.Sprintf("%s/sleeper-chart", getOCIReference(k)),
fmt.Sprintf("%s/sleeper-chart", ociRef),
helmOpsSecretName,
insecure,
"0.1.0",
Expand Down Expand Up @@ -175,10 +178,3 @@ var _ = Describe("HelmApp resource tests with oci registry", Label("infra-setup"
})
})
})

func getOCIReference(k kubectl.Command) string {
externalIP, err := k.Get("service", "zot-service", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
Expect(err).ToNot(HaveOccurred(), externalIP)
Expect(net.ParseIP(externalIP)).ShouldNot(BeNil())
return fmt.Sprintf("oci://%s:8082", externalIP)
}
30 changes: 30 additions & 0 deletions e2e/testenv/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
)

const gitrepoTemplate = "gitrepo-template.yaml"
const helmappTemplate = "helmapp-template.yaml"
const clusterTemplate = "cluster-template.yaml"
const clustergroupTemplate = "clustergroup-template.yaml"

Expand All @@ -30,6 +31,16 @@ type GitRepoData struct {
Shard string
}

// HelmAppData can be used with the helmapp-template.yaml asset when no custom
// HelmApp properties are required. All fields except Shard are required.
type HelmAppData struct {
Name string
Chart string
Version string
Namespace string
Shard string
}

// CreateGitRepo uses the template to create a gitrepo resource. The namespace
// is the TargetNamespace for the workloads.
func CreateGitRepo(
Expand All @@ -49,6 +60,25 @@ func CreateGitRepo(
})
}

// CreateHelmApp uses the template to create a HelmApp resource. The namespace
// is the namespace for the workloads.
func CreateHelmApp(
k kubectl.Command,
namespace string,
name string,
chart string,
version string,
shard string,
) error {
return ApplyTemplate(k, AssetPath(helmappTemplate), HelmAppData{
Namespace: namespace,
Name: name,
Chart: chart,
Version: version,
Shard: shard,
})
}

func CreateCluster(
k kubectl.Command,
namespace,
Expand Down
19 changes: 19 additions & 0 deletions e2e/testenv/zothelper/zothelper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package zothelper

import (
"fmt"
"net"

"github.com/rancher/fleet/e2e/testenv/kubectl"
)

func GetOCIReference(k kubectl.Command) (string, error) {
externalIP, err := k.Get("service", "zot-service", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
if err != nil {
return "", err
}
if net.ParseIP(externalIP) == nil {
return "", fmt.Errorf("external ip is not valid")
}
return fmt.Sprintf("oci://%s:8082", externalIP), err
}
4 changes: 2 additions & 2 deletions internal/metrics/helm_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

var (
helmSubsystem = "helm"
helmSubsystem = "helmapp"
helmLabels = []string{"name", "namespace", "repo", "chart", "version"}
HelmCollector = CollectorCollection{
helmSubsystem,
Expand All @@ -27,7 +27,7 @@ var (
"name": helm.Name,
"namespace": helm.Namespace,
"repo": helm.Spec.Helm.Repo,
"branch": helm.Spec.Helm.Chart,
"chart": helm.Spec.Helm.Chart,
"version": helm.Status.Version,
}

Expand Down

0 comments on commit fc68a6c

Please sign in to comment.