bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/annotate/backend/elastic2.go (about)

     1  package backend
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"reflect"
     8  	"strings"
     9  	"time"
    10  
    11  	"bosun.org/annotate"
    12  	elastic "gopkg.in/olivere/elastic.v3"
    13  )
    14  
    15  type Elastic2 struct {
    16  	*elastic.Client
    17  	index             string
    18  	urls              []string
    19  	simpleClient      bool
    20  	clientOptionFuncs []elastic.ClientOptionFunc
    21  	maxResults        int
    22  	initialized       bool
    23  }
    24  
    25  func NewElastic2(urls []string, simpleclient bool, index string, clientoptions []elastic.ClientOptionFunc) *Elastic2 {
    26  	return &Elastic2{&elastic.Client{}, index, urls, simpleclient, clientoptions, 200, false}
    27  }
    28  
    29  func (e *Elastic2) GetAnnotations(start, end *time.Time, fieldFilters ...FieldFilter) (annotate.Annotations, error) {
    30  	if !e.initialized {
    31  		return nil, unInitErr
    32  	}
    33  	annotations := annotate.Annotations{}
    34  	filters := []elastic.Query{}
    35  	if start != nil && end != nil {
    36  		startQ := elastic.NewRangeQuery(annotate.EndDate).Gte(start)
    37  		endQ := elastic.NewRangeQuery(annotate.StartDate).Lte(end)
    38  		filters = append(filters, elastic.NewBoolQuery().Must(startQ, endQ))
    39  	}
    40  	for _, filter := range fieldFilters {
    41  		switch filter.Field {
    42  		case annotate.Source, annotate.Host, annotate.CreationUser, annotate.Owner, annotate.Category:
    43  		default:
    44  			return annotations, fmt.Errorf("%v is not a field that can be filtered on", filter.Field)
    45  		}
    46  		var q elastic.Query
    47  		switch filter.Verb {
    48  		case Is, "":
    49  			q = elastic.NewTermQuery(filter.Field, filter.Value)
    50  		case Empty:
    51  			// Can't detect empty on a analyzed field
    52  			if filter.Field == annotate.Message {
    53  				return annotations, fmt.Errorf("message field does not support empty searches")
    54  			}
    55  			q = elastic.NewTermQuery(filter.Field, "")
    56  		default:
    57  			return annotations, fmt.Errorf("%v is not a valid query verb", filter.Verb)
    58  		}
    59  		if filter.Not {
    60  			q = elastic.NewBoolQuery().MustNot(q)
    61  		}
    62  		filters = append(filters, q)
    63  	}
    64  
    65  	var aType annotate.Annotation
    66  	scroll := e.Scroll(e.index).Query(elastic.NewBoolQuery().Must(filters...)).Size(e.maxResults).Pretty(true)
    67  	for {
    68  		res, err := scroll.Do()
    69  		if err == io.EOF {
    70  			break
    71  		}
    72  		if err != nil {
    73  			return annotations, err
    74  		}
    75  		for _, item := range res.Each(reflect.TypeOf(aType)) {
    76  			a := item.(annotate.Annotation)
    77  			annotations = append(annotations, a)
    78  		}
    79  	}
    80  	return annotations, nil
    81  }
    82  
    83  func (e *Elastic2) GetFieldValues(field string) ([]string, error) {
    84  	if !e.initialized {
    85  		return nil, unInitErr
    86  	}
    87  	terms := []string{}
    88  	switch field {
    89  	case annotate.Source, annotate.Host, annotate.CreationUser, annotate.Owner, annotate.Category:
    90  		//continue
    91  	default:
    92  		return terms, fmt.Errorf("invalid field %v", field)
    93  	}
    94  	termsAgg := elastic.NewTermsAggregation().Field(field)
    95  	res, err := e.Search(e.index).Aggregation(field, termsAgg).Size(e.maxResults).Do()
    96  	if err != nil {
    97  		return terms, err
    98  	}
    99  	b, found := res.Aggregations.Terms(field)
   100  	if !found {
   101  		return terms, fmt.Errorf("expected aggregation %v not found in result", field)
   102  	}
   103  	for _, bucket := range b.Buckets {
   104  		if v, ok := bucket.Key.(string); ok {
   105  			terms = append(terms, v)
   106  		}
   107  	}
   108  	return terms, nil
   109  }
   110  
   111  func (e *Elastic2) InitBackend() error {
   112  	var err error
   113  	var ec *elastic.Client
   114  
   115  	if e.simpleClient {
   116  		ec, err = elastic.NewSimpleClient(elastic.SetURL(e.urls...))
   117  	} else if len(e.urls) == 0 {
   118  		ec, err = elastic.NewClient(e.clientOptionFuncs...)
   119  	} else {
   120  		ec, err = elastic.NewClient(elastic.SetURL(e.urls...))
   121  	}
   122  	if err != nil {
   123  		return err
   124  	}
   125  	e.Client = ec
   126  	exists, err := e.IndexExists(e.index).Do()
   127  	if err != nil {
   128  		return err
   129  	}
   130  	if !exists {
   131  		res, err := e.CreateIndex(e.index).Do()
   132  		if (res != nil && !res.Acknowledged) || err != nil {
   133  			return fmt.Errorf("failed to create elastic mapping (ack: %v): %v", res != nil && res.Acknowledged, err)
   134  		}
   135  	}
   136  	stringNA := map[string]string{
   137  		"type":  "string",
   138  		"index": "not_analyzed",
   139  	}
   140  	stringA := map[string]string{
   141  		"type": "string",
   142  	}
   143  	date := map[string]string{
   144  		"type": "date",
   145  	}
   146  	p := make(map[string]interface{})
   147  	p[annotate.Message] = stringA
   148  	p[annotate.StartDate] = date
   149  	p[annotate.EndDate] = date
   150  	p[annotate.Source] = stringNA
   151  	p[annotate.Host] = stringNA
   152  	p[annotate.CreationUser] = stringNA
   153  	p[annotate.Owner] = stringNA
   154  	p[annotate.Category] = stringNA
   155  	p[annotate.Url] = stringNA
   156  	mapping := make(map[string]interface{})
   157  	mapping["properties"] = p
   158  	q := e.PutMapping().Index(e.index).Type(docType).BodyJson(mapping)
   159  	res, err := q.Do()
   160  	if (res != nil && !res.Acknowledged) || err != nil {
   161  		return fmt.Errorf("failed to create elastic mapping (ack: %v): %v", res != nil && res.Acknowledged, err)
   162  	}
   163  	e.initialized = true
   164  	return nil
   165  }
   166  
   167  func (e *Elastic2) InsertAnnotation(a *annotate.Annotation) error {
   168  	if !e.initialized {
   169  		return unInitErr
   170  	}
   171  	_, err := e.Index().Index(e.index).BodyJson(a).Id(a.Id).Type(docType).Do()
   172  	return err
   173  }
   174  
   175  func (e *Elastic2) GetAnnotation(id string) (*annotate.Annotation, bool, error) {
   176  	if !e.initialized {
   177  		return nil, false, unInitErr
   178  	}
   179  	a := annotate.Annotation{}
   180  	if id == "" {
   181  		return &a, false, fmt.Errorf("must provide id")
   182  	}
   183  	res, err := e.Get().Index(e.index).Type(docType).Id(id).Do()
   184  	// Ewwww...
   185  	if err != nil && strings.Contains(err.Error(), "Error 404") {
   186  		return &a, false, nil
   187  	}
   188  	if err != nil {
   189  		return &a, false, err
   190  	}
   191  	if err := json.Unmarshal(*res.Source, &a); err != nil {
   192  		return &a, res.Found, err
   193  	}
   194  	return &a, res.Found, nil
   195  }
   196  
   197  func (e *Elastic2) DeleteAnnotation(id string) error {
   198  	if !e.initialized {
   199  		return unInitErr
   200  	}
   201  	_, err := e.Delete().Index(e.index).Type(docType).Id(id).Do()
   202  	if err != nil {
   203  		return err
   204  	}
   205  	return nil
   206  	//TODO? Check res.Found?
   207  }