1package ualert 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "math/rand" 9 "os" 10 "path/filepath" 11 "strconv" 12 "time" 13 14 "github.com/gofrs/uuid" 15 "github.com/matttproud/golang_protobuf_extensions/pbutil" 16 pb "github.com/prometheus/alertmanager/silence/silencepb" 17 "github.com/prometheus/common/model" 18 19 "github.com/grafana/grafana/pkg/services/sqlstore/migrator" 20) 21 22const ( 23 // Should be the same as 'NoDataAlertName' in pkg/services/schedule/compat.go. 24 NoDataAlertName = "DatasourceNoData" 25 26 ErrorAlertName = "DatasourceError" 27) 28 29func (m *migration) addSilence(da dashAlert, rule *alertRule) error { 30 if da.State != "paused" { 31 return nil 32 } 33 34 uid, err := uuid.NewV4() 35 if err != nil { 36 return errors.New("failed to create uuid for silence") 37 } 38 39 n, v := getLabelForRouteMatching(rule.UID) 40 s := &pb.MeshSilence{ 41 Silence: &pb.Silence{ 42 Id: uid.String(), 43 Matchers: []*pb.Matcher{ 44 { 45 Type: pb.Matcher_EQUAL, 46 Name: n, 47 Pattern: v, 48 }, 49 }, 50 StartsAt: time.Now(), 51 EndsAt: time.Now().Add(365 * 20 * time.Hour), // 1 year. 52 CreatedBy: "Grafana Migration", 53 Comment: "Created during auto migration to unified alerting", 54 }, 55 ExpiresAt: time.Now().Add(365 * 20 * time.Hour), // 1 year. 56 } 57 58 _, ok := m.silences[da.OrgId] 59 if !ok { 60 m.silences[da.OrgId] = make([]*pb.MeshSilence, 0) 61 } 62 m.silences[da.OrgId] = append(m.silences[da.OrgId], s) 63 return nil 64} 65 66func (m *migration) addErrorSilence(da dashAlert, rule *alertRule) error { 67 if da.ParsedSettings.ExecutionErrorState != "keep_state" { 68 return nil 69 } 70 71 uid, err := uuid.NewV4() 72 if err != nil { 73 return errors.New("failed to create uuid for silence") 74 } 75 76 s := &pb.MeshSilence{ 77 Silence: &pb.Silence{ 78 Id: uid.String(), 79 Matchers: []*pb.Matcher{ 80 { 81 Type: pb.Matcher_EQUAL, 82 Name: model.AlertNameLabel, 83 Pattern: ErrorAlertName, 84 }, 85 { 86 Type: pb.Matcher_EQUAL, 87 Name: "rule_uid", 88 Pattern: rule.UID, 89 }, 90 }, 91 StartsAt: time.Now(), 92 EndsAt: time.Now().AddDate(1, 0, 0), // 1 year 93 CreatedBy: "Grafana Migration", 94 Comment: fmt.Sprintf("Created during migration to unified alerting to silence Error state for alert rule ID '%s' and Title '%s' because the option 'Keep Last State' was selected for Error state", rule.UID, rule.Title), 95 }, 96 ExpiresAt: time.Now().AddDate(1, 0, 0), // 1 year 97 } 98 if _, ok := m.silences[da.OrgId]; !ok { 99 m.silences[da.OrgId] = make([]*pb.MeshSilence, 0) 100 } 101 m.silences[da.OrgId] = append(m.silences[da.OrgId], s) 102 return nil 103} 104 105func (m *migration) addNoDataSilence(da dashAlert, rule *alertRule) error { 106 if da.ParsedSettings.NoDataState != "keep_state" { 107 return nil 108 } 109 110 uid, err := uuid.NewV4() 111 if err != nil { 112 return errors.New("failed to create uuid for silence") 113 } 114 115 s := &pb.MeshSilence{ 116 Silence: &pb.Silence{ 117 Id: uid.String(), 118 Matchers: []*pb.Matcher{ 119 { 120 Type: pb.Matcher_EQUAL, 121 Name: model.AlertNameLabel, 122 Pattern: NoDataAlertName, 123 }, 124 { 125 Type: pb.Matcher_EQUAL, 126 Name: "rule_uid", 127 Pattern: rule.UID, 128 }, 129 }, 130 StartsAt: time.Now(), 131 EndsAt: time.Now().AddDate(1, 0, 0), // 1 year. 132 CreatedBy: "Grafana Migration", 133 Comment: fmt.Sprintf("Created during migration to unified alerting to silence NoData state for alert rule ID '%s' and Title '%s' because the option 'Keep Last State' was selected for NoData state", rule.UID, rule.Title), 134 }, 135 ExpiresAt: time.Now().AddDate(1, 0, 0), // 1 year. 136 } 137 _, ok := m.silences[da.OrgId] 138 if !ok { 139 m.silences[da.OrgId] = make([]*pb.MeshSilence, 0) 140 } 141 m.silences[da.OrgId] = append(m.silences[da.OrgId], s) 142 return nil 143} 144 145func (m *migration) writeSilencesFile(orgID int64) error { 146 var buf bytes.Buffer 147 orgSilences, ok := m.silences[orgID] 148 if !ok { 149 return nil 150 } 151 152 for _, e := range orgSilences { 153 if _, err := pbutil.WriteDelimited(&buf, e); err != nil { 154 return err 155 } 156 } 157 158 f, err := openReplace(silencesFileNameForOrg(m.mg, orgID)) 159 if err != nil { 160 return err 161 } 162 163 if _, err := io.Copy(f, bytes.NewReader(buf.Bytes())); err != nil { 164 return err 165 } 166 167 return f.Close() 168} 169 170func getSilenceFileNamesForAllOrgs(mg *migrator.Migrator) ([]string, error) { 171 return filepath.Glob(filepath.Join(mg.Cfg.DataPath, "alerting", "*", "silences")) 172} 173 174func silencesFileNameForOrg(mg *migrator.Migrator, orgID int64) string { 175 return filepath.Join(mg.Cfg.DataPath, "alerting", strconv.Itoa(int(orgID)), "silences") 176} 177 178// replaceFile wraps a file that is moved to another filename on closing. 179type replaceFile struct { 180 *os.File 181 filename string 182} 183 184func (f *replaceFile) Close() error { 185 if err := f.File.Sync(); err != nil { 186 return err 187 } 188 if err := f.File.Close(); err != nil { 189 return err 190 } 191 return os.Rename(f.File.Name(), f.filename) 192} 193 194// openReplace opens a new temporary file that is moved to filename on closing. 195func openReplace(filename string) (*replaceFile, error) { 196 tmpFilename := fmt.Sprintf("%s.%x", filename, uint64(rand.Int63())) 197 198 if err := os.MkdirAll(filepath.Dir(tmpFilename), os.ModePerm); err != nil { 199 return nil, err 200 } 201 202 f, err := os.Create(tmpFilename) 203 if err != nil { 204 return nil, err 205 } 206 207 rf := &replaceFile{ 208 File: f, 209 filename: filename, 210 } 211 return rf, nil 212} 213