From 10be839d8fe1d4abe96aa7080851e4050eac5a01 Mon Sep 17 00:00:00 2001 From: Sam Nguyen Date: Fri, 29 Jul 2022 14:33:00 -0400 Subject: [PATCH] Remove unused metrics cmd --- cmd/cmd.go | 1 - cmd/metrics.go | 168 -------------------------------------- cmd/metrics_test.go | 56 ------------- pkg/prom/prom.go | 52 ------------ pkg/prom/prom_test.go | 185 ------------------------------------------ 5 files changed, 462 deletions(-) delete mode 100644 cmd/metrics.go delete mode 100644 cmd/metrics_test.go delete mode 100644 pkg/prom/prom.go delete mode 100644 pkg/prom/prom_test.go diff --git a/cmd/cmd.go b/cmd/cmd.go index 9ebcae27..17a62255 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -57,7 +57,6 @@ func NewCmdRoot(streams genericclioptions.IOStreams) *cobra.Command { rootCmd.AddCommand(env.NewCmdEnv(streams, kubeFlags)) rootCmd.AddCommand(federatedrole.NewCmdFederatedRole(streams, kubeFlags, kubeClient)) rootCmd.AddCommand(network.NewCmdNetwork(streams, kubeFlags, kubeClient)) - rootCmd.AddCommand(newCmdMetrics(streams, kubeFlags, kubeClient)) rootCmd.AddCommand(servicelog.NewCmdServiceLog()) rootCmd.AddCommand(sts.NewCmdSts(streams, kubeFlags, kubeClient)) diff --git a/cmd/metrics.go b/cmd/metrics.go deleted file mode 100644 index 5e19d41e..00000000 --- a/cmd/metrics.go +++ /dev/null @@ -1,168 +0,0 @@ -package cmd - -import ( - "context" - "crypto/tls" - "fmt" - "net/http" - "strings" - - routev1 "github.com/openshift/api/route/v1" - "github.com/openshift/osdctl/pkg/prom" - "github.com/spf13/cobra" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/cli-runtime/pkg/genericclioptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/openshift/osdctl/cmd/common" -) - -const ( - operatorRoute = common.AWSAccountNamespace - operatorServiceAccount = common.AWSAccountNamespace -) - -// newCmdMetrics displays the metrics of aws-account-operator -func newCmdMetrics(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags, client client.Client) *cobra.Command { - ops := newMetricsOptions(streams, flags, client) - resetCmd := &cobra.Command{ - Use: "metrics", - Short: "Display metrics of aws-account-operator", - Args: cobra.NoArgs, - DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(ops.complete(cmd)) - cmdutil.CheckErr(ops.run()) - }, - } - - resetCmd.Flags().StringVar(&ops.accountNamespace, "account-namespace", common.AWSAccountNamespace, - "The namespace to keep AWS accounts. The default value is aws-account-operator.") - resetCmd.Flags().StringVarP(&ops.metricsURL, "metrics-url", "m", "", "The URL of aws-account-operator metrics endpoint. "+ - "Used only for debug purpose! Only HTTP scheme is supported.") - resetCmd.Flags().StringVarP(&ops.routeName, "route", "r", operatorRoute, "The route created for aws-account-operator") - resetCmd.Flags().StringVar(&ops.saName, "sa", operatorServiceAccount, "The service account name for aws-account-operator") - resetCmd.Flags().BoolVar(&ops.useHTTPS, "https", false, "Use HTTPS to access metrics or not. By default we use HTTP scheme.") - - return resetCmd -} - -// metricsOptions defines the struct for running metrics command -type metricsOptions struct { - accountNamespace string - routeName string - saName string - useHTTPS bool - - // the URL of aws-account-operator metrics endpoint - metricsURL string - - flags *genericclioptions.ConfigFlags - genericclioptions.IOStreams - kubeCli client.Client -} - -func newMetricsOptions(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags, client client.Client) *metricsOptions { - return &metricsOptions{ - flags: flags, - IOStreams: streams, - kubeCli: client, - } -} - -func (o *metricsOptions) complete(cmd *cobra.Command) error { - // account CR name and account ID cannot be empty at the same time - if o.metricsURL == "" && o.routeName == "" { - return cmdutil.UsageErrorf(cmd, "Metrics URL and route name cannot be empty at the same time") - } - - return nil -} - -func (o *metricsOptions) run() error { - var ( - metricsEndpoint string - resp *http.Response - err error - ) - - ctx := context.TODO() - - if o.metricsURL != "" { - metricsEndpoint = o.metricsURL - resp, err = http.Get(metricsEndpoint) //#nosec G107 -- metricsEndpoint cannot be constant - } else { - key := types.NamespacedName{ - Namespace: o.accountNamespace, - Name: o.routeName, - } - - var route routev1.Route - if err := o.kubeCli.Get(ctx, key, &route); err != nil { - return err - } - - if o.useHTTPS { - var sa v1.ServiceAccount - if err := o.kubeCli.Get(ctx, types.NamespacedName{ - Namespace: o.accountNamespace, - Name: o.saName, - }, &sa); err != nil { - return err - } - - var secretName string - for _, v := range sa.Secrets { - if strings.Contains(v.Name, "token") { - secretName = v.Name - } - } - if secretName == "" { - return fmt.Errorf("secret for service account %s doesn't exist", o.saName) - } - - var secret v1.Secret - if err := o.kubeCli.Get(ctx, types.NamespacedName{ - Namespace: o.accountNamespace, - Name: secretName, - }, &secret); err != nil { - return err - } - - token, ok := secret.Data["token"] - if !ok { - return fmt.Errorf("secret %s doesn't have token field", secretName) - } - metricsEndpoint = "https://" + route.Spec.Host + "/metrics" - req, err := http.NewRequest(http.MethodGet, metricsEndpoint, nil) - if err != nil { - return err - } - - req.Header.Add("Authorization", "Bearer "+string(token)) - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //#nosec G402 -- metricsEndpoint listens on HTTP only - resp, err = http.DefaultClient.Do(req) - } else { - metricsEndpoint = "http://" + route.Spec.Host + "/metrics" - resp, err = http.Get(metricsEndpoint) //#nosec G107 -- metricsEndpoint cannot be constant - } - } - - if err != nil { - return err - } - defer resp.Body.Close() - metrics, err := prom.DecodeMetrics(resp.Body, map[string]string{"name": "aws-account-operator"}) - if err != nil { - return err - } - - for _, v := range metrics { - fmt.Fprintln(o.IOStreams.Out, v) - } - - return nil -} diff --git a/cmd/metrics_test.go b/cmd/metrics_test.go deleted file mode 100644 index 4a56d7b1..00000000 --- a/cmd/metrics_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package cmd - -import ( - "os" - "strings" - "testing" - - "github.com/golang/mock/gomock" - . "github.com/onsi/gomega" - - mockk8s "github.com/openshift/osdctl/cmd/clusterdeployment/mock/k8s" - "k8s.io/cli-runtime/pkg/genericclioptions" -) - -func TestMetricsCmdComplete(t *testing.T) { - g := NewGomegaWithT(t) - mockCtrl := gomock.NewController(t) - streams := genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr} - kubeFlags := genericclioptions.NewConfigFlags(false) - testCases := []struct { - title string - option *metricsOptions - errExpected bool - errContent string - }{ - { - title: "no route and metrics url specified", - option: &metricsOptions{}, - errExpected: true, - errContent: "Metrics URL and route name cannot be empty at the same time", - }, - { - title: "route name specified", - option: &metricsOptions{ - routeName: "aaa", - flags: kubeFlags, - }, - errExpected: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.title, func(t *testing.T) { - cmd := newCmdMetrics(streams, kubeFlags, mockk8s.NewMockClient(mockCtrl)) - err := tc.option.complete(cmd) - if tc.errExpected { - g.Expect(err).Should(HaveOccurred()) - if tc.errContent != "" { - g.Expect(true).Should(Equal(strings.Contains(err.Error(), tc.errContent))) - } - } else { - g.Expect(err).ShouldNot(HaveOccurred()) - } - }) - } -} diff --git a/pkg/prom/prom.go b/pkg/prom/prom.go deleted file mode 100644 index 650288ad..00000000 --- a/pkg/prom/prom.go +++ /dev/null @@ -1,52 +0,0 @@ -package prom - -import ( - "fmt" - "io" - "sort" - - "github.com/prometheus/common/expfmt" - "github.com/prometheus/common/model" -) - -// DecodeMetrics decodes Prometheus metrics. -func DecodeMetrics(r io.Reader, matchers map[string]string) ([]string, error) { - dec := expfmt.NewDecoder(r, expfmt.FmtText) - decoder := expfmt.SampleDecoder{ - Dec: dec, - Opts: &expfmt.DecodeOptions{}, - } - - res := make([]string, 0) - for { - var vector model.Vector - if err := decoder.Decode(&vector); err != nil { - if err == io.EOF { - break - } - return nil, err - } - for _, metric := range vector { - if matchLabels(metric.Metric, matchers) { - res = append(res, fmt.Sprintf("%s => %f", metric.Metric, metric.Value)) - } - } - } - - sort.Strings(res) - - return res, nil -} - -// matchLabels checks whether the metric has the required labels or not -func matchLabels(metric model.Metric, matchers map[string]string) bool { - for k, v := range matchers { - if labelValue, ok := metric[model.LabelName(k)]; !ok { - return false - } else if string(labelValue) != v { - return false - } - } - - return true -} diff --git a/pkg/prom/prom_test.go b/pkg/prom/prom_test.go deleted file mode 100644 index 89cfbdca..00000000 --- a/pkg/prom/prom_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package prom - -import ( - "bytes" - "io" - "testing" - - . "github.com/onsi/gomega" - "github.com/prometheus/common/model" -) - -func TestMatchLabels(t *testing.T) { - g := NewGomegaWithT(t) - - testCases := []struct { - title string - metric model.Metric - matchers map[string]string - match bool - }{ - { - title: "one label-value pair match", - metric: map[model.LabelName]model.LabelValue{ - "label1": "value1", - }, - matchers: map[string]string{ - "label1": "value1", - }, - match: true, - }, - { - title: "multiple label sets match", - metric: map[model.LabelName]model.LabelValue{ - "label1": "value1", - "label2": "value2", - }, - matchers: map[string]string{ - "label1": "value1", - }, - match: true, - }, - { - title: "no matchers", - metric: map[model.LabelName]model.LabelValue{ - "label1": "value1", - }, - matchers: map[string]string{}, - match: true, - }, - { - title: "no metric", - metric: map[model.LabelName]model.LabelValue{}, - matchers: map[string]string{ - "label1": "value1", - }, - match: false, - }, - { - title: "metric don't have all required matchers", - metric: map[model.LabelName]model.LabelValue{ - "label1": "value1", - }, - matchers: map[string]string{ - "label1": "value1", - "label2": "value2", - }, - match: false, - }, - { - title: "same label, different value", - metric: map[model.LabelName]model.LabelValue{ - "label1": "value1", - }, - matchers: map[string]string{ - "label1": "foo", - }, - match: false, - }, - { - title: "different label set", - metric: map[model.LabelName]model.LabelValue{ - "label1": "value1", - }, - matchers: map[string]string{ - "foo": "bar", - }, - match: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.title, func(t *testing.T) { - matched := matchLabels(tc.metric, tc.matchers) - g.Expect(matched).Should(Equal(tc.match)) - }) - } -} - -func TestDecodeMetrics(t *testing.T) { - g := NewGomegaWithT(t) - - exampleMetrics := `# HELP go_info Information about the Go environment. -# TYPE go_info gauge -go_info{foo="bar"} 1 -go_info{foo="buz"} 0 -` - - testCases := []struct { - title string - input io.Reader - matchers map[string]string - hasError bool - metrics []string - }{ - { - title: "invalid metrics format", - input: bytes.NewReader([]byte(`123`)), - matchers: map[string]string{ - "foo": "bar", - }, - hasError: true, - metrics: []string{}, - }, - // This case doesn't throw error, but return empty string slice - { - title: "empty input metrics", - input: bytes.NewReader([]byte("")), - matchers: map[string]string{ - "foo": "bar", - }, - hasError: false, - metrics: []string{}, - }, - { - title: "match one metric", - input: bytes.NewReader([]byte(exampleMetrics)), - matchers: map[string]string{ - "foo": "bar", - }, - hasError: false, - metrics: []string{`go_info{foo="bar"} => 1.000000`}, - }, - { - title: "match another metric", - input: bytes.NewReader([]byte(exampleMetrics)), - matchers: map[string]string{ - "foo": "buz", - }, - hasError: false, - metrics: []string{`go_info{foo="buz"} => 0.000000`}, - }, - { - title: "match all metrics with name go_info", - input: bytes.NewReader([]byte(exampleMetrics)), - matchers: map[string]string{ - "__name__": "go_info", - }, - hasError: false, - metrics: []string{ - `go_info{foo="bar"} => 1.000000`, - `go_info{foo="buz"} => 0.000000`, - }, - }, - { - title: "no match", - input: bytes.NewReader([]byte(exampleMetrics)), - matchers: map[string]string{ - "foo": "aaa", - }, - hasError: false, - metrics: []string{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.title, func(t *testing.T) { - metrics, err := DecodeMetrics(tc.input, tc.matchers) - if !tc.hasError { - g.Expect(metrics).Should(Equal(tc.metrics)) - } else { - g.Expect(err).Should(HaveOccurred()) - } - }) - } -}