diff --git a/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/client/WallApiClientImpl.java b/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/client/WallApiClientImpl.java index 81dfd281c994..dd1a1274fb93 100644 --- a/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/client/WallApiClientImpl.java +++ b/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/client/WallApiClientImpl.java @@ -1245,13 +1245,14 @@ private ObjectNode findByNameInArray(final ArrayNode groupsArray, final String g private String resolveFolderUidByTitle(final String title) { if (title == null || title.isBlank()) return null; try { - final String url = baseUrl + "/api/search?type=dash-folder&query=" + final String url = baseUrlNow() + "/api/search?type=dash-folder&query=" + java.net.URLEncoder.encode(title, java.nio.charset.StandardCharsets.UTF_8); HttpRequest.Builder rb = HttpRequest.newBuilder() .uri(URI.create(url)) .timeout(java.time.Duration.ofMillis(readTimeoutMs)) .header("Accept", "application/json"); - if (bearer != null) rb.header("Authorization", "Bearer " + bearer); + final String b = bearerNow(); + if (b != null) rb.header("Authorization", "Bearer " + b); final java.net.http.HttpResponse res = http.send(rb.GET().build(), java.net.http.HttpResponse.BodyHandlers.ofString()); @@ -1481,9 +1482,10 @@ private String resolveNamespaceByGroupName(final String groupName) { private RulerRulesResponse.Group fetchRulerGroupDirect(final String namespace, final String groupName) { final String encodedFolder = URLEncoder.encode(namespace, StandardCharsets.UTF_8); + final String base = baseUrlNow(); final String[] urls = new String[] { - baseUrl + "/api/ruler/grafana/api/v1/rules/" + encodedFolder + "?subtype=cortex", - baseUrl + "/api/ruler/grafana/api/v1/rules/" + encodedFolder + base + "/api/ruler/grafana/api/v1/rules/" + encodedFolder + "?subtype=cortex", + base + "/api/ruler/grafana/api/v1/rules/" + encodedFolder }; for (String url : urls) { try { @@ -1491,7 +1493,8 @@ private RulerRulesResponse.Group fetchRulerGroupDirect(final String namespace, f .uri(URI.create(url)) .timeout(Duration.ofMillis(readTimeoutMs)) .header("Accept", "application/json"); - if (bearer != null) rb.header("Authorization", "Bearer " + bearer); + final String b = bearerNow(); + if (b != null) rb.header("Authorization", "Bearer " + b); final HttpResponse res = http.send(rb.GET().build(), HttpResponse.BodyHandlers.ofString()); if (res.statusCode() < 200 || res.statusCode() >= 300) { LOG.warn("[Ruler] GET " + url + " -> " + res.statusCode()); @@ -1559,18 +1562,19 @@ private boolean trySend(final String url, final int sc = res.statusCode(); if (sc >= 200 && sc < 300) { // 200/202 모두 성공 처리 - LOG.info("[Ruler][send] {} {} -> {}"); + LOG.info("[Ruler][send] " + method + " " + url + " -> " + sc); return true; } - LOG.warn("[Ruler][send] {} {} -> {} (preview: {})" - ); + LOG.warn("[Ruler][send] " + method + " " + url + " -> " + sc + + " (preview: " + trimBody(res.body(), 600) + ")"); return false; } catch (IllegalArgumentException iae) { // URI.create 등 - LOG.warn("[Ruler][send] invalid URI for {}: {} ({})"); + LOG.warn("[Ruler][send] invalid URI for " + method + ": " + url + + " (" + iae.getMessage() + ")"); return false; } catch (Exception e) { - LOG.warn("[Ruler][send] {} {} failed: {}"); + LOG.warn("[Ruler][send] " + method + " " + url + " failed: " + e.getMessage(), e); return false; } } @@ -1630,7 +1634,7 @@ private ObjectNode fetchRulerGroupRawFlexible(final String nsRaw, final String nsKeyForUrl, final String folderUid, final String groupName) { - final String base = baseUrl; // baseUrlNow() 쓰는 구조면 그걸로 교체해도 됨 + final String base = baseUrlNow(); final java.util.LinkedHashSet nsSet = buildNsCandidates(nsRaw, nsKeyForUrl, folderUid); final String encGroup = encodePathSegment(groupName); @@ -1658,7 +1662,8 @@ private ObjectNode fetchRulerGroupRawFlexible(final String nsRaw, .uri(URI.create(url)) .timeout(java.time.Duration.ofMillis(readTimeoutMs)) .header("Accept", "application/json"); - if (bearer != null) rb.header("Authorization", "Bearer " + bearer); + final String b = bearerNow(); + if (b != null) rb.header("Authorization", "Bearer " + b); final HttpResponse res = http.send(rb.GET().build(), HttpResponse.BodyHandlers.ofString()); final int sc = res.statusCode(); @@ -1740,13 +1745,13 @@ private static String textOrNull(final JsonNode n) { private ObjectNode fetchRulerNamespaceRaw(final String nsKey) { try { - final String url = baseUrl + "/api/ruler/grafana/api/v1/rules/" + encodePathSegment(nsKey); - final HttpRequest req = HttpRequest.newBuilder(URI.create(url)) + final String url = baseUrlNow() + "/api/ruler/grafana/api/v1/rules/" + encodePathSegment(nsKey); + final HttpRequest.Builder rb = HttpRequest.newBuilder(URI.create(url)) .timeout(Duration.ofMillis(Math.max(readTimeoutMs, 10000))) - .header("Authorization", "Bearer " + bearer) - .header("Accept", "application/json") - .GET() - .build(); + .header("Accept", "application/json"); + final String b = bearerNow(); + if (b != null) rb.header("Authorization", "Bearer " + b); + final HttpRequest req = rb.GET().build(); final HttpResponse res = http.send(req, HttpResponse.BodyHandlers.ofString()); if (res.statusCode() / 100 == 2) { diff --git a/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/service/WallAlertsServiceImpl.java b/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/service/WallAlertsServiceImpl.java index bf03018a8efa..a15f4b298e14 100644 --- a/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/service/WallAlertsServiceImpl.java +++ b/plugins/integrations/wall-alerts/src/main/java/org/apache/cloudstack/wallAlerts/service/WallAlertsServiceImpl.java @@ -627,6 +627,13 @@ public WallAlertRuleResponse updateWallAlertRuleThreshold(final UpdateWallAlertR ok = wallApiClient.updateRuleThreshold(m.namespace, m.group, m.title, op, newThreshold, null); } + if (!ok) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "임계값 업데이트에 실패했습니다. Wall Ruler API 응답 로그를 확인해 주세요."); + } + + invalidateRulesCache(); + final WallAlertRuleResponse resp = new WallAlertRuleResponse(); resp.setObjectName("wallalertrule"); resp.setId(id != null && !id.isBlank() ? id : (m.group + ":" + m.title)); diff --git a/plugins/integrations/wall-alerts/src/test/java/org/apache/cloudstack/wallAlerts/client/WallApiClientImplTest.java b/plugins/integrations/wall-alerts/src/test/java/org/apache/cloudstack/wallAlerts/client/WallApiClientImplTest.java new file mode 100644 index 000000000000..aab08ceace97 --- /dev/null +++ b/plugins/integrations/wall-alerts/src/test/java/org/apache/cloudstack/wallAlerts/client/WallApiClientImplTest.java @@ -0,0 +1,62 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.wallAlerts.client; + +import java.lang.reflect.Method; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class WallApiClientImplTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private WallApiClientImpl client; + private Method mutateThresholdRaw; + + @Before + public void setUp() throws Exception { + client = new WallApiClientImpl("http://localhost", null, 1000, 1000); + mutateThresholdRaw = WallApiClientImpl.class.getDeclaredMethod("mutateThresholdRaw", + ObjectNode.class, String.class, String.class, Double.class, Double.class); + mutateThresholdRaw.setAccessible(true); + } + + @Test + public void updatesEvaluatorReferencedByCondition() throws Exception { + final ObjectNode group = groupWithData("[{\"refId\":\"C\",\"model\":{\"conditions\":[" + + "{\"evaluator\":{\"type\":\"lt\",\"params\":[60]}}]}}]"); + + Assert.assertTrue(mutate(group, "gt", 75.0, null)); + Assert.assertEquals("gt", group.at("/rules/0/grafana_alert/data/0/model/conditions/0/evaluator/type").asText()); + Assert.assertEquals(75.0, + group.at("/rules/0/grafana_alert/data/0/model/conditions/0/evaluator/params/0").asDouble(), 0.0); + } + + private ObjectNode groupWithData(final String data) throws Exception { + final String group = "{\"name\":\"WALL_Every_1m\",\"rules\":[{\"title\":\"host cpu\"," + + "\"grafana_alert\":{\"title\":\"host cpu\",\"condition\":\"C\",\"data\":" + data + "}}]}"; + return (ObjectNode) objectMapper.readTree(group); + } + + private boolean mutate(final ObjectNode group, final String operator, + final Double threshold, final Double threshold2) throws Exception { + return (Boolean) mutateThresholdRaw.invoke(client, group, "host cpu", operator, threshold, threshold2); + } +} diff --git a/ui/src/views/infra/WallThresholdEditor.vue b/ui/src/views/infra/WallThresholdEditor.vue index 306277b65ff4..a43bb1b3eba9 100644 --- a/ui/src/views/infra/WallThresholdEditor.vue +++ b/ui/src/views/infra/WallThresholdEditor.vue @@ -412,16 +412,8 @@ export default { if (isRange) { payload.threshold = lower payload.threshold2 = upper - payload.lower = lower - payload.upper = upper - payload.thresholdUpper = upper } else { payload.threshold = lower - payload.lower = lower - // 상한 키들을 명시적으로 비워서 “되돌림” 방지 - payload.threshold2 = null - payload.upper = null - payload.thresholdUpper = null } return payload