[fix] fix cyclic silence time matching and cross-midnight handling (#4081)

Co-authored-by: yunfan24 <yunfan24@outlook.com>
Co-authored-by: Tomsun28 <tomsun28@outlook.com>
This commit is contained in:
shown
2026-03-22 22:05:27 +08:00
committed by GitHub
co-authored by yunfan24 Tomsun28
parent b9e07d81f9
commit a4d2630320
2 changed files with 112 additions and 4 deletions
@@ -18,6 +18,7 @@
package org.apache.hertzbeat.alert.reduce;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -95,10 +96,35 @@ public class AlarmSilenceReduce {
* @return true if alert should not be silenced, false if alert should be silenced
*/
private boolean checkAndSave(LocalDateTime now, AlertSilence alertSilence) {
boolean startMatch = alertSilence.getPeriodStart() == null
|| now.isAfter(alertSilence.getPeriodStart().toLocalDateTime());
boolean endMatch = alertSilence.getPeriodEnd() == null
|| now.isBefore(alertSilence.getPeriodEnd().toLocalDateTime());
boolean startMatch;
boolean endMatch;
if (alertSilence.getType() == 1) {
LocalTime nowTime = now.toLocalTime();
LocalTime startTime = alertSilence.getPeriodStart() == null ? null : alertSilence.getPeriodStart().toLocalTime();
LocalTime endTime = alertSilence.getPeriodEnd() == null ? null : alertSilence.getPeriodEnd().toLocalTime();
if (startTime == null && endTime == null) {
startMatch = true;
endMatch = true;
} else if (startTime == null) {
startMatch = true;
endMatch = !nowTime.isAfter(endTime);
} else if (endTime == null) {
startMatch = !nowTime.isBefore(startTime);
endMatch = true;
} else if (!startTime.isAfter(endTime)) {
startMatch = !nowTime.isBefore(startTime);
endMatch = !nowTime.isAfter(endTime);
} else {
// Cross-midnight window, e.g. 23:00-02:00.
startMatch = !nowTime.isBefore(startTime) || !nowTime.isAfter(endTime);
endMatch = true;
}
} else {
startMatch = alertSilence.getPeriodStart() == null
|| now.isAfter(alertSilence.getPeriodStart().toLocalDateTime());
endMatch = alertSilence.getPeriodEnd() == null
|| now.isBefore(alertSilence.getPeriodEnd().toLocalDateTime());
}
if (startMatch && endMatch) {
int time = Optional.ofNullable(alertSilence.getTimes()).orElse(0);
@@ -119,6 +119,88 @@ class AlarmSilenceReduceTest {
verify(alertSilenceDao).save(silenceRule);
}
@Test
void whenCyclicSilenceRuleTimeMatchesButDateDifferent_shouldNotForwardAlert() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime yesterday = now.minusDays(1);
LocalDateTime start = yesterday.withHour(now.getHour())
.withMinute(Math.max(0, now.getMinute() - 10))
.withSecond(0)
.withNano(0);
LocalDateTime end = yesterday.withHour(now.getHour())
.withMinute(Math.min(59, now.getMinute() + 10))
.withSecond(59)
.withNano(0);
AlertSilence silenceRule = AlertSilence.builder()
.enable(true)
.matchAll(false)
.type((byte) 1)
.labels(createLabels("service", "web"))
.periodStart(start.atZone(ZoneId.systemDefault()))
.periodEnd(end.atZone(ZoneId.systemDefault()))
.days(Collections.singletonList((byte) now.getDayOfWeek().getValue()))
.times(0)
.build();
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
alarmSilenceReduce.silenceAlarm(alert);
verify(alertNoticeDispatch, never()).dispatchAlarm(alert);
verify(alertSilenceDao).save(silenceRule);
}
@Test
void whenMatchingCrossMidnightCyclicSilenceRule_shouldNotForwardAlert() {
LocalDateTime now = LocalDateTime.now();
AlertSilence silenceRule = AlertSilence.builder()
.enable(true)
.matchAll(false)
.type((byte) 1)
.labels(createLabels("service", "web"))
.periodStart(now.minusHours(1).atZone(ZoneId.systemDefault()))
.periodEnd(now.minusHours(2).atZone(ZoneId.systemDefault()))
.days(Collections.singletonList((byte) now.getDayOfWeek().getValue()))
.times(0)
.build();
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
when(alertSilenceDao.save(any(AlertSilence.class))).thenReturn(silenceRule);
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
alarmSilenceReduce.silenceAlarm(alert);
verify(alertNoticeDispatch, never()).dispatchAlarm(alert);
verify(alertSilenceDao).save(silenceRule);
}
@Test
void whenCrossMidnightCyclicSilenceRuleDoesNotIncludeCurrentDay_shouldForwardAlert() {
LocalDateTime now = LocalDateTime.now();
byte previousDay = (byte) now.minusDays(1).getDayOfWeek().getValue();
AlertSilence silenceRule = AlertSilence.builder()
.enable(true)
.matchAll(false)
.type((byte) 1)
.labels(createLabels("service", "web"))
.periodStart(now.minusHours(1).atZone(ZoneId.systemDefault()))
.periodEnd(now.minusHours(2).atZone(ZoneId.systemDefault()))
.days(Collections.singletonList(previousDay))
.times(0)
.build();
when(alertSilenceDao.findAlertSilencesByEnableTrue()).thenReturn(Collections.singletonList(silenceRule));
GroupAlert alert = createGroupAlert("firing", createLabels("service", "web"));
alarmSilenceReduce.silenceAlarm(alert);
verify(alertNoticeDispatch).dispatchAlarm(alert);
verify(alertSilenceDao, never()).save(any());
}
@Test
void whenSilenceRuleExpired_shouldForwardAlert() {
AlertSilence silenceRule = AlertSilence.builder()