This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git
The following commit(s) were added to refs/heads/master by this push:
new ec5564b54f [fix] pre-validate monitor import batch to avoid orphan
tasks and empty list (#4180)
ec5564b54f is described below
commit ec5564b54f77644ceb2a5f15d9696fb1e5702211
Author: Duansg <[email protected]>
AuthorDate: Mon Jun 29 09:54:43 2026 +0800
[fix] pre-validate monitor import batch to avoid orphan tasks and empty
list (#4180)
Co-authored-by: aias00 <[email protected]>
---
.../common/constants/ImExportTaskConstant.java | 5 +-
.../service/impl/AbstractImExportServiceImpl.java | 28 ++++++++--
.../manager/service/ExcelImExportServiceTest.java | 63 ++++++++++++++++++++++
3 files changed, 89 insertions(+), 7 deletions(-)
diff --git
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/constants/ImExportTaskConstant.java
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/constants/ImExportTaskConstant.java
index 31256dfed7..4d8fb53b97 100644
---
a/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/constants/ImExportTaskConstant.java
+++
b/hertzbeat-common-core/src/main/java/org/apache/hertzbeat/common/constants/ImExportTaskConstant.java
@@ -24,7 +24,8 @@ package org.apache.hertzbeat.common.constants;
public interface ImExportTaskConstant {
/**
- * If the number of tasks exceeds 100, progress information will broadcast
+ * If the number of tasks reaches this threshold, progress information
will broadcast.
+ * Kept low so medium-sized imports (e.g. dozens of monitors) still report
incremental progress.
*/
- Integer IMPORT_TASK_PROCESS_THRESHOLD = 100;
+ Integer IMPORT_TASK_PROCESS_THRESHOLD = 10;
}
diff --git
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
index 73115896f9..901c5132a0 100644
---
a/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
+++
b/hertzbeat-manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AbstractImExportServiceImpl.java
@@ -31,7 +31,6 @@ import org.apache.hertzbeat.manager.config.ManagerSseManager;
import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.ImExportService;
import org.apache.hertzbeat.manager.service.MonitorService;
-import org.apache.hertzbeat.base.service.LabelService;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;
@@ -40,9 +39,11 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* class AbstractImExportServiceImpl
@@ -54,9 +55,6 @@ public abstract class AbstractImExportServiceImpl implements
ImExportService {
@Lazy
private MonitorService monitorService;
- @Resource
- private LabelService tagService;
-
@Resource
private ManagerSseManager managerSseManager;
@@ -65,10 +63,10 @@ public abstract class AbstractImExportServiceImpl
implements ImExportService {
var formList = parseImport(is).stream().map(this::convert).toList();
if (!CollectionUtils.isEmpty(formList)) {
int totalElements = formList.size();
+ validateImportBatch(formList);
int progressInterval = Math.max(1, totalElements / 10);
for (int i = 0; i < totalElements; i++) {
MonitorDto monitorDto = formList.get(i);
- monitorService.validate(monitorDto, false);
monitorService.addMonitor(monitorDto.getMonitor(),
monitorDto.getParams(), monitorDto.getCollector(),
monitorDto.getGrafanaDashboard());
if (totalElements >=
ImExportTaskConstant.IMPORT_TASK_PROCESS_THRESHOLD && ((i + 1) %
progressInterval == 0) && (i + 1 < totalElements)) {
managerSseManager.broadcastImportTaskInProgress(taskName,
(int) ((i + 1) * 100.0 / totalElements));
@@ -78,6 +76,26 @@ public abstract class AbstractImExportServiceImpl implements
ImExportService {
}
}
+ /**
+ * Validate every entry of an import batch before any monitor is persisted.
+ * Beyond the per-entry checks (params/collector/name uniqueness against
the DB), this also detects
+ * duplicate monitor names within the import file itself, which the
single-entry validation cannot
+ * catch because nothing has been persisted yet when the conflicting rows
are validated.
+ *
+ * @param formList parsed monitors to import
+ * @throws IllegalArgumentException if any entry is invalid or names
collide within the file
+ */
+ private void validateImportBatch(List<MonitorDto> formList) {
+ Set<String> names = new HashSet<>(formList.size());
+ for (MonitorDto monitorDto : formList) {
+ monitorService.validate(monitorDto, false);
+ String name = monitorDto.getMonitorInfo().getName();
+ if (!names.add(name)) {
+ throw new IllegalArgumentException("Duplicate monitor name in
import file: " + name);
+ }
+ }
+ }
+
@Override
public void exportConfig(OutputStream os, List<Long> configList) {
var monitorList = configList.stream().map(it ->
monitorService.getMonitorDto(it)).filter(Objects::nonNull).map(this::convert).toList();
diff --git
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ExcelImExportServiceTest.java
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ExcelImExportServiceTest.java
index ac83520077..f87adfe46b 100644
---
a/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ExcelImExportServiceTest.java
+++
b/hertzbeat-manager/src/test/java/org/apache/hertzbeat/manager/service/ExcelImExportServiceTest.java
@@ -19,11 +19,20 @@ package org.apache.hertzbeat.manager.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
+import org.apache.hertzbeat.manager.config.ManagerSseManager;
+import org.apache.hertzbeat.manager.pojo.dto.MonitorDto;
import org.apache.hertzbeat.manager.service.impl.AbstractImExportServiceImpl;
import org.apache.hertzbeat.manager.service.impl.ExcelImExportServiceImpl;
import org.apache.poi.ss.usermodel.Workbook;
@@ -32,6 +41,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
@@ -43,6 +53,12 @@ class ExcelImExportServiceTest {
@InjectMocks
private ExcelImExportServiceImpl excelImExportService;
+ @Mock
+ private MonitorService monitorService;
+
+ @Mock
+ private ManagerSseManager managerSseManager;
+
@BeforeEach
public void setUp() {
@@ -108,6 +124,53 @@ class ExcelImExportServiceTest {
assertEquals("Host1", dataRow.getCell(2).getStringCellValue());
}
+ @Test
+ public void testImportConfigAllValid() throws IOException {
+ ByteArrayInputStream is = buildImportStream(List.of("Monitor1",
"Monitor2"));
+ excelImExportService.importConfig("task", is);
+ verify(monitorService, times(2)).validate(any(MonitorDto.class),
eq(false));
+ verify(monitorService, times(2)).addMonitor(any(), any(), any(),
any());
+ verify(managerSseManager).broadcastImportTaskSuccess("task");
+ }
+
+ @Test
+ public void testImportConfigRejectsDuplicateNameInFile() throws
IOException {
+ ByteArrayInputStream is = buildImportStream(List.of("DupMonitor",
"DupMonitor"));
+ assertThrows(IllegalArgumentException.class, () ->
excelImExportService.importConfig("task", is));
+ verify(monitorService, never()).addMonitor(any(), any(), any(), any());
+ verify(managerSseManager, never()).broadcastImportTaskSuccess(any());
+ }
+
+ @Test
+ public void testImportConfigRejectsWholeBatchWhenLaterEntryInvalid()
throws IOException {
+ doNothing().doThrow(new IllegalArgumentException("Monitoring name
already exists!"))
+ .when(monitorService).validate(any(MonitorDto.class),
eq(false));
+ ByteArrayInputStream is = buildImportStream(List.of("Monitor1",
"Monitor2"));
+ assertThrows(IllegalArgumentException.class, () ->
excelImExportService.importConfig("task", is));
+ verify(monitorService, never()).addMonitor(any(), any(), any(), any());
+ verify(managerSseManager, never()).broadcastImportTaskSuccess(any());
+ }
+
+ private ByteArrayInputStream buildImportStream(List<String> names) throws
IOException {
+ try (Workbook workbook = new XSSFWorkbook()) {
+ var sheet = workbook.createSheet();
+ var headerRow = sheet.createRow(0);
+ headerRow.createCell(0).setCellValue("name");
+ headerRow.createCell(1).setCellValue("app");
+ headerRow.createCell(2).setCellValue("host");
+ for (int i = 0; i < names.size(); i++) {
+ var dataRow = sheet.createRow(i + 1);
+ dataRow.createCell(0).setCellValue(names.get(i));
+ dataRow.createCell(1).setCellValue("App");
+ dataRow.createCell(2).setCellValue("Host");
+ dataRow.createCell(4).setCellValue(1);
+ }
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ workbook.write(bos);
+ return new ByteArrayInputStream(bos.toByteArray());
+ }
+ }
+
@Test
public void testWriteThenParsePreservesAllFields() throws IOException {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]