github.com/verrazzano/verrazzano@v1.7.1/tools/psr/backend/workers/opensearch/getlogs/getlogs.go (about) 1 // Copyright (c) 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package getlogs 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "math/rand" 11 "net/http" 12 "net/url" 13 "sync/atomic" 14 "time" 15 16 "github.com/prometheus/client_golang/prometheus" 17 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 18 "github.com/verrazzano/verrazzano/tools/psr/backend/config" 19 "github.com/verrazzano/verrazzano/tools/psr/backend/metrics" 20 "github.com/verrazzano/verrazzano/tools/psr/backend/osenv" 21 "github.com/verrazzano/verrazzano/tools/psr/backend/spi" 22 ) 23 24 const ( 25 // metricsPrefix is the prefix that is automatically pre-pended to all metrics exported by this worker. 26 metricsPrefix = "opensearch_getlogs" 27 28 osIngestService = "vmi-system-os-ingest.verrazzano-system:9200" 29 letters = "abcdefghijklmnopqrstuvwxyz" 30 ) 31 32 // Use an http client interface so that we can override http.Client for unit tests 33 type httpClientI interface { 34 Do(_ *http.Request) (resp *http.Response, err error) 35 } 36 37 var httpClient httpClientI = &http.Client{} 38 var _ httpClientI = &http.Client{} 39 40 // worker contains the data to perform work 41 type worker struct { 42 metricDescList []prometheus.Desc 43 *workerMetrics 44 } 45 46 var _ spi.Worker = worker{} 47 48 // workerMetrics holds the metrics produced by the worker. Metrics must be thread safe. 49 type workerMetrics struct { 50 openSearchGetSuccessCountTotal metrics.MetricItem 51 openSearchGetFailureCountTotal metrics.MetricItem 52 openSearchGetSuccessLatencyNanoSeconds metrics.MetricItem 53 openSearchGetFailureLatencyNanoSeconds metrics.MetricItem 54 openSearchGetDataCharsTotal metrics.MetricItem 55 } 56 57 func NewGetLogsWorker() (spi.Worker, error) { 58 w := worker{workerMetrics: &workerMetrics{ 59 openSearchGetSuccessCountTotal: metrics.MetricItem{ 60 Name: "success_count_total", 61 Help: "The total number of successful OpenSearch GET requests", 62 Type: prometheus.CounterValue, 63 }, 64 openSearchGetFailureCountTotal: metrics.MetricItem{ 65 Name: "failure_count_total", 66 Help: "The total number of successful OpenSearch GET requests", 67 Type: prometheus.CounterValue, 68 }, 69 openSearchGetSuccessLatencyNanoSeconds: metrics.MetricItem{ 70 Name: "success_latency_nanoseconds", 71 Help: "The latency of successful OpenSearch GET requests in nanoseconds", 72 Type: prometheus.GaugeValue, 73 }, 74 openSearchGetFailureLatencyNanoSeconds: metrics.MetricItem{ 75 Name: "failure_latency_nanoseconds", 76 Help: "The latency of failed OpenSearch GET requests in nanoseconds", 77 Type: prometheus.GaugeValue, 78 }, 79 openSearchGetDataCharsTotal: metrics.MetricItem{ 80 Name: "data_chars_total", 81 Help: "The total number of characters return from OpenSearch get request", 82 Type: prometheus.CounterValue, 83 }, 84 }} 85 86 if err := config.PsrEnv.LoadFromEnv(w.GetEnvDescList()); err != nil { 87 return w, err 88 } 89 90 metricsLabels := map[string]string{ 91 config.PsrWorkerTypeMetricsName: config.PsrEnv.GetEnv(config.PsrWorkerType), 92 } 93 94 w.metricDescList = metrics.BuildMetricDescList([]*metrics.MetricItem{ 95 &w.openSearchGetSuccessCountTotal, 96 &w.openSearchGetFailureCountTotal, 97 &w.openSearchGetSuccessLatencyNanoSeconds, 98 &w.openSearchGetFailureLatencyNanoSeconds, 99 &w.openSearchGetDataCharsTotal, 100 }, metricsLabels, w.GetWorkerDesc().MetricsPrefix) 101 102 return w, nil 103 } 104 105 // GetWorkerDesc returns the WorkerDesc for the worker 106 func (w worker) GetWorkerDesc() spi.WorkerDesc { 107 return spi.WorkerDesc{ 108 WorkerType: config.WorkerTypeOpsGetLogs, 109 Description: "The log getter worker performs GET requests on the OpenSearch endpoint", 110 MetricsPrefix: metricsPrefix, 111 } 112 } 113 114 func (w worker) GetEnvDescList() []osenv.EnvVarDesc { 115 return []osenv.EnvVarDesc{} 116 } 117 118 func (w worker) WantLoopInfoLogged() bool { 119 return false 120 } 121 122 func (w worker) PreconditionsMet() (bool, error) { 123 return true, nil 124 } 125 126 func (w worker) DoWork(conf config.CommonConfig, log vzlog.VerrazzanoLogger) error { 127 c := httpClient 128 req := http.Request{ 129 URL: &url.URL{ 130 Scheme: "http", 131 Host: osIngestService, 132 Path: "/_search", 133 }, 134 Header: http.Header{"Content-Type": {"application/json"}}, 135 Body: getBody(), 136 } 137 startRequest := time.Now().UnixNano() 138 resp, err := c.Do(&req) 139 if err != nil { 140 atomic.AddInt64(&w.workerMetrics.openSearchGetFailureCountTotal.Val, 1) 141 return err 142 } 143 if resp == nil { 144 atomic.AddInt64(&w.workerMetrics.openSearchGetFailureCountTotal.Val, 1) 145 return fmt.Errorf("GET request to URI %s received a nil response", req.URL.RequestURI()) 146 } 147 if resp.StatusCode == 200 { 148 atomic.StoreInt64(&w.workerMetrics.openSearchGetSuccessLatencyNanoSeconds.Val, time.Now().UnixNano()-startRequest) 149 atomic.AddInt64(&w.workerMetrics.openSearchGetSuccessCountTotal.Val, 1) 150 } else { 151 atomic.StoreInt64(&w.workerMetrics.openSearchGetFailureLatencyNanoSeconds.Val, time.Now().UnixNano()-startRequest) 152 atomic.AddInt64(&w.workerMetrics.openSearchGetFailureCountTotal.Val, 1) 153 return fmt.Errorf("OpenSearch GET request failed, returned %v status code", resp.StatusCode) 154 } 155 respBody, err := io.ReadAll(resp.Body) 156 resp.Body.Close() 157 if err != nil { 158 return fmt.Errorf("Error reading response body: %v", err) 159 } 160 atomic.AddInt64(&w.workerMetrics.openSearchGetDataCharsTotal.Val, int64(len(respBody))) 161 return nil 162 } 163 164 func (w worker) GetMetricDescList() []prometheus.Desc { 165 return w.metricDescList 166 } 167 168 func (w worker) GetMetricList() []prometheus.Metric { 169 return []prometheus.Metric{ 170 w.openSearchGetSuccessCountTotal.BuildMetric(), 171 w.openSearchGetFailureCountTotal.BuildMetric(), 172 w.openSearchGetSuccessLatencyNanoSeconds.BuildMetric(), 173 w.openSearchGetFailureLatencyNanoSeconds.BuildMetric(), 174 w.openSearchGetDataCharsTotal.BuildMetric(), 175 } 176 } 177 178 func getBody() io.ReadCloser { 179 body := fmt.Sprintf(` 180 { 181 "query": { 182 "bool": { 183 "should": [ 184 { 185 "match": { 186 "message": "%s" 187 } 188 }, 189 { 190 "match": { 191 "message": "%s" 192 } 193 }, 194 { 195 "match": { 196 "message": "%s" 197 } 198 }, 199 { 200 "match": { 201 "message": "%s" 202 } 203 }, 204 { 205 "match": { 206 "message": "%s" 207 } 208 }, 209 { 210 "match": { 211 "message": "%s" 212 } 213 }, 214 { 215 "match": { 216 "message": "%s" 217 } 218 }, 219 { 220 "match": { 221 "message": "%s" 222 } 223 }, 224 { 225 "match": { 226 "message": "%s" 227 } 228 }, 229 { 230 "match": { 231 "message": "%s" 232 } 233 } 234 ] 235 } 236 } 237 }`, getRandomLowerAlpha(10)...) 238 return io.NopCloser(bytes.NewBuffer([]byte(body))) 239 } 240 241 // getRandomLowerAlpha returns an array of len n of random lowercase letters 242 func getRandomLowerAlpha(n int) []interface{} { 243 var str []interface{} 244 for i := 0; i < n; i++ { 245 str = append(str, string(letters[rand.Intn(len(letters))])) //nolint:gosec //#gosec G404 246 } 247 return str 248 }