istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/components/prometheus/kube.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package prometheus 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 "sort" 24 "strings" 25 "time" 26 27 prometheusApi "github.com/prometheus/client_golang/api" 28 prometheusApiV1 "github.com/prometheus/client_golang/api/prometheus/v1" 29 "github.com/prometheus/common/model" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 32 istioKube "istio.io/istio/pkg/kube" 33 "istio.io/istio/pkg/test/env" 34 "istio.io/istio/pkg/test/framework/components/cluster" 35 "istio.io/istio/pkg/test/framework/components/istio" 36 "istio.io/istio/pkg/test/framework/resource" 37 "istio.io/istio/pkg/test/framework/resource/config/apply" 38 testKube "istio.io/istio/pkg/test/kube" 39 "istio.io/istio/pkg/test/scopes" 40 ) 41 42 const ( 43 serviceName = "prometheus" 44 appName = "prometheus" 45 ) 46 47 var ( 48 _ Instance = &kubeComponent{} 49 _ io.Closer = &kubeComponent{} 50 ) 51 52 type kubeComponent struct { 53 id resource.ID 54 55 api map[string]prometheusApiV1.API 56 forwarder map[string]istioKube.PortForwarder 57 clusters cluster.Clusters 58 } 59 60 func getPrometheusYaml() (string, error) { 61 yamlBytes, err := os.ReadFile(filepath.Join(env.IstioSrc, "samples/addons/prometheus.yaml")) 62 if err != nil { 63 return "", err 64 } 65 yaml := string(yamlBytes) 66 // For faster tests, drop scrape interval 67 yaml = strings.ReplaceAll(yaml, "scrape_interval: 15s", "scrape_interval: 5s") 68 yaml = strings.ReplaceAll(yaml, "scrape_timeout: 10s", "scrape_timeout: 5s") 69 return yaml, nil 70 } 71 72 func installPrometheus(ctx resource.Context, ns string) error { 73 yaml, err := getPrometheusYaml() 74 if err != nil { 75 return err 76 } 77 if err := ctx.ConfigKube().YAML(ns, yaml).Apply(apply.NoCleanup); err != nil { 78 return err 79 } 80 ctx.CleanupConditionally(func() { 81 _ = ctx.ConfigKube().YAML(ns, yaml).Delete() 82 }) 83 return nil 84 } 85 86 func newKube(ctx resource.Context, cfgIn Config) (Instance, error) { 87 c := &kubeComponent{ 88 clusters: ctx.Clusters(), 89 } 90 c.id = ctx.TrackResource(c) 91 c.api = make(map[string]prometheusApiV1.API) 92 c.forwarder = make(map[string]istioKube.PortForwarder) 93 cfg, err := istio.DefaultConfig(ctx) 94 if err != nil { 95 return nil, err 96 } 97 98 if !cfgIn.SkipDeploy { 99 if err := installPrometheus(ctx, cfg.TelemetryNamespace); err != nil { 100 return nil, err 101 } 102 } 103 for _, cls := range ctx.Clusters().Kube() { 104 scopes.Framework.Debugf("Installing Prometheus on cluster: %s", cls.Name()) 105 // Find the Prometheus pod and service, and start forwarding a local port. 106 fetchFn := testKube.NewSinglePodFetch(cls, cfg.TelemetryNamespace, fmt.Sprintf("app.kubernetes.io/name=%s", appName)) 107 pods, err := testKube.WaitUntilPodsAreReady(fetchFn) 108 if err != nil { 109 return nil, err 110 } 111 pod := pods[0] 112 113 svc, err := cls.Kube().CoreV1().Services(cfg.TelemetryNamespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) 114 if err != nil { 115 return nil, err 116 } 117 port := uint16(svc.Spec.Ports[0].Port) 118 119 forwarder, err := cls.NewPortForwarder(pod.Name, pod.Namespace, "", 0, int(port)) 120 if err != nil { 121 return nil, err 122 } 123 124 if err := forwarder.Start(); err != nil { 125 return nil, err 126 } 127 c.forwarder[cls.Name()] = forwarder 128 scopes.Framework.Debugf("initialized Prometheus port forwarder: %v", forwarder.Address()) 129 130 address := fmt.Sprintf("http://%s", forwarder.Address()) 131 var client prometheusApi.Client 132 client, err = prometheusApi.NewClient(prometheusApi.Config{Address: address}) 133 if err != nil { 134 return nil, err 135 } 136 137 c.api[cls.Name()] = prometheusApiV1.NewAPI(client) 138 } 139 return c, nil 140 } 141 142 func (c *kubeComponent) ID() resource.ID { 143 return c.id 144 } 145 146 // API implements environment.DeployedPrometheus. 147 func (c *kubeComponent) API() prometheusApiV1.API { 148 return c.api[c.clusters.Default().Name()] 149 } 150 151 func (c *kubeComponent) APIForCluster(cluster cluster.Cluster) prometheusApiV1.API { 152 return c.api[cluster.Name()] 153 } 154 155 func (c *kubeComponent) RawQuery(cluster cluster.Cluster, promQL string) (model.Value, error) { 156 scopes.Framework.Debugf("Query running: %s", promQL) 157 158 v, _, err := c.api[cluster.Name()].Query(context.Background(), promQL, time.Now()) 159 if err != nil { 160 return nil, fmt.Errorf("error querying Prometheus: %v", err) 161 } 162 scopes.Framework.Debugf("Query received: %v", v) 163 164 switch v.Type() { 165 case model.ValScalar, model.ValString: 166 return v, nil 167 168 case model.ValVector: 169 value := v.(model.Vector) 170 if len(value) == 0 { 171 return nil, fmt.Errorf("value not found (query: %v)", promQL) 172 } 173 return v, nil 174 175 default: 176 return nil, fmt.Errorf("unhandled value type: %v", v.Type()) 177 } 178 } 179 180 func (c *kubeComponent) Query(cluster cluster.Cluster, query Query) (model.Value, error) { 181 return c.RawQuery(cluster, query.String()) 182 } 183 184 func (c *kubeComponent) QuerySum(cluster cluster.Cluster, query Query) (float64, error) { 185 val, err := c.Query(cluster, query) 186 if err != nil { 187 return 0, err 188 } 189 got, err := Sum(val) 190 if err != nil { 191 return 0, fmt.Errorf("could not find metric value: %v", err) 192 } 193 return got, nil 194 } 195 196 func Sum(val model.Value) (float64, error) { 197 if val.Type() != model.ValVector { 198 return 0, fmt.Errorf("value not a model.Vector; was %s", val.Type().String()) 199 } 200 201 value := val.(model.Vector) 202 203 valueCount := 0.0 204 for _, sample := range value { 205 valueCount += float64(sample.Value) 206 } 207 208 if valueCount > 0.0 { 209 return valueCount, nil 210 } 211 return 0, fmt.Errorf("value not found") 212 } 213 214 // Close implements io.Closer. 215 func (c *kubeComponent) Close() error { 216 for _, forwarder := range c.forwarder { 217 forwarder.Close() 218 } 219 return nil 220 } 221 222 type Query struct { 223 Metric string 224 Aggregation string 225 Labels map[string]string 226 } 227 228 func (q Query) String() string { 229 query := q.Metric + `{` 230 231 keys := []string{} 232 for k := range q.Labels { 233 keys = append(keys, k) 234 } 235 sort.Strings(keys) 236 for _, k := range keys { 237 v := q.Labels[k] 238 query += fmt.Sprintf(`%s=%q,`, k, v) 239 } 240 query += "}" 241 if q.Aggregation != "" { 242 query = fmt.Sprintf(`%s(%s)`, q.Aggregation, query) 243 } 244 return query 245 }