t/chaos/utils/utils.go (238 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package utils import ( "fmt" "net/http" "strconv" "strings" "time" "github.com/gavv/httpexpect" "github.com/onsi/ginkgo" "github.com/onsi/gomega" ) var ( token = "edd1c9f034335f136f87ad84b625c8f1" // TODO: refactor the code. We should move the endpoint from the expect to the http call. // So we don't need to remember to pass the correct expect. Host = "http://127.0.0.1:9180" DataPanelHost = "http://127.0.0.1:9080" PrometheusHost = "http://127.0.0.1:9080" setRouteBody = `{ "uri": "/get", "plugins": { "prometheus": {} }, "upstream": { "nodes": { "httpbin.default.svc.cluster.local:8000": 1 }, "type": "roundrobin" } }` ignoreErrorFuncMap = map[string]func(e *httpexpect.Expect) *httpexpect.Response{ http.MethodGet: GetRouteIgnoreError, http.MethodPut: SetRouteIgnoreError, } ) type httpTestCase struct { E *httpexpect.Expect Method string Path string Body string Headers map[string]string IgnoreError bool ExpectStatus int ExpectBody string ExpectStatusRange httpexpect.StatusRange } func caseCheck(tc httpTestCase) *httpexpect.Response { e := tc.E var req *httpexpect.Request switch tc.Method { case http.MethodGet: req = e.GET(tc.Path) case http.MethodPut: req = e.PUT(tc.Path) case http.MethodDelete: req = e.DELETE(tc.Path) default: panic("invalid HTTP method") } if req == nil { panic("fail to init request") } for key, val := range tc.Headers { req.WithHeader(key, val) } if tc.Body != "" { req.WithText(tc.Body) } resp := req.Expect() if tc.IgnoreError { return resp } if tc.ExpectStatus != 0 { resp.Status(tc.ExpectStatus) } if tc.ExpectStatusRange != 0 { resp.StatusRange(tc.ExpectStatusRange) } if tc.ExpectBody != "" { resp.Body().Contains(tc.ExpectBody) } return resp } func SetRoute(e *httpexpect.Expect, expectStatusRange httpexpect.StatusRange) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodPut, Path: "/apisix/admin/routes/1", Headers: map[string]string{"X-API-KEY": token}, Body: setRouteBody, ExpectStatusRange: expectStatusRange, }) } func SetRouteIgnoreError(e *httpexpect.Expect) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodPut, Path: "/apisix/admin/routes/1", Headers: map[string]string{"X-API-KEY": token}, Body: setRouteBody, IgnoreError: true, }) } func GetRoute(e *httpexpect.Expect, expectStatus int) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodGet, Path: "/get", ExpectStatus: expectStatus, }) } func GetRouteIgnoreError(e *httpexpect.Expect) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodGet, Path: "/get", IgnoreError: true, }) } func GetRouteList(e *httpexpect.Expect, expectStatus int) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodGet, Path: "/apisix/admin/routes", Headers: map[string]string{"X-API-KEY": token}, ExpectStatus: expectStatus, ExpectBody: "httpbin.default.svc.cluster.local", }) } func DeleteRoute(e *httpexpect.Expect) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodDelete, Path: "/apisix/admin/routes/1", Headers: map[string]string{"X-API-KEY": token}, }) } func SetPrometheusMetricsPublicAPI(e *httpexpect.Expect) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodPut, Path: "/apisix/admin/routes/metrics", Headers: map[string]string{"X-API-KEY": token}, Body: `{ "uri": "/apisix/prometheus/metrics", "plugins": { "public-api": {} }, "upstream": { "nodes": { "httpbin.default.svc.cluster.local:8000": 1 }, "type": "roundrobin" } }`, }) } func TestPrometheusEtcdMetric(e *httpexpect.Expect, expectEtcd int) *httpexpect.Response { return caseCheck(httpTestCase{ E: e, Method: http.MethodGet, Path: "/apisix/prometheus/metrics", ExpectBody: fmt.Sprintf("apisix_etcd_reachable %d", expectEtcd), }) } // get the first line which contains the key func getPrometheusMetric(e *httpexpect.Expect, key string) string { resp := caseCheck(httpTestCase{ E: e, Method: http.MethodGet, Path: "/apisix/prometheus/metrics", }) resps := strings.Split(resp.Body().Raw(), "\n") var targetLine string for _, line := range resps { if strings.Contains(line, key) { targetLine = line break } } targetSlice := strings.Fields(targetLine) gomega.Ω(len(targetSlice)).Should(gomega.BeNumerically("==", 2)) return targetSlice[1] } func GetEgressBandwidthPerSecond(e *httpexpect.Expect) (float64, float64) { key := "apisix_bandwidth{type=\"egress\"," bandWidthString := getPrometheusMetric(e, key) bandWidthStart, err := strconv.ParseFloat(bandWidthString, 64) gomega.Expect(err).To(gomega.BeNil()) // after etcd got killed, it would take longer time to get the metrics // so need to calculate the duration timeStart := time.Now() time.Sleep(10 * time.Second) bandWidthString = getPrometheusMetric(e, key) bandWidthEnd, err := strconv.ParseFloat(bandWidthString, 64) gomega.Expect(err).To(gomega.BeNil()) duration := time.Since(timeStart) return bandWidthEnd - bandWidthStart, duration.Seconds() } func GetSilentHttpexpectClient() *httpexpect.Expect { return httpexpect.WithConfig(httpexpect.Config{ BaseURL: Host, Reporter: httpexpect.NewAssertReporter(ginkgo.GinkgoT()), Printers: []httpexpect.Printer{ newSilentPrinter(ginkgo.GinkgoT()), }, }) } func WaitUntilMethodSucceed(e *httpexpect.Expect, method string, interval int) { f, ok := ignoreErrorFuncMap[method] gomega.Expect(ok).To(gomega.BeTrue()) resp := f(e) if resp.Raw().StatusCode != http.StatusOK { for i := range [60]int{} { timeWait := fmt.Sprintf("wait for %ds\n", i*interval) fmt.Fprint(ginkgo.GinkgoWriter, timeWait) resp = f(e) if resp.Raw().StatusCode != http.StatusOK { time.Sleep(5 * time.Second) } else { break } } } gomega.Ω(resp.Raw().StatusCode).Should(gomega.BeNumerically("==", http.StatusOK)) } func RoughCompare(a float64, b float64) bool { ratio := a / b if ratio < 1.3 && ratio > 0.7 { return true } return false } type silentPrinter struct { logger httpexpect.Logger } func newSilentPrinter(logger httpexpect.Logger) silentPrinter { return silentPrinter{logger} } // Request implements Printer.Request. func (p silentPrinter) Request(req *http.Request) { } // Response implements Printer.Response. func (silentPrinter) Response(*http.Response, time.Duration) { }