Skip to content
Merged
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 10 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,28 @@ require (
github.com/openshift/library-go v0.0.0-20200424095618-2aeb4725dadf
github.com/operator-framework/kubectl-operator v0.3.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.11.0
github.com/rawagner/graphql-transport-ws v0.0.0-20200817140314-dcfbf0388067
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
gopkg.in/yaml.v2 v2.4.0
helm.sh/helm/v3 v3.6.2
k8s.io/api v0.21.0
k8s.io/apiextensions-apiserver v0.21.0
k8s.io/apimachinery v0.21.0
k8s.io/api v0.21.1
k8s.io/apiextensions-apiserver v0.21.1
k8s.io/apimachinery v0.21.1
k8s.io/cli-runtime v0.21.0
k8s.io/client-go v0.21.0
k8s.io/client-go v0.21.1
k8s.io/klog v1.0.0
sigs.k8s.io/controller-runtime v0.8.2
k8s.io/klog/v2 v2.8.0
sigs.k8s.io/controller-runtime v0.9.0
sigs.k8s.io/yaml v1.2.0
)

replace (
github.com/Azure/go-autorest/autorest => github.com/Azure/go-autorest/autorest v0.9.0
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
github.com/irifrance/gini v1.0.1 => github.com/go-air/gini v1.0.1
github.com/mikefarah/yq/v2 v2.4.1 => github.com/mikefarah/yq/v4 v4.13.2
github.com/opencontainers/runc => github.com/opencontainers/runc v1.0.0-rc8.0.20190926150303-84373aaa560b
)
132 changes: 95 additions & 37 deletions go.sum

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions pkg/helm/actions/install_chart.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package actions

import (
"github.com/openshift/console/pkg/helm/metrics"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
Expand Down Expand Up @@ -40,5 +41,10 @@ func InstallChart(ns, name, url string, vals map[string]interface{}, conf *actio
if err != nil {
return nil, err
}

if ch.Metadata.Name != "" && ch.Metadata.Version != "" {
metrics.HandleconsoleHelmInstallsTotal(ch.Metadata.Name, ch.Metadata.Version)
}

return release, nil
}
7 changes: 7 additions & 0 deletions pkg/helm/actions/uninstall_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package actions
import (
"strings"

"github.com/openshift/console/pkg/helm/metrics"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/release"
)
Expand All @@ -16,5 +17,11 @@ func UninstallRelease(name string, conf *action.Configuration) (*release.Uninsta
}
return nil, err
}

ch := resp.Release.Chart
if ch != nil && ch.Metadata != nil && ch.Metadata.Name != "" && ch.Metadata.Version != "" {
metrics.HandleconsoleHelmUninstallsTotal(ch.Metadata.Name, ch.Metadata.Version)
}

return resp, nil
}
12 changes: 11 additions & 1 deletion pkg/helm/actions/upgrade_release.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package actions
import (
"strings"

"github.com/openshift/console/pkg/helm/metrics"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chart/loader"
Expand Down Expand Up @@ -61,5 +62,14 @@ func UpgradeRelease(ns, name, url string, vals map[string]interface{}, conf *act
ch.Metadata.Annotations["chart_url"] = url
}

return client.Run(name, ch, vals)
rel, err = client.Run(name, ch, vals)
if err != nil {
return nil, err
}

if ch.Metadata.Name != "" && ch.Metadata.Version != "" {
metrics.HandleconsoleHelmUpgradesTotal(ch.Metadata.Name, ch.Metadata.Version)
}

return rel, nil
}
75 changes: 75 additions & 0 deletions pkg/helm/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package metrics

import (
"github.com/prometheus/client_golang/prometheus"
"k8s.io/klog/v2"
)

const (
consoleHelmInstallsTotalMetric = "console_helm_installs_total"
consoleHelmUpgradesTotalMetric = "console_helm_upgrades_total"
consoleHelmUninstallsTotalMetric = "console_helm_uninstalls_total"

consoleHelmChartNameLabel = "chart_name"
consoleHelmChartVersionLabel = "chart_version"
)

var (
consoleHelmInstallsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: consoleHelmInstallsTotalMetric,
Help: "Number of Helm installations from console by chart name and version.",
},
[]string{consoleHelmChartNameLabel, consoleHelmChartVersionLabel},
)
consoleHelmUpgradesTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: consoleHelmUpgradesTotalMetric,
Help: "Number of Helm release upgrades from console by chart name and version.",
},
[]string{consoleHelmChartNameLabel, consoleHelmChartVersionLabel},
)
consoleHelmUninstallsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: consoleHelmUninstallsTotalMetric,
Help: "Number of Helm release uninstallations from console by chart name and version.",
},
[]string{consoleHelmChartNameLabel, consoleHelmChartVersionLabel},
)
)

func init() {
prometheus.MustRegister(consoleHelmInstallsTotal)
prometheus.MustRegister(consoleHelmUpgradesTotal)
prometheus.MustRegister(consoleHelmUninstallsTotal)
}

func HandleconsoleHelmInstallsTotal(chartName, chartVersion string) {
klog.V(4).Infof("metric %s: %s %s", consoleHelmInstallsTotalMetric, chartName, chartVersion)
counter, err := consoleHelmInstallsTotal.GetMetricWithLabelValues(chartName, chartVersion)
if err != nil {
klog.Errorf("Recovering from metric function - %v", err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wont recover the function since the GetMetricWithLabelValues throws a panic.
Instead

func HandleconsoleHelmInstallsTotal(chartName, chartVersion string) {
	defer recoverMetricPanic()
        klog.V(4).Infof("metric console_helm_installs_total: %s %s", chartName, chartVersion)
	counter, _ := consoleHelmInstallsTotal.GetMetricWithLabelValues(chartName, chartVersion)
        counter.Add(1)
}

// We will never want to panic console server because of metric saving.
// Therefore, we will recover our panics here and error log them
// for later diagnosis but will never fail the console server.
func recoverMetricPanic() {
	if r := recover(); r != nil {
		klog.Errorf("Recovering from metric function - %v", r)
	}
}

We should do the same for the HandleconsoleHelmUpgradesTotal & HandleconsoleHelmUninstallsTotal functions

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the official doc it seems like WithLabelValues will panic in the case when GetMetricWithLabelValues returns an error: https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#CounterVec.GetMetricWithLabelValues vs. https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#CounterVec.WithLabelValues.

Could you confirm the above doc and GetMetricWithLabelValues will indeed panic? Will proceed to update after confirming :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetMetricWithLabelValues won't panic. WithLabelValues would only panic if you give it an incorrect number of parameters (e.g. your metric declares 2 labels but you only passed 1 value).
In general, it's better to let WithLabelValues crash the program since it means that there's a programmatic error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@simonpasquier you are right, WithLabelValues would only panic. Misread the code 👍

return
}
counter.Add(1)
}

func HandleconsoleHelmUpgradesTotal(chartName, chartVersion string) {
klog.V(4).Infof("metric %s: %s %s", consoleHelmUpgradesTotalMetric, chartName, chartVersion)
counter, err := consoleHelmUpgradesTotal.GetMetricWithLabelValues(chartName, chartVersion)
if err != nil {
klog.Errorf("Recovering from metric function - %v", err)
return
}
counter.Add(1)
}

func HandleconsoleHelmUninstallsTotal(chartName, chartVersion string) {
klog.V(4).Infof("metric %s: %s %s", consoleHelmUninstallsTotalMetric, chartName, chartVersion)
counter, err := consoleHelmUninstallsTotal.GetMetricWithLabelValues(chartName, chartVersion)
if err != nil {
klog.Errorf("Recovering from metric function - %v", err)
return
}
counter.Add(1)
}
191 changes: 191 additions & 0 deletions pkg/helm/metrics/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package metrics

import (
"bufio"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"

"github.com/prometheus/client_golang/prometheus/promhttp"
)

func TestMetricsNoRelease(t *testing.T) {
consoleHelmInstallsTotal.Reset()
consoleHelmUpgradesTotal.Reset()
consoleHelmUninstallsTotal.Reset()
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()

count := countMetric(t, ts, consoleHelmInstallsTotalMetric)
if count > 0 {
t.Errorf("%s should not be available", consoleHelmInstallsTotalMetric)
}

count = countMetric(t, ts, consoleHelmUpgradesTotalMetric)
if count > 0 {
t.Errorf("%s should not be available", consoleHelmUpgradesTotalMetric)
}

count = countMetric(t, ts, consoleHelmUninstallsTotalMetric)
if count > 0 {
t.Errorf("%s should not be available", consoleHelmUninstallsTotalMetric)
}
}

func TestMetricsSingleRelease(t *testing.T) {
consoleHelmInstallsTotal.Reset()
consoleHelmUpgradesTotal.Reset()
consoleHelmUninstallsTotal.Reset()
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()

chartName, chartVersion := "test-chart", "0.0.1"
chartNameLabel, chartVersionLabel := fmt.Sprintf("%s=\"%v\"", consoleHelmChartNameLabel, chartName), fmt.Sprintf("%s=\"%v\"", consoleHelmChartVersionLabel, chartVersion)
HandleconsoleHelmInstallsTotal(chartName, chartVersion)
HandleconsoleHelmUpgradesTotal(chartName, chartVersion)
HandleconsoleHelmUninstallsTotal(chartName, chartVersion)

count := countMetric(t, ts, consoleHelmInstallsTotalMetric, chartNameLabel, chartVersionLabel)
if count != 1 {
t.Errorf("%s with labels %s, %s should be 1: %v", consoleHelmInstallsTotalMetric, chartNameLabel, chartVersionLabel, count)
}

count = countMetric(t, ts, consoleHelmUpgradesTotalMetric, chartNameLabel, chartVersionLabel)
if count != 1 {
t.Errorf("%s with labels %s, %s should be 1: %v", consoleHelmUpgradesTotalMetric, chartNameLabel, chartVersionLabel, count)
}

count = countMetric(t, ts, consoleHelmUninstallsTotalMetric, chartNameLabel, chartVersionLabel)
if count != 1 {
t.Errorf("%s with labels %s, %s should be 1: %v", consoleHelmUninstallsTotalMetric, chartNameLabel, chartVersionLabel, count)
}
}

func TestMetricsMultipleReleases(t *testing.T) {
consoleHelmInstallsTotal.Reset()
consoleHelmUpgradesTotal.Reset()
consoleHelmUninstallsTotal.Reset()
ts := httptest.NewServer(promhttp.Handler())
defer ts.Close()

chartName, chartVersion := "test-chart", "0.0.1"
chartNameLabel, chartVersionLabel := fmt.Sprintf("%s=\"%v\"", consoleHelmChartNameLabel, chartName), fmt.Sprintf("%s=\"%v\"", consoleHelmChartVersionLabel, chartVersion)
HandleconsoleHelmInstallsTotal(chartName, chartVersion)
HandleconsoleHelmInstallsTotal(chartName, chartVersion)

HandleconsoleHelmUpgradesTotal(chartName, chartVersion)
HandleconsoleHelmUpgradesTotal(chartName, chartVersion)

HandleconsoleHelmUninstallsTotal(chartName, chartVersion)
HandleconsoleHelmUninstallsTotal(chartName, chartVersion)

count := countMetric(t, ts, consoleHelmInstallsTotalMetric, chartNameLabel, chartVersionLabel)
if count != 2 {
t.Errorf("%s with labels %s, %s should be 2: %v", consoleHelmInstallsTotalMetric, chartNameLabel, chartVersionLabel, count)
}

count = countMetric(t, ts, consoleHelmUpgradesTotalMetric, chartNameLabel, chartVersionLabel)
if count != 2 {
t.Errorf("%s with labels %s, %s should be 2: %v", consoleHelmUpgradesTotalMetric, chartNameLabel, chartVersionLabel, count)
}

count = countMetric(t, ts, consoleHelmUninstallsTotalMetric, chartNameLabel, chartVersionLabel)
if count != 2 {
t.Errorf("%s with labels %s, %s should be 2: %v", consoleHelmUninstallsTotalMetric, chartNameLabel, chartVersionLabel, count)
}

chartName, chartVersion = "test-chart-2", "0.0.2"
chartNameLabel, chartVersionLabel = fmt.Sprintf("%s=\"%v\"", consoleHelmChartNameLabel, chartName), fmt.Sprintf("%s=\"%v\"", consoleHelmChartVersionLabel, chartVersion)
HandleconsoleHelmInstallsTotal(chartName, chartVersion)
HandleconsoleHelmUpgradesTotal(chartName, chartVersion)
HandleconsoleHelmUninstallsTotal(chartName, chartVersion)

count = countMetric(t, ts, consoleHelmInstallsTotalMetric, chartNameLabel, chartVersionLabel)
if count != 1 {
t.Errorf("%s with labels %s, %s should be 1: %v", consoleHelmInstallsTotalMetric, chartNameLabel, chartVersionLabel, count)
}

count = countMetric(t, ts, consoleHelmUpgradesTotalMetric, chartNameLabel, chartVersionLabel)
if count != 1 {
t.Errorf("%s with labels %s, %s should be 1: %v", consoleHelmUpgradesTotalMetric, chartNameLabel, chartVersionLabel, count)
}

count = countMetric(t, ts, consoleHelmUninstallsTotalMetric, chartNameLabel, chartVersionLabel)
if count != 1 {
t.Errorf("%s with labels %s, %s should be 1: %v", consoleHelmUninstallsTotalMetric, chartNameLabel, chartVersionLabel, count)
}

// Metrics without specific labels
count = countMetric(t, ts, consoleHelmInstallsTotalMetric)
if count != 3 {
t.Errorf("%s without labels should be 3: %v", consoleHelmInstallsTotalMetric, count)
}

count = countMetric(t, ts, consoleHelmUpgradesTotalMetric)
if count != 3 {
t.Errorf("%s without labels should be 3: %v", consoleHelmUpgradesTotalMetric, count)
}

count = countMetric(t, ts, consoleHelmUninstallsTotalMetric)
if count != 3 {
t.Errorf("%s without labels should be 3: %v", consoleHelmUninstallsTotalMetric, count)
}
}

func getMetrics(t *testing.T, ts *httptest.Server) *http.Response {
res, err := http.Get(ts.URL + "/metrics")
if err != nil {
t.Errorf("http error: %s", err)
}

if res.StatusCode != 200 {
t.Errorf("http error: %d %s", res.StatusCode, http.StatusText(res.StatusCode))
}
return res
}

func countMetric(t *testing.T, ts *httptest.Server, metric string, labels ...string) int {
res := getMetrics(t, ts)
defer res.Body.Close()

bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("read error: %s", err)
}

return countMetricWithLabels(t, string(bytes), metric, labels...)
}

func countMetricWithLabels(t *testing.T, response, metric string, labels ...string) (count int) {
scanner := bufio.NewScanner(strings.NewReader(response))
for scanner.Scan() {
text := scanner.Text()
// skip comments
if strings.HasPrefix(text, "#") {
continue
}
if strings.Contains(text, metric) {
t.Logf("found %s\n", scanner.Text())
curr_count, _ := strconv.Atoi(text[len(text)-1:])
// no specific labels, count all
if len(labels) == 0 {
count += curr_count
}
// return metric value with specified labels
for i, label := range labels {
if !strings.Contains(text, label) {
break
}
if i == len(labels)-1 {
// return directly since metrics are aggregated
return curr_count
}
}
}
}
return count
}
18 changes: 18 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"time"

"github.com/coreos/pkg/health"
"github.com/prometheus/client_golang/prometheus/promhttp"

"github.com/openshift/console/pkg/auth"
"github.com/openshift/console/pkg/graphql/resolver"
Expand Down Expand Up @@ -470,6 +471,23 @@ func (s *Server) HTTPHandler() http.Handler {
})

// Helm Endpoints
metricsHandler := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Requests from prometheus-k8s have the access token in headers instead of cookies.
// This allows metric requests with proper tokens in either headers or cookies.
if r.URL.Path == "/metrics" {
openshiftSessionCookieName := "openshift-session-token"
openshiftSessionCookieValue := r.Header.Get("Authorization")
r.AddCookie(&http.Cookie{Name: openshiftSessionCookieName, Value: openshiftSessionCookieValue})
}
next.ServeHTTP(w, r)
})
}

handle("/metrics", metricsHandler(authHandler(func(w http.ResponseWriter, r *http.Request) {
promhttp.Handler().ServeHTTP(w, r)
})))

handle("/api/helm/template", authHandlerWithUser(helmHandlers.HandleHelmRenderManifests))
handle("/api/helm/releases", authHandlerWithUser(helmHandlers.HandleHelmList))
handle("/api/helm/chart", authHandlerWithUser(helmHandlers.HandleChartGet))
Expand Down
Loading