From a4dbf4ab27a251af5cd9d68c47ba74d477ecb335 Mon Sep 17 00:00:00 2001 From: curry772 Date: Tue, 26 May 2026 16:32:32 +0900 Subject: [PATCH] =?UTF-8?q?Client=20=EC=9C=A0=EB=9F=89=EC=A0=9C=EC=96=B4?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20eapim-online=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReloadInflowClientControlCommand.java | 60 ------ .../RemoveInflowClientControlCommand.java | 50 ----- .../common/inflow/dual/ClientDualBucket.java | 22 -- .../dual/ClientDualInflowControlManager.java | 112 ----------- ...ntDualInflowControlManagerMetricsTest.java | 190 ------------------ 5 files changed, 434 deletions(-) delete mode 100644 src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java delete mode 100644 src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java delete mode 100644 src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java delete mode 100644 src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java delete mode 100644 src/test/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManagerMetricsTest.java diff --git a/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java b/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java deleted file mode 100644 index becbfeb..0000000 --- a/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.eactive.eai.agent.inflow; - -import com.eactive.eai.agent.command.Command; -import com.eactive.eai.agent.command.CommandException; -import com.eactive.eai.common.inflow.AbstractInflowControlManager; -import com.eactive.eai.common.inflow.InflowControlUtil; -import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; -import com.eactive.eai.common.util.Logger; - -public class ReloadInflowClientControlCommand extends Command { - - private static final long serialVersionUID = 1L; - - public ReloadInflowClientControlCommand() { - super("ReloadInflowClientControlCommand"); - } - - public Object execute() throws CommandException { - Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); - if (logger.isInfo()) - logger.info(this.name + " is executed"); - - if (!(args instanceof String)) { - String rspErrorCode = "RECEAIMCM001"; - String msg = makeException(rspErrorCode, null); - if (logger.isError()) - logger.error(msg); - throw new CommandException(msg); - } - - String keyName = (String) args; - try { - AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); - if (!(base instanceof ClientDualInflowControlManager)) { - throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다."); - } - ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base; - - if (keyName != null) { - if ("ALL".equals(keyName)) { - manager.reloadClient(); - if (logger.isWarn()) - logger.warn(this.name + "] all rule Reload."); - } else { - manager.reloadClient(keyName); - if (logger.isWarn()) - logger.warn(this.name + "] " + keyName + " Reload."); - } - } - - } catch (Exception e) { - String rspErrorCode = "RECEAIMCM002"; - String msg = makeException(rspErrorCode, e); - if (logger.isError()) - logger.error(msg, e); - throw new CommandException(msg); - } - return "success"; - } -} diff --git a/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java b/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java deleted file mode 100644 index 97fe72c..0000000 --- a/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.eactive.eai.agent.inflow; - -import com.eactive.eai.agent.command.Command; -import com.eactive.eai.agent.command.CommandException; -import com.eactive.eai.common.inflow.AbstractInflowControlManager; -import com.eactive.eai.common.inflow.InflowControlUtil; -import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager; -import com.eactive.eai.common.util.Logger; - -public class RemoveInflowClientControlCommand extends Command { - - private static final long serialVersionUID = 1L; - - public RemoveInflowClientControlCommand() { - super("RemoveInflowClientControlCommand"); - } - - public Object execute() throws CommandException { - Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); - if (logger.isInfo()) - logger.info(this.name + " is executed"); - - if (!(args instanceof String)) { - String rspErrorCode = "RECEAIMCM001"; - String msg = makeException(rspErrorCode, null); - if (logger.isError()) - logger.error(msg); - throw new CommandException(msg); - } - - String key = (String) args; - try { - AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager(); - if (!(base instanceof ClientDualInflowControlManager)) { - throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다."); - } - ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base; - manager.removeClient(key); - if (logger.isWarn()) - logger.warn(this.name + "] " + key + " removed."); - } catch (Exception e) { - String rspErrorCode = "RECEAIMCM002"; - String msg = makeException(rspErrorCode, e); - if (logger.isError()) - logger.error(msg, e); - throw new CommandException(msg); - } - return "success"; - } -} diff --git a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java deleted file mode 100644 index da8d686..0000000 --- a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.eactive.eai.common.inflow.dual; - -import com.eactive.eai.common.inflow.InflowTargetVO; - -/** - * DualBucket에 클라이언트 유량제어 메서드를 추가한 인터페이스. - * {@link ClientDualInflowControlManager}가 구현한다. - */ -public interface ClientDualBucket extends DualBucket { - - /** - * 클라이언트 유량제어 체크 — 차단 원인 포함. - * @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단 - */ - String isClientPassDetail(String clientId); - - /** - * 클라이언트 유량제어 설정 조회 — 에러 메시지 생성용. - * @return 등록된 설정이 없으면 null - */ - InflowTargetVO getClientInflowThreshold(String clientId); -} diff --git a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java deleted file mode 100644 index 606d1e7..0000000 --- a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.eactive.eai.common.inflow.dual; - -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.springframework.stereotype.Component; - -import com.eactive.eai.common.inflow.Bucket; -import com.eactive.eai.common.inflow.InflowTargetVO; -import com.eactive.eai.common.inflow.InflowType; -import com.eactive.eai.common.lifecycle.LifecycleException; -import com.eactive.eai.common.util.ApplicationContextProvider; -import com.eactive.eai.common.util.Logger; - -/** - * 어댑터/인터페이스 이중 버킷(DualInflowControlManager)에 클라이언트 유량제어를 추가한 관리자. - * - *

적용 방법: INFLOW.properties에 아래 한 줄 추가. - *

inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager
- */ -@Component -public class ClientDualInflowControlManager extends DualInflowControlManager implements ClientDualBucket { - - private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT); - - public static synchronized Bucket getBucket() { - return ApplicationContextProvider.getContext().getBean(ClientDualInflowControlManager.class); - } - - private volatile Map clientBucketMap = new ConcurrentHashMap<>(); - private final ConcurrentHashMap clientMetricsMap = new ConcurrentHashMap<>(); - - // ------------------------------------------------------------------------- - // Lifecycle - // ------------------------------------------------------------------------- - - @Override - public void start() throws LifecycleException { - super.start(); - try { - initClients(); - } catch (Exception e) { - throw new LifecycleException("ClientDualInflowControlManager init failed: " + e.getMessage()); - } - } - - // ------------------------------------------------------------------------- - // 초기화 / 리로드 - // ------------------------------------------------------------------------- - - private void initClients() throws Exception { - this.clientBucketMap = loadBucketMap(InflowType.CLIENT); - if (logger.isInfo()) logger.info("ClientDualInflowControlManager] initClients: " + clientBucketMap.size() + " buckets"); - } - - public synchronized void reloadClient() throws Exception { - initClients(); - clientMetricsMap.clear(); - } - - public synchronized void reloadClient(String clientId) throws Exception { - reloadBucketMap(clientBucketMap, InflowType.CLIENT, clientId); - TargetMetrics m = clientMetricsMap.get(clientId); - if (m != null) m.reset(); - } - - public synchronized void removeClient(String clientId) { - clientBucketMap.remove(clientId); - clientMetricsMap.remove(clientId); - } - - // ------------------------------------------------------------------------- - // 클라이언트 메트릭 접근자 - // ------------------------------------------------------------------------- - - public TargetMetrics getClientMetrics(String clientId) { return clientMetricsMap.get(clientId); } - public Map getAllClientMetrics() { return Collections.unmodifiableMap(clientMetricsMap); } - public void resetClientMetrics(String clientId) { TargetMetrics m = clientMetricsMap.get(clientId); if (m != null) m.reset(); } - public void resetAllClientMetrics() { clientMetricsMap.values().forEach(TargetMetrics::reset); } - - // ------------------------------------------------------------------------- - // ClientDualBucket — 클라이언트 유량제어 - // ------------------------------------------------------------------------- - - @Override - public String isClientPassDetail(String clientId) { - DualCustomBucket b = clientBucketMap.get(clientId); - if (b == null) return DualCustomBucket.RESULT_PASS; - String result = b.tryConsume(); - recordMetrics(clientMetricsMap, clientId, result); - return result; - } - - @Override - public InflowTargetVO getClientInflowThreshold(String clientId) { - DualCustomBucket b = clientBucketMap.get(clientId); - return (b == null) ? null : b.getInflowTargetVo(); - } - - // ------------------------------------------------------------------------- - // 모니터링용 접근자 - // ------------------------------------------------------------------------- - - public DualCustomBucket getClientBucket(String clientId) { - return clientBucketMap.get(clientId); - } - - public Map getClientBucketMap() { - return clientBucketMap; - } -} diff --git a/src/test/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManagerMetricsTest.java b/src/test/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManagerMetricsTest.java deleted file mode 100644 index cb991d2..0000000 --- a/src/test/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManagerMetricsTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package com.eactive.eai.common.inflow.dual; - -import static org.junit.jupiter.api.Assertions.*; - -import java.time.Duration; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.test.util.ReflectionTestUtils; - -import com.eactive.eai.common.inflow.InflowTargetVO; - -import io.github.bucket4j.Bandwidth; -import io.github.bucket4j.Bucket4j; -import io.github.bucket4j.local.LocalBucket; - -/** - * ClientDualInflowControlManager 클라이언트 메트릭 단위 테스트. - */ -class ClientDualInflowControlManagerMetricsTest { - - private ClientDualInflowControlManager manager; - - @BeforeEach - void setUp() { - manager = new ClientDualInflowControlManager(); - } - - // ------------------------------------------------------------------------- - // Helpers - // ------------------------------------------------------------------------- - - private InflowTargetVO makeTargetVO(String name) { - InflowTargetVO vo = new InflowTargetVO(); - vo.setName(name); - vo.setThresholdPerSecond(100); - vo.setThreshold(10000); - vo.setThresholdTimeUnit("DAY"); - vo.setActivate(true); - return vo; - } - - private LocalBucket stableBucket(long capacity) { - return Bucket4j.builder() - .addLimit(Bandwidth.simple(capacity, Duration.ofHours(1))) - .build(); - } - - private LocalBucket exhaustedBucket(long capacity) { - LocalBucket b = stableBucket(capacity); - b.tryConsume(capacity); - return b; - } - - private DualCustomBucket passDualBucket(String name) { - return new DualCustomBucket(stableBucket(100), null, makeTargetVO(name)); - } - - private DualCustomBucket blockedPerSecondDualBucket(String name) { - return new DualCustomBucket(exhaustedBucket(1), null, makeTargetVO(name)); - } - - private DualCustomBucket blockedThresholdDualBucket(String name) { - return new DualCustomBucket(stableBucket(100), exhaustedBucket(1), makeTargetVO(name)); - } - - private void injectClientMap(Map map) { - ReflectionTestUtils.setField(manager, "clientBucketMap", map); - } - - // ========================================================================= - // isClientPassDetail() — 카운터 증가 - // ========================================================================= - - @Test - void isClientPassDetail_bucketNotRegistered_noMetricsCreated() { - manager.isClientPassDetail("UNKNOWN"); - - assertNull(manager.getClientMetrics("UNKNOWN")); - } - - @Test - void isClientPassDetail_pass_incrementsAllowed() { - Map map = new ConcurrentHashMap<>(); - map.put("C1", passDualBucket("C1")); - injectClientMap(map); - - manager.isClientPassDetail("C1"); - - assertEquals(1, manager.getClientMetrics("C1").allowed.get()); - } - - @Test - void isClientPassDetail_blockedThreshold_incrementsRejectedThreshold() { - Map map = new ConcurrentHashMap<>(); - map.put("C1", blockedThresholdDualBucket("C1")); - injectClientMap(map); - - manager.isClientPassDetail("C1"); - - assertEquals(1, manager.getClientMetrics("C1").rejectedThreshold.get()); - } - - @Test - void isClientPassDetail_blockedPerSecond_incrementsRejectedPerSecond() { - Map map = new ConcurrentHashMap<>(); - map.put("C1", blockedPerSecondDualBucket("C1")); - injectClientMap(map); - - manager.isClientPassDetail("C1"); - - assertEquals(1, manager.getClientMetrics("C1").rejectedPerSecond.get()); - } - - // ========================================================================= - // getAllClientMetrics — 조회 및 불변성 - // ========================================================================= - - @Test - void getAllClientMetrics_empty_returnsEmptyMap() { - assertTrue(manager.getAllClientMetrics().isEmpty()); - } - - @Test - void getAllClientMetrics_afterCalls_containsEntries() { - Map map = new ConcurrentHashMap<>(); - map.put("C1", passDualBucket("C1")); - map.put("C2", passDualBucket("C2")); - injectClientMap(map); - - manager.isClientPassDetail("C1"); - manager.isClientPassDetail("C2"); - - Map all = manager.getAllClientMetrics(); - assertEquals(2, all.size()); - assertTrue(all.containsKey("C1")); - assertTrue(all.containsKey("C2")); - } - - // ========================================================================= - // resetClientMetrics / resetAllClientMetrics - // ========================================================================= - - @Test - void resetClientMetrics_resetsTargetClient() { - Map map = new ConcurrentHashMap<>(); - map.put("C1", passDualBucket("C1")); - injectClientMap(map); - - manager.isClientPassDetail("C1"); - manager.resetClientMetrics("C1"); - - assertEquals(0, manager.getClientMetrics("C1").allowed.get()); - } - - @Test - void resetAllClientMetrics_resetsAll() { - Map map = new ConcurrentHashMap<>(); - map.put("C1", passDualBucket("C1")); - map.put("C2", passDualBucket("C2")); - injectClientMap(map); - - manager.isClientPassDetail("C1"); - manager.isClientPassDetail("C2"); - manager.resetAllClientMetrics(); - - assertEquals(0, manager.getClientMetrics("C1").allowed.get()); - assertEquals(0, manager.getClientMetrics("C2").allowed.get()); - } - - // ========================================================================= - // removeClient — 메트릭 자동 삭제 - // ========================================================================= - - @Test - void removeClient_cleansUpMetrics() { - Map map = new ConcurrentHashMap<>(); - map.put("C1", passDualBucket("C1")); - injectClientMap(map); - - manager.isClientPassDetail("C1"); - assertNotNull(manager.getClientMetrics("C1")); - - manager.removeClient("C1"); - - assertNull(manager.getClientMetrics("C1")); - } -}