bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/sched/views.go (about)

     1  package sched
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	"bosun.org/cmd/bosun/conf"
     9  	"bosun.org/models"
    10  	"bosun.org/opentsdb"
    11  	"github.com/ryanuber/go-glob"
    12  )
    13  
    14  // Views
    15  
    16  type EventSummary struct {
    17  	Status models.Status
    18  	Time   int64
    19  }
    20  
    21  // EventSummary is like a models.Event but strips the Results and Unevaluated
    22  func MakeEventSummary(e models.Event) (EventSummary, bool) {
    23  	return EventSummary{
    24  		Status: e.Status,
    25  		Time:   e.Time.Unix(),
    26  	}, e.Unevaluated
    27  }
    28  
    29  type EpochAction struct {
    30  	User    string
    31  	Message string
    32  	Time    int64
    33  	Type    models.ActionType
    34  }
    35  
    36  func MakeEpochAction(a models.Action) EpochAction {
    37  	return EpochAction{
    38  		User:    a.User,
    39  		Message: a.Message,
    40  		Time:    a.Time.UTC().Unix(),
    41  		Type:    a.Type,
    42  	}
    43  }
    44  
    45  type IncidentSummaryView struct {
    46  	Id                     int64
    47  	Subject                string
    48  	Start                  int64
    49  	AlertName              string
    50  	Tags                   opentsdb.TagSet
    51  	TagsString             string
    52  	CurrentStatus          models.Status
    53  	WorstStatus            models.Status
    54  	LastAbnormalStatus     models.Status
    55  	LastAbnormalTime       models.Epoch
    56  	Unevaluated            bool
    57  	NeedAck                bool
    58  	Silenced               bool
    59  	Actions                []EpochAction
    60  	Events                 []EventSummary
    61  	WarnNotificationChains [][]string
    62  	CritNotificationChains [][]string
    63  	LastStatusTime         int64
    64  }
    65  
    66  func MakeIncidentSummary(c conf.RuleConfProvider, s SilenceTester, is *models.IncidentState) (*IncidentSummaryView, error) {
    67  	alert := c.GetAlert(is.AlertKey.Name())
    68  	if alert == nil {
    69  		return nil, fmt.Errorf("alert %v does not exist in the configuration", is.AlertKey.Name())
    70  	}
    71  	warnNotifications := alert.WarnNotification.Get(c, is.AlertKey.Group())
    72  	critNotifications := alert.CritNotification.Get(c, is.AlertKey.Group())
    73  	eventSummaries := []EventSummary{}
    74  	nonNormalNonUnknownCount := 0
    75  	for _, event := range is.Events {
    76  		if event.Status > models.StNormal && event.Status < models.StUnknown {
    77  			nonNormalNonUnknownCount++
    78  		}
    79  		if eventSummary, unevaluated := MakeEventSummary(event); !unevaluated {
    80  			eventSummaries = append(eventSummaries, eventSummary)
    81  		}
    82  	}
    83  	actions := make([]EpochAction, len(is.Actions))
    84  	for i, action := range is.Actions {
    85  		actions[i] = MakeEpochAction(action)
    86  	}
    87  	subject := is.Subject
    88  	// There is no rendered subject when the state is unknown and
    89  	// there is no other non-normal status in the history.
    90  	if subject == "" && nonNormalNonUnknownCount == 0 {
    91  		subject = fmt.Sprintf("%s: %v", is.CurrentStatus, is.AlertKey)
    92  	}
    93  	return &IncidentSummaryView{
    94  		Id:                     is.Id,
    95  		Subject:                subject,
    96  		Start:                  is.Start.Unix(),
    97  		AlertName:              is.AlertKey.Name(),
    98  		Tags:                   is.AlertKey.Group(),
    99  		TagsString:             is.AlertKey.Group().String(),
   100  		CurrentStatus:          is.CurrentStatus,
   101  		WorstStatus:            is.WorstStatus,
   102  		LastAbnormalStatus:     is.LastAbnormalStatus,
   103  		LastAbnormalTime:       is.LastAbnormalTime,
   104  		Unevaluated:            is.Unevaluated,
   105  		NeedAck:                is.NeedAck,
   106  		Silenced:               s(is.AlertKey) != nil,
   107  		Actions:                actions,
   108  		Events:                 eventSummaries,
   109  		WarnNotificationChains: conf.GetNotificationChains(warnNotifications),
   110  		CritNotificationChains: conf.GetNotificationChains(critNotifications),
   111  		LastStatusTime:         is.Last().Time.Unix(),
   112  	}, nil
   113  }
   114  
   115  func (is IncidentSummaryView) Ask(filter string) (bool, error) {
   116  	sp := strings.SplitN(filter, ":", 2)
   117  	if len(sp) != 2 {
   118  		return false, fmt.Errorf("bad filter, filter must be in k:v format, got %v", filter)
   119  	}
   120  	key := sp[0]
   121  	value := sp[1]
   122  	switch key {
   123  	case "ack":
   124  		switch value {
   125  		case "true":
   126  			return is.NeedAck == false, nil
   127  		case "false":
   128  			return is.NeedAck == true, nil
   129  		default:
   130  			return false, fmt.Errorf("unknown %s value: %s", key, value)
   131  		}
   132  	case "ackTime":
   133  		if is.NeedAck == true {
   134  			return false, nil
   135  		}
   136  		for _, action := range is.Actions {
   137  			if action.Type == models.ActionAcknowledge {
   138  				return checkTimeArg(action.Time, value)
   139  			}
   140  		}
   141  		// In case an incident does not need ack but have no ack event
   142  		// I found one case of this from bosun autoclosing, but it was
   143  		// a very old incident. Don't think we should end up here, but
   144  		// if we do better to show the ack'd incident than hide it.
   145  		return true, nil
   146  	case "hasTag":
   147  		if strings.Contains(value, "=") {
   148  			if strings.HasPrefix(value, "=") {
   149  				q := strings.TrimPrefix(value, "=")
   150  				for _, v := range is.Tags {
   151  					if glob.Glob(q, v) {
   152  						return true, nil
   153  					}
   154  				}
   155  				return false, nil
   156  			}
   157  			if strings.HasSuffix(value, "=") {
   158  				q := strings.TrimSuffix(value, "=")
   159  				_, ok := is.Tags[q]
   160  				return ok, nil
   161  			}
   162  			sp := strings.Split(value, "=")
   163  			if len(sp) != 2 {
   164  				return false, fmt.Errorf("unexpected tag specification: %v", value)
   165  			}
   166  			tagValues := strings.Split(sp[1], "|")
   167  			for k, v := range is.Tags {
   168  				for _, tagValue := range tagValues {
   169  					if k == sp[0] && glob.Glob(tagValue, v) {
   170  						return true, nil
   171  					}
   172  				}
   173  			}
   174  			return false, nil
   175  		}
   176  		q := strings.TrimRight(value, "=")
   177  		_, ok := is.Tags[q]
   178  		return ok, nil
   179  	case "hidden":
   180  		hide := is.Silenced || is.Unevaluated
   181  		switch value {
   182  		case "true":
   183  			return hide == true, nil
   184  		case "false":
   185  			return hide == false, nil
   186  		default:
   187  			return false, fmt.Errorf("unknown %s value: %s", key, value)
   188  		}
   189  	case "name":
   190  		return glob.Glob(value, is.AlertName), nil
   191  	case "user":
   192  		for _, action := range is.Actions {
   193  			if glob.Glob(value, action.User) {
   194  				return true, nil
   195  			}
   196  		}
   197  		return false, nil
   198  	case "notify":
   199  		for _, chain := range is.WarnNotificationChains {
   200  			for _, wn := range chain {
   201  				if glob.Glob(value, wn) {
   202  					return true, nil
   203  				}
   204  			}
   205  		}
   206  		for _, chain := range is.CritNotificationChains {
   207  			for _, cn := range chain {
   208  				if glob.Glob(value, cn) {
   209  					return true, nil
   210  				}
   211  			}
   212  		}
   213  		return false, nil
   214  	case "silenced":
   215  		switch value {
   216  		case "true":
   217  			return is.Silenced == true, nil
   218  		case "false":
   219  			return is.Silenced == false, nil
   220  		default:
   221  			return false, fmt.Errorf("unknown %s value: %s", key, value)
   222  		}
   223  	case "start":
   224  		return checkTimeArg(is.Start, value)
   225  	case "unevaluated":
   226  		switch value {
   227  		case "true":
   228  			return is.Unevaluated == true, nil
   229  		case "false":
   230  			return is.Unevaluated == false, nil
   231  		default:
   232  			return false, fmt.Errorf("unknown %s value: %s", key, value)
   233  		}
   234  	case "status": // CurrentStatus
   235  		return is.CurrentStatus.String() == value, nil
   236  	case "worstStatus":
   237  		return is.WorstStatus.String() == value, nil
   238  	case "lastAbnormalStatus":
   239  		return is.LastAbnormalStatus.String() == value, nil
   240  	case "subject":
   241  		return glob.Glob(value, is.Subject), nil
   242  	case "since":
   243  		return checkTimeArg(is.LastStatusTime, value)
   244  	}
   245  	return false, nil
   246  }
   247  
   248  func checkTimeArg(ts int64, arg string) (bool, error) {
   249  	var op string
   250  	val := arg
   251  	if strings.HasPrefix(arg, "<") {
   252  		op = "<"
   253  		val = strings.TrimLeft(arg, op)
   254  	}
   255  	if strings.HasPrefix(arg, ">") {
   256  		op = ">"
   257  		val = strings.TrimLeft(arg, op)
   258  	}
   259  	d, err := opentsdb.ParseDuration(val)
   260  	if err != nil {
   261  		return false, err
   262  	}
   263  	startTime := time.Unix(ts, 0)
   264  	// might want to make Now a property of incident summary for viewing things in the past
   265  	// but not going there at the moment. This is because right now I'm working with open
   266  	// incidents. And "What did incidents look like at this time?" is a different question
   267  	// since those incidents will no longer be open.
   268  	relativeTime := time.Now().UTC().Add(time.Duration(-d))
   269  	switch op {
   270  	case ">", "":
   271  		return startTime.After(relativeTime), nil
   272  	case "<":
   273  		return startTime.Before(relativeTime), nil
   274  	default:
   275  		return false, fmt.Errorf("unexpected op: %v", op)
   276  	}
   277  }