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  }