github.com/verrazzano/verrazzano@v1.7.1/tools/psr/backend/workers/opensearch/postlogs/postlogs.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 postlogs 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 "strconv" 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/pkg/security/password" 19 "github.com/verrazzano/verrazzano/tools/psr/backend/config" 20 "github.com/verrazzano/verrazzano/tools/psr/backend/metrics" 21 "github.com/verrazzano/verrazzano/tools/psr/backend/osenv" 22 "github.com/verrazzano/verrazzano/tools/psr/backend/spi" 23 ) 24 25 const ( 26 // metricsPrefix is the prefix that is automatically pre-pended to all metrics exported by this worker. 27 metricsPrefix = "opensearch_postlogs" 28 29 LogEntries = "LOG_ENTRIES" 30 LogLength = "LOG_LENGTH" 31 osIngestService = "vmi-system-os-ingest.verrazzano-system:9200" 32 ) 33 34 // Use an http client interface so that we can override http.Client for unit tests 35 type httpClientI interface { 36 Do(_ *http.Request) (resp *http.Response, err error) 37 } 38 39 var httpClient httpClientI = &http.Client{} 40 var _ httpClientI = &http.Client{} 41 42 type worker struct { 43 metricDescList []prometheus.Desc 44 *workerMetrics 45 } 46 47 var _ spi.Worker = worker{} 48 49 // workerMetrics holds the metrics produced by the worker. Metrics must be thread safe. 50 type workerMetrics struct { 51 openSearchPostSuccessCountTotal metrics.MetricItem 52 openSearchPostFailureCountTotal metrics.MetricItem 53 openSearchPostSuccessLatencyNanoSeconds metrics.MetricItem 54 openSearchPostFailureLatencyNanoSeconds metrics.MetricItem 55 openSearchPostDataCharsTotal metrics.MetricItem 56 } 57 58 func NewPostLogsWorker() (spi.Worker, error) { 59 w := worker{workerMetrics: &workerMetrics{ 60 openSearchPostSuccessCountTotal: metrics.MetricItem{ 61 Name: "success_count_total", 62 Help: "The total number of successful OpenSearch POST requests", 63 Type: prometheus.CounterValue, 64 }, 65 openSearchPostFailureCountTotal: metrics.MetricItem{ 66 Name: "failure_count_total", 67 Help: "The total number of successful OpenSearch POST requests", 68 Type: prometheus.CounterValue, 69 }, 70 openSearchPostSuccessLatencyNanoSeconds: metrics.MetricItem{ 71 Name: "success_latency_nanoseconds", 72 Help: "The latency of successful OpenSearch POST requests in nanoseconds", 73 Type: prometheus.GaugeValue, 74 }, 75 openSearchPostFailureLatencyNanoSeconds: metrics.MetricItem{ 76 Name: "failure_latency_nanoseconds", 77 Help: "The latency of failed OpenSearch POST requests in nanoseconds", 78 Type: prometheus.GaugeValue, 79 }, 80 openSearchPostDataCharsTotal: metrics.MetricItem{ 81 Name: "data_chars_total", 82 Help: "The total number of characters posted to OpenSearch", 83 Type: prometheus.CounterValue, 84 }, 85 }} 86 87 if err := config.PsrEnv.LoadFromEnv(w.GetEnvDescList()); err != nil { 88 return w, err 89 } 90 91 metricsLabels := map[string]string{ 92 config.PsrWorkerTypeMetricsName: config.PsrEnv.GetEnv(config.PsrWorkerType), 93 } 94 95 w.metricDescList = metrics.BuildMetricDescList([]*metrics.MetricItem{ 96 &w.openSearchPostSuccessCountTotal, 97 &w.openSearchPostFailureCountTotal, 98 &w.openSearchPostSuccessLatencyNanoSeconds, 99 &w.openSearchPostFailureLatencyNanoSeconds, 100 &w.openSearchPostDataCharsTotal, 101 }, metricsLabels, w.GetWorkerDesc().MetricsPrefix) 102 103 return w, nil 104 } 105 106 // GetWorkerDesc returns the WorkerDesc for the worker 107 func (w worker) GetWorkerDesc() spi.WorkerDesc { 108 return spi.WorkerDesc{ 109 WorkerType: config.WorkerTypeOpsPostLogs, 110 Description: "The postlogs worker performs POST requests on the OpenSearch endpoint", 111 MetricsPrefix: metricsPrefix, 112 } 113 } 114 115 func (w worker) GetEnvDescList() []osenv.EnvVarDesc { 116 return []osenv.EnvVarDesc{ 117 {Key: LogEntries, DefaultVal: "1", Required: false}, 118 {Key: LogLength, DefaultVal: "1", Required: false}, 119 } 120 } 121 122 func (w worker) WantLoopInfoLogged() bool { 123 return false 124 } 125 126 func (w worker) PreconditionsMet() (bool, error) { 127 return true, nil 128 } 129 130 func (w worker) DoWork(conf config.CommonConfig, log vzlog.VerrazzanoLogger) error { 131 c := httpClient 132 logEntries, err := strconv.Atoi(config.PsrEnv.GetEnv(LogEntries)) 133 if err != nil { 134 return err 135 } 136 logLength, err := strconv.Atoi(config.PsrEnv.GetEnv(LogLength)) 137 if err != nil { 138 return err 139 } 140 141 body, bodyChars, err := getBody(logEntries, logLength) 142 if err != nil { 143 return err 144 } 145 146 req := http.Request{ 147 Method: "POST", 148 URL: &url.URL{ 149 Scheme: "http", 150 Host: osIngestService, 151 Path: fmt.Sprintf("/verrazzano-application-%s/_bulk", conf.Namespace), 152 }, 153 Header: http.Header{"Content-Type": {"application/json"}}, 154 Body: body, 155 } 156 157 startRequest := time.Now().UnixNano() 158 resp, err := c.Do(&req) 159 if err != nil { 160 atomic.AddInt64(&w.workerMetrics.openSearchPostFailureCountTotal.Val, 1) 161 return err 162 } 163 if resp == nil { 164 atomic.AddInt64(&w.workerMetrics.openSearchPostFailureCountTotal.Val, 1) 165 return fmt.Errorf("POST request to URI %s received a nil response", req.URL.RequestURI()) 166 } 167 168 if resp.StatusCode != 200 && resp.StatusCode != 201 { 169 atomic.StoreInt64(&w.workerMetrics.openSearchPostFailureLatencyNanoSeconds.Val, time.Now().UnixNano()-startRequest) 170 atomic.AddInt64(&w.workerMetrics.openSearchPostFailureCountTotal.Val, 1) 171 return fmt.Errorf("OpenSearch POST request failed, returned %v status code with status: %s", resp.StatusCode, resp.Status) 172 } 173 atomic.StoreInt64(&w.workerMetrics.openSearchPostSuccessLatencyNanoSeconds.Val, time.Now().UnixNano()-startRequest) 174 atomic.AddInt64(&w.workerMetrics.openSearchPostSuccessCountTotal.Val, 1) 175 atomic.AddInt64(&w.workerMetrics.openSearchPostDataCharsTotal.Val, bodyChars) 176 return nil 177 } 178 179 func (w worker) GetMetricDescList() []prometheus.Desc { 180 return w.metricDescList 181 } 182 183 func (w worker) GetMetricList() []prometheus.Metric { 184 return []prometheus.Metric{ 185 w.openSearchPostSuccessCountTotal.BuildMetric(), 186 w.openSearchPostFailureCountTotal.BuildMetric(), 187 w.openSearchPostSuccessLatencyNanoSeconds.BuildMetric(), 188 w.openSearchPostFailureLatencyNanoSeconds.BuildMetric(), 189 w.openSearchPostDataCharsTotal.BuildMetric(), 190 } 191 } 192 193 func getBody(logCount int, dataLength int) (io.ReadCloser, int64, error) { 194 var body string 195 for i := 0; i < logCount; i++ { 196 data, err := password.GeneratePassword(dataLength) 197 if err != nil { 198 return nil, 0, err 199 } 200 body = body + "{\"create\": {}}\n" + fmt.Sprintf("{\"postlogs-data\":\"%s\",\"@timestamp\":\"%v\"}\n", data, getTimestamp()) 201 } 202 return io.NopCloser(bytes.NewBuffer([]byte(body))), int64(len(body)), nil 203 } 204 205 func getTimestamp() interface{} { 206 return fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02d", 207 time.Now().Year(), 208 int(time.Now().Month()), 209 time.Now().Day(), 210 time.Now().Hour(), 211 time.Now().Minute(), 212 time.Now().Second(), 213 ) 214 }