bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/database/notification_data.go (about)

     1  package database
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/garyburd/redigo/redis"
     9  
    10  	"bosun.org/models"
    11  	"bosun.org/slog"
    12  )
    13  
    14  /*
    15  
    16  pendingNotifications: ZSET timestamp ak:notification
    17  
    18  notsByAlert:alert SET of notifications possible per alert. used to clear alerts by alert key
    19  
    20  */
    21  
    22  const (
    23  	pendingNotificationsKey = "pendingNotifications"
    24  )
    25  
    26  func notsByAlertKeyKey(ak models.AlertKey) string {
    27  	return fmt.Sprintf("notsByAlert:%s", ak.Name())
    28  }
    29  
    30  type NotificationDataAccess interface {
    31  	InsertNotification(ak models.AlertKey, notification string, dueAt time.Time) error
    32  
    33  	//Get notifications that are currently due or past due. Does not delete.
    34  	GetDueNotifications() (map[models.AlertKey]map[string]time.Time, error)
    35  
    36  	//Clear all notifications due on or before a given timestamp. Intended is to use the max returned from GetDueNotifications once you have processed them.
    37  	ClearNotificationsBefore(time.Time) error
    38  
    39  	ClearNotifications(ak models.AlertKey) error
    40  
    41  	GetNextNotificationTime() (time.Time, error)
    42  }
    43  
    44  func (d *dataAccess) Notifications() NotificationDataAccess {
    45  	return d
    46  }
    47  
    48  func (d *dataAccess) InsertNotification(ak models.AlertKey, notification string, dueAt time.Time) error {
    49  	conn := d.Get()
    50  	defer conn.Close()
    51  
    52  	_, err := conn.Do("ZADD", pendingNotificationsKey, dueAt.UTC().Unix(), fmt.Sprintf("%s:%s", ak, notification))
    53  	if err != nil {
    54  		return slog.Wrap(err)
    55  	}
    56  	_, err = conn.Do("SADD", notsByAlertKeyKey(ak), notification)
    57  	return slog.Wrap(err)
    58  }
    59  
    60  func (d *dataAccess) GetDueNotifications() (map[models.AlertKey]map[string]time.Time, error) {
    61  	conn := d.Get()
    62  	defer conn.Close()
    63  	m, err := redis.Int64Map(conn.Do("ZRANGEBYSCORE", pendingNotificationsKey, 0, time.Now().UTC().Unix(), "WITHSCORES"))
    64  	if err != nil {
    65  		return nil, slog.Wrap(err)
    66  	}
    67  	results := map[models.AlertKey]map[string]time.Time{}
    68  	for key, t := range m {
    69  		last := strings.LastIndex(key, ":")
    70  		if last == -1 {
    71  			continue
    72  		}
    73  		ak, not := models.AlertKey(key[:last]), key[last+1:]
    74  		if results[ak] == nil {
    75  			results[ak] = map[string]time.Time{}
    76  		}
    77  		results[ak][not] = time.Unix(t, 0).UTC()
    78  	}
    79  	return results, err
    80  }
    81  
    82  func (d *dataAccess) ClearNotificationsBefore(t time.Time) error {
    83  	conn := d.Get()
    84  	defer conn.Close()
    85  
    86  	_, err := conn.Do("ZREMRANGEBYSCORE", pendingNotificationsKey, 0, t.UTC().Unix())
    87  	return slog.Wrap(err)
    88  }
    89  
    90  func (d *dataAccess) ClearNotifications(ak models.AlertKey) error {
    91  	conn := d.Get()
    92  	defer conn.Close()
    93  
    94  	nots, err := redis.Strings(conn.Do("SMEMBERS", notsByAlertKeyKey(ak)))
    95  	if err != nil {
    96  		return slog.Wrap(err)
    97  	}
    98  
    99  	if len(nots) == 0 {
   100  		return nil
   101  	}
   102  
   103  	args := []interface{}{pendingNotificationsKey}
   104  	for _, not := range nots {
   105  		key := fmt.Sprintf("%s:%s", ak, not)
   106  		args = append(args, key)
   107  	}
   108  	_, err = conn.Do("ZREM", args...)
   109  	return slog.Wrap(err)
   110  }
   111  
   112  func (d *dataAccess) GetNextNotificationTime() (time.Time, error) {
   113  	conn := d.Get()
   114  	defer conn.Close()
   115  
   116  	m, err := redis.Int64Map(conn.Do("ZRANGE", pendingNotificationsKey, 0, 0, "WITHSCORES"))
   117  	if err != nil {
   118  		return time.Time{}, slog.Wrap(err)
   119  	}
   120  	// default time is one hour from now if no pending notifications exist
   121  	t := time.Now().UTC().Add(time.Hour).Truncate(time.Second)
   122  	for _, i := range m {
   123  		t = time.Unix(i, 0).UTC()
   124  	}
   125  	return t, nil
   126  }