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

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