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 }