github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/integration/resources/docker/dockerexternal/prometheus.go (about) 1 // Copyright (c) 2021 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package dockerexternal 22 23 import ( 24 "context" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "io/ioutil" 29 "net/http" 30 "time" 31 32 xdockertest "github.com/m3db/m3/src/x/dockertest" 33 "github.com/m3db/m3/src/x/instrument" 34 35 "github.com/cenkalti/backoff/v3" 36 "github.com/ory/dockertest/v3" 37 "github.com/prometheus/common/model" 38 ) 39 40 // Prometheus is a docker-backed instantiation of Prometheus. 41 type Prometheus struct { 42 pool *dockertest.Pool 43 pathToCfg string 44 iOpts instrument.Options 45 46 resource *xdockertest.Resource 47 } 48 49 // PrometheusOptions contains the options for 50 // spinning up docker container running Prometheus 51 type PrometheusOptions struct { 52 // Pool is the connection to the docker API 53 Pool *dockertest.Pool 54 // PathToCfg contains the path to the prometheus.yml configuration 55 // file to be used on startup. 56 PathToCfg string 57 // InstrumentOptions are the instrument.Options to use when 58 // creating the resource. 59 InstrumentOptions instrument.Options 60 } 61 62 // NewPrometheus creates a new docker-backed Prometheus 63 // that implements the resources.ExternalResources interface. 64 func NewPrometheus(opts PrometheusOptions) *Prometheus { 65 if opts.InstrumentOptions == nil { 66 opts.InstrumentOptions = instrument.NewOptions() 67 } 68 return &Prometheus{ 69 pool: opts.Pool, 70 pathToCfg: opts.PathToCfg, 71 iOpts: opts.InstrumentOptions, 72 } 73 } 74 75 // Setup is a method that setups up the prometheus instance. 76 func (p *Prometheus) Setup(ctx context.Context) error { 77 if p.resource != nil { 78 return errors.New("prometheus already setup. must close resource " + 79 "before attempting to setup again") 80 } 81 82 if err := xdockertest.SetupNetwork(p.pool, true); err != nil { 83 return err 84 } 85 86 res, err := xdockertest.NewDockerResource(p.pool, xdockertest.ResourceOptions{ 87 ContainerName: "prometheus", 88 Image: xdockertest.Image{ 89 Name: "prom/prometheus", 90 Tag: "latest", 91 }, 92 PortList: []int{9090}, 93 Mounts: []string{ 94 fmt.Sprintf("%s:/etc/prometheus/prometheus.yml", p.pathToCfg), 95 }, 96 InstrumentOpts: p.iOpts, 97 }) 98 if err != nil { 99 return err 100 } 101 102 p.resource = res 103 104 return p.waitForHealthy(ctx) 105 } 106 107 func (p *Prometheus) waitForHealthy(ctx context.Context) error { 108 retrier := backoff.WithContext(backoff.NewExponentialBackOff(), ctx) 109 return backoff.Retry(func() error { 110 req, err := http.NewRequestWithContext( 111 context.Background(), 112 http.MethodGet, 113 "http://0.0.0.0:9090/-/ready", 114 nil, 115 ) 116 if err != nil { 117 return err 118 } 119 120 client := http.Client{} 121 res, _ := client.Do(req) 122 if res != nil { 123 _ = res.Body.Close() 124 125 if res.StatusCode == http.StatusOK { 126 return nil 127 } 128 } 129 130 return errors.New("prometheus not ready") 131 }, retrier) 132 } 133 134 // PrometheusQueryRequest contains the parameters for making a query request. 135 type PrometheusQueryRequest struct { 136 // Query is the prometheus query to execute 137 Query string 138 // Time is the time to execute the query at 139 Time time.Time 140 } 141 142 // String converts the query request into a string suitable for use 143 // in the url params or request body 144 func (p *PrometheusQueryRequest) String() string { 145 str := fmt.Sprintf("query=%v", p.Query) 146 147 if !p.Time.IsZero() { 148 str += fmt.Sprintf("&time=%v", p.Time.Unix()) 149 } 150 151 return str 152 } 153 154 // Query executes a query request against the prometheus resource. 155 func (p *Prometheus) Query(req PrometheusQueryRequest) (model.Vector, error) { 156 if p.resource.Closed() { 157 return nil, xdockertest.ErrClosed 158 } 159 160 r, err := http.NewRequestWithContext( 161 context.Background(), 162 http.MethodGet, 163 fmt.Sprintf("http://0.0.0.0:9090/api/v1/query?%s", req.String()), 164 nil, 165 ) 166 if err != nil { 167 return nil, err 168 } 169 170 client := http.Client{} 171 res, err := client.Do(r) 172 if err != nil { 173 return nil, err 174 } 175 defer res.Body.Close() 176 177 body, err := ioutil.ReadAll(res.Body) 178 if err != nil { 179 return nil, err 180 } 181 182 if res.StatusCode != http.StatusOK { 183 return nil, fmt.Errorf("non-200 status code received. "+ 184 "status=%v responseBody=%v", res.StatusCode, string(body)) 185 } 186 187 var parsedResp jsonInstantQueryResponse 188 if err := json.Unmarshal(body, &parsedResp); err != nil { 189 return nil, err 190 } 191 192 return parsedResp.Data.Result, nil 193 } 194 195 type jsonInstantQueryResponse struct { 196 Status string 197 Data vectorResult 198 } 199 200 type vectorResult struct { 201 ResultType model.ValueType 202 Result model.Vector 203 } 204 205 // Close cleans up the prometheus instance. 206 func (p *Prometheus) Close(context.Context) error { 207 if p.resource.Closed() { 208 return xdockertest.ErrClosed 209 } 210 211 if err := p.resource.Close(); err != nil { 212 return err 213 } 214 215 p.resource = nil 216 217 return nil 218 }