github.com/netdata/go.d.plugin@v0.58.1/modules/solr/solr.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package solr
     4  
     5  import (
     6  	_ "embed"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"net/url"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/netdata/go.d.plugin/pkg/web"
    17  
    18  	"github.com/netdata/go.d.plugin/agent/module"
    19  )
    20  
    21  //go:embed "config_schema.json"
    22  var configSchema string
    23  
    24  func init() {
    25  	module.Register("solr", module.Creator{
    26  		JobConfigSchema: configSchema,
    27  		Create:          func() module.Module { return New() },
    28  	})
    29  }
    30  
    31  const (
    32  	defaultURL         = "http://127.0.0.1:8983"
    33  	defaultHTTPTimeout = time.Second
    34  )
    35  
    36  const (
    37  	minSupportedVersion   = 6.4
    38  	coresHandlersURLPath  = "/solr/admin/metrics"
    39  	coresHandlersURLQuery = "group=core&prefix=UPDATE,QUERY&wt=json"
    40  	infoSystemURLPath     = "/solr/admin/info/system"
    41  	infoSystemURLQuery    = "wt=json"
    42  )
    43  
    44  type infoSystem struct {
    45  	Lucene struct {
    46  		Version string `json:"solr-spec-version"`
    47  	}
    48  }
    49  
    50  // New creates Solr with default values
    51  func New() *Solr {
    52  	config := Config{
    53  		HTTP: web.HTTP{
    54  			Request: web.Request{
    55  				URL: defaultURL,
    56  			},
    57  			Client: web.Client{
    58  				Timeout: web.Duration{Duration: defaultHTTPTimeout},
    59  			},
    60  		},
    61  	}
    62  	return &Solr{
    63  		Config: config,
    64  		cores:  make(map[string]bool),
    65  	}
    66  }
    67  
    68  // Config is the Solr module configuration.
    69  type Config struct {
    70  	web.HTTP `yaml:",inline"`
    71  }
    72  
    73  // Solr solr module
    74  type Solr struct {
    75  	module.Base
    76  	Config `yaml:",inline"`
    77  
    78  	cores   map[string]bool
    79  	client  *http.Client
    80  	version float64
    81  	charts  *Charts
    82  }
    83  
    84  func (s *Solr) doRequest(req *http.Request) (*http.Response, error) {
    85  	return s.client.Do(req)
    86  }
    87  
    88  // Cleanup makes cleanup
    89  func (Solr) Cleanup() {}
    90  
    91  // Init makes initialization
    92  func (s *Solr) Init() bool {
    93  	if s.URL == "" {
    94  		s.Error("URL not set")
    95  		return false
    96  	}
    97  
    98  	client, err := web.NewHTTPClient(s.Client)
    99  	if err != nil {
   100  		s.Error(err)
   101  		return false
   102  	}
   103  
   104  	s.client = client
   105  	return true
   106  }
   107  
   108  // Check makes check
   109  func (s *Solr) Check() bool {
   110  	if err := s.getVersion(); err != nil {
   111  		s.Error(err)
   112  		return false
   113  	}
   114  
   115  	if s.version < minSupportedVersion {
   116  		s.Errorf("unsupported Solr version : %.1f", s.version)
   117  		return false
   118  	}
   119  
   120  	return true
   121  }
   122  
   123  // Charts creates Charts
   124  func (s *Solr) Charts() *Charts {
   125  	s.charts = &Charts{}
   126  
   127  	return s.charts
   128  }
   129  
   130  // Collect collects metrics
   131  func (s *Solr) Collect() map[string]int64 {
   132  	req, err := createRequest(s.Request, coresHandlersURLPath, coresHandlersURLQuery)
   133  	if err != nil {
   134  		s.Errorf("error on creating http request : %v", err)
   135  		return nil
   136  	}
   137  
   138  	resp, err := s.doRequest(req)
   139  	if err != nil {
   140  		s.Errorf("error on request to %s : %s", req.URL, err)
   141  		return nil
   142  	}
   143  	defer closeBody(resp)
   144  
   145  	if resp.StatusCode != http.StatusOK {
   146  		s.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
   147  		return nil
   148  	}
   149  
   150  	metrics, err := s.parse(resp)
   151  	if err != nil {
   152  		s.Errorf("error on parse response from %s : %s", req.URL, err)
   153  		return nil
   154  	}
   155  
   156  	return metrics
   157  }
   158  
   159  func (s *Solr) getVersion() error {
   160  	req, err := createRequest(s.Request, infoSystemURLPath, infoSystemURLQuery)
   161  	if err != nil {
   162  		return fmt.Errorf("error on creating http request : %v", err)
   163  	}
   164  
   165  	resp, err := s.doRequest(req)
   166  	if err != nil {
   167  		return fmt.Errorf("error on request to %s : %s", req.URL, err)
   168  	}
   169  	defer closeBody(resp)
   170  
   171  	if resp.StatusCode != http.StatusOK {
   172  		return fmt.Errorf("%s returned HTTP status %d", req.URL, resp.StatusCode)
   173  	}
   174  
   175  	var info infoSystem
   176  
   177  	if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
   178  		return fmt.Errorf("error on decode response from %s : %s", req.URL, err)
   179  	}
   180  
   181  	var idx int
   182  
   183  	if idx = strings.LastIndex(info.Lucene.Version, "."); idx == -1 {
   184  		return fmt.Errorf("error on parsing version '%s': bad format", info.Lucene.Version)
   185  	}
   186  
   187  	if s.version, err = strconv.ParseFloat(info.Lucene.Version[:idx], 64); err != nil {
   188  		return fmt.Errorf("error on parsing version '%s' :  %s", info.Lucene.Version, err)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func createRequest(req web.Request, urlPath, urlQuery string) (*http.Request, error) {
   195  	r := req.Copy()
   196  	u, err := url.Parse(r.URL)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	u.Path = urlPath
   202  	u.RawQuery = urlQuery
   203  	r.URL = u.String()
   204  	return web.NewHTTPRequest(r)
   205  }
   206  
   207  func closeBody(resp *http.Response) {
   208  	if resp != nil && resp.Body != nil {
   209  		_, _ = io.Copy(io.Discard, resp.Body)
   210  		_ = resp.Body.Close()
   211  	}
   212  }