Merge branch 'feature/inflow-control-improvement'
- AbstractInflowControlManager 상속 구조 리팩토링 - DualInflowControlManager DAO 이중 호출 제거 및 타겟 메트릭 추가 - ReloadInflowClientControlCommandTest / RemoveInflowClientControlCommandTest 수정
This commit is contained in:
@@ -2,7 +2,8 @@ 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.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class ReloadInflowAdapterControlCommand extends Command {
|
||||
@@ -27,7 +28,7 @@ public class ReloadInflowAdapterControlCommand extends Command {
|
||||
|
||||
String keyName = (String) args;
|
||||
try {
|
||||
InflowControlManager manager = InflowControlManager.getInstance();
|
||||
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
|
||||
|
||||
if (keyName != null) {
|
||||
if ("ALL".equals(keyName)) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
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.DualInflowControlManager;
|
||||
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 DualInflowControlManager)) {
|
||||
throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
DualInflowControlManager manager = (DualInflowControlManager) 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";
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ 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.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class ReloadInflowGroupControlCommand extends Command {
|
||||
@@ -28,7 +29,7 @@ public class ReloadInflowGroupControlCommand extends Command {
|
||||
|
||||
String keyName = (String) args;
|
||||
try {
|
||||
InflowControlManager manager = InflowControlManager.getInstance();
|
||||
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
|
||||
|
||||
if (keyName != null) {
|
||||
if ("ALL".equals(keyName)) {
|
||||
|
||||
@@ -2,7 +2,8 @@ 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.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class ReloadInflowInterfaceControlCommand extends Command {
|
||||
@@ -28,7 +29,7 @@ public class ReloadInflowInterfaceControlCommand extends Command {
|
||||
|
||||
String keyName = (String) args;
|
||||
try {
|
||||
InflowControlManager manager = InflowControlManager.getInstance();
|
||||
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
|
||||
|
||||
if (keyName != null) {
|
||||
if ("ALL".equals(keyName)) {
|
||||
|
||||
@@ -2,7 +2,8 @@ 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.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class RemoveInflowAdapterControlCommand extends Command {
|
||||
@@ -26,7 +27,7 @@ public class RemoveInflowAdapterControlCommand extends Command {
|
||||
|
||||
String key = (String) args;
|
||||
try {
|
||||
InflowControlManager manager = InflowControlManager.getInstance();
|
||||
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
|
||||
manager.removeAdapter(key);
|
||||
} catch (Exception e) {
|
||||
String rspErrorCode = "RECEAIMCM002";
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
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.DualInflowControlManager;
|
||||
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 DualInflowControlManager)) {
|
||||
throw new IllegalStateException("DualInflowControlManager 가 활성화되지 않았습니다.");
|
||||
}
|
||||
DualInflowControlManager manager = (DualInflowControlManager) 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";
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ 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.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class RemoveInflowGroupControlCommand extends Command {
|
||||
@@ -28,7 +29,7 @@ public class RemoveInflowGroupControlCommand extends Command {
|
||||
|
||||
String key = (String) args;
|
||||
try {
|
||||
InflowControlManager manager = InflowControlManager.getInstance();
|
||||
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
|
||||
manager.removeGroup(key);
|
||||
if (logger.isWarn())
|
||||
logger.warn(this.name + "] group " + key + " removed.");
|
||||
|
||||
@@ -2,7 +2,8 @@ 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.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
public class RemoveInflowInterfaceControlCommand extends Command {
|
||||
@@ -25,7 +26,7 @@ public class RemoveInflowInterfaceControlCommand extends Command {
|
||||
|
||||
String key = (String) args;
|
||||
try {
|
||||
InflowControlManager manager = InflowControlManager.getInstance();
|
||||
AbstractInflowControlManager manager = InflowControlUtil.getInflowControlManager();
|
||||
manager.removeInterface(key);
|
||||
} catch (Exception e) {
|
||||
String rspErrorCode = "RECEAIMCM002";
|
||||
|
||||
@@ -0,0 +1,496 @@
|
||||
package com.eactive.eai.common.inflow;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.lifecycle.Lifecycle;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleListener;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleSupport;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.local.LocalBucket;
|
||||
import io.github.bucket4j.local.LocalBucketBuilder;
|
||||
|
||||
public abstract class AbstractInflowControlManager implements Lifecycle, Bucket {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
public static final String QUOTA_TIMEUNIT_SECOND = "SEC";
|
||||
public static final String QUOTA_TIMEUNIT_MINUTE = "MIN";
|
||||
public static final String QUOTA_TIMEUNIT_HOUR = "HOU";
|
||||
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
|
||||
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
|
||||
|
||||
private volatile Map<String, CustomBucket> adapterBucketList = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, CustomBucket> interfaceBucketList = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, CustomGroupBucket> groupBucketList = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, String> interfaceToGroupMap = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, InflowGroupVO> groupVoMap = new ConcurrentHashMap<>();
|
||||
private boolean started;
|
||||
private LifecycleSupport lifecycle = new LifecycleSupport(this);
|
||||
|
||||
@Autowired
|
||||
protected InflowControlDAO inflowControlDAO;
|
||||
|
||||
public void start() throws LifecycleException {
|
||||
if (started)
|
||||
throw new LifecycleException("RECEAICMM201");
|
||||
|
||||
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
|
||||
try {
|
||||
init();
|
||||
} catch (Exception e) {
|
||||
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICMM202"));
|
||||
}
|
||||
started = true;
|
||||
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
|
||||
}
|
||||
|
||||
private void init() throws Exception {
|
||||
initAdapter();
|
||||
initInterface();
|
||||
initGroup();
|
||||
}
|
||||
|
||||
private void initAdapter() throws Exception {
|
||||
Map<String, CustomBucket> newMap = new HashMap<>();
|
||||
List<InflowTargetVO> inflowAdapterVoList = inflowControlDAO.getInflowAdapterList();
|
||||
|
||||
for (InflowTargetVO inflowVo : inflowAdapterVoList) {
|
||||
if (AbstractInflowControlManager.isInflowTarget(inflowVo)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
|
||||
if (bucket != null) {
|
||||
newMap.put(inflowVo.getName(), bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.adapterBucketList = newMap;
|
||||
}
|
||||
|
||||
private void initInterface() throws Exception {
|
||||
Map<String, CustomBucket> newMap = new HashMap<>();
|
||||
List<InflowTargetVO> inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList();
|
||||
|
||||
for (InflowTargetVO inflowVo : inflowInterfaceVoList) {
|
||||
if (AbstractInflowControlManager.isInflowTarget(inflowVo)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
|
||||
if (bucket != null) {
|
||||
newMap.put(inflowVo.getName(), bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.interfaceBucketList = newMap;
|
||||
}
|
||||
|
||||
private void initGroup() throws Exception {
|
||||
Map<String, CustomGroupBucket> newGroupBucketList = new HashMap<>();
|
||||
Map<String, String> newInterfaceToGroupMap = new HashMap<>();
|
||||
Map<String, InflowGroupVO> newGroupVoMap = new HashMap<>();
|
||||
|
||||
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
|
||||
|
||||
for (InflowGroupVO groupVo : inflowGroupVoList) {
|
||||
if (isInflowGroupTarget(groupVo)) {
|
||||
CustomGroupBucket bucket = makeGroupBucket(groupVo);
|
||||
if (bucket != null) {
|
||||
newGroupBucketList.put(groupVo.getGroupId(), bucket);
|
||||
newGroupVoMap.put(groupVo.getGroupId(), groupVo);
|
||||
for (String interfaceId : groupVo.getInterfaceList()) {
|
||||
newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isInfo()) {
|
||||
logger.info("InflowControlManager] initGroup completed. groups=" + newGroupBucketList.size()
|
||||
+ ", mappings=" + newInterfaceToGroupMap.size());
|
||||
}
|
||||
|
||||
this.groupBucketList = newGroupBucketList;
|
||||
this.interfaceToGroupMap = newInterfaceToGroupMap;
|
||||
this.groupVoMap = newGroupVoMap;
|
||||
}
|
||||
|
||||
public synchronized void reloadAdapter() throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all Started ...");
|
||||
}
|
||||
initAdapter();
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadInterface() throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all Started ...");
|
||||
}
|
||||
initInterface();
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadAdapter(String adapter) throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload Started ...");
|
||||
}
|
||||
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByAdater(adapter);
|
||||
if (map != null) {
|
||||
Iterator<String> it = map.keySet().iterator();
|
||||
String key = "";
|
||||
while (it.hasNext()) {
|
||||
key = it.next();
|
||||
|
||||
InflowTargetVO inflowTarget = map.get(key);
|
||||
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
|
||||
if (bucket != null) {
|
||||
adapterBucketList.put(inflowTarget.getName(), bucket);
|
||||
} else {
|
||||
adapterBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
} else {
|
||||
adapterBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadInterface(String inter) throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload Started ...");
|
||||
}
|
||||
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByInterface(inter);
|
||||
if (map != null) {
|
||||
Iterator<String> it = map.keySet().iterator();
|
||||
String key = "";
|
||||
while (it.hasNext()) {
|
||||
key = it.next();
|
||||
|
||||
InflowTargetVO inflowTarget = map.get(key);
|
||||
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
|
||||
if (bucket != null) {
|
||||
interfaceBucketList.put(inflowTarget.getName(), bucket);
|
||||
} else {
|
||||
interfaceBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
} else {
|
||||
interfaceBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadGroup() throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup all Started ...");
|
||||
}
|
||||
initGroup();
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup all finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadGroup(String groupId) throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup Started ... groupId=" + groupId);
|
||||
}
|
||||
Map<String, InflowGroupVO> map = inflowControlDAO.getInflowTargetByGroup(groupId);
|
||||
if (map != null) {
|
||||
Iterator<Map.Entry<String, String>> mappingIt = interfaceToGroupMap.entrySet().iterator();
|
||||
while (mappingIt.hasNext()) {
|
||||
Map.Entry<String, String> entry = mappingIt.next();
|
||||
if (groupId.equals(entry.getValue())) {
|
||||
mappingIt.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<String> it = map.keySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
String key = it.next();
|
||||
InflowGroupVO groupVo = map.get(key);
|
||||
|
||||
if (isInflowGroupTarget(groupVo)) {
|
||||
CustomGroupBucket bucket = makeGroupBucket(groupVo);
|
||||
if (bucket != null) {
|
||||
groupBucketList.put(groupVo.getGroupId(), bucket);
|
||||
groupVoMap.put(groupVo.getGroupId(), groupVo);
|
||||
for (String interfaceId : groupVo.getInterfaceList()) {
|
||||
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
|
||||
}
|
||||
} else {
|
||||
groupBucketList.remove(groupVo.getGroupId());
|
||||
groupVoMap.remove(groupVo.getGroupId());
|
||||
}
|
||||
} else {
|
||||
groupBucketList.remove(groupVo.getGroupId());
|
||||
groupVoMap.remove(groupVo.getGroupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws LifecycleException {
|
||||
if (!started)
|
||||
throw new LifecycleException("RECEAICMM203");
|
||||
|
||||
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
|
||||
|
||||
adapterBucketList = new ConcurrentHashMap<>();
|
||||
interfaceBucketList = new ConcurrentHashMap<>();
|
||||
groupBucketList = new ConcurrentHashMap<>();
|
||||
interfaceToGroupMap = new ConcurrentHashMap<>();
|
||||
groupVoMap = new ConcurrentHashMap<>();
|
||||
|
||||
started = false;
|
||||
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
|
||||
}
|
||||
|
||||
public void addLifecycleListener(LifecycleListener listener) {
|
||||
lifecycle.addLifecycleListener(listener);
|
||||
}
|
||||
|
||||
public LifecycleListener[] findLifecycleListeners() {
|
||||
return lifecycle.findLifecycleListeners();
|
||||
}
|
||||
|
||||
public void removeLifecycleListener(LifecycleListener listener) {
|
||||
lifecycle.removeLifecycleListener(listener);
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return this.started;
|
||||
}
|
||||
|
||||
public String[] getAdapterAllKeys() {
|
||||
Iterator<String> it = this.adapterBucketList.keySet().iterator();
|
||||
String[] svcCd = new String[this.adapterBucketList.size()];
|
||||
for (int i = 0; it.hasNext(); i++) {
|
||||
svcCd[i] = it.next();
|
||||
}
|
||||
Arrays.sort(svcCd);
|
||||
return svcCd;
|
||||
}
|
||||
|
||||
public String[] getInterfaceAllKeys() {
|
||||
Iterator<String> it = this.interfaceBucketList.keySet().iterator();
|
||||
String[] svcCd = new String[this.interfaceBucketList.size()];
|
||||
for (int i = 0; it.hasNext(); i++) {
|
||||
svcCd[i] = it.next();
|
||||
}
|
||||
Arrays.sort(svcCd);
|
||||
return svcCd;
|
||||
}
|
||||
|
||||
public synchronized void removeAdapter(String adapter) {
|
||||
adapterBucketList.remove(adapter);
|
||||
}
|
||||
|
||||
public synchronized void removeInterface(String inter) {
|
||||
interfaceBucketList.remove(inter);
|
||||
}
|
||||
|
||||
public synchronized void removeGroup(String groupId) {
|
||||
groupBucketList.remove(groupId);
|
||||
groupVoMap.remove(groupId);
|
||||
Iterator<Map.Entry<String, String>> it = interfaceToGroupMap.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, String> entry = it.next();
|
||||
if (groupId.equals(entry.getValue())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getGroupAllKeys() {
|
||||
Iterator<String> it = this.groupBucketList.keySet().iterator();
|
||||
String[] groupIds = new String[this.groupBucketList.size()];
|
||||
for (int i = 0; it.hasNext(); i++) {
|
||||
groupIds[i] = it.next();
|
||||
}
|
||||
Arrays.sort(groupIds);
|
||||
return groupIds;
|
||||
}
|
||||
|
||||
public CustomGroupBucket getGroupBucket(String groupId) {
|
||||
return groupBucketList.get(groupId);
|
||||
}
|
||||
|
||||
private CustomBucket makeBucketUsingInflowVO(InflowTargetVO inflowTarget) {
|
||||
if (AbstractInflowControlManager.isInflowTarget(inflowTarget)) {
|
||||
LocalBucketBuilder builder = Bucket4j.builder();
|
||||
if (inflowTarget.getThresholdPerSecond() > 0) {
|
||||
builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1)));
|
||||
}
|
||||
|
||||
if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) {
|
||||
builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(),
|
||||
AbstractInflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit())));
|
||||
}
|
||||
|
||||
return new CustomBucket(builder.build(), inflowTarget);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CustomGroupBucket makeGroupBucket(InflowGroupVO groupVo) {
|
||||
if (!isInflowGroupTarget(groupVo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalBucket perSecondBucket = null;
|
||||
LocalBucket thresholdBucket = null;
|
||||
|
||||
if (groupVo.getThresholdPerSecond() > 0) {
|
||||
perSecondBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1)))
|
||||
.build();
|
||||
}
|
||||
|
||||
if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) {
|
||||
thresholdBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(groupVo.getThreshold(),
|
||||
AbstractInflowControlManager.getPeriod(groupVo.getThresholdTimeUnit())))
|
||||
.build();
|
||||
}
|
||||
|
||||
return new CustomGroupBucket(perSecondBucket, thresholdBucket, groupVo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAdapterPass(String adapter) {
|
||||
CustomBucket b = adapterBucketList.get(adapter);
|
||||
|
||||
if (b == null)
|
||||
return true;
|
||||
|
||||
return b.getLocalBucket().tryConsume(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterfacePass(String inter) {
|
||||
CustomBucket b = interfaceBucketList.get(inter);
|
||||
|
||||
if (b == null)
|
||||
return true;
|
||||
|
||||
return b.getLocalBucket().tryConsume(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
|
||||
CustomBucket b = adapterBucketList.get(adapter);
|
||||
if (b == null)
|
||||
return null;
|
||||
return b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
|
||||
CustomBucket b = interfaceBucketList.get(inter);
|
||||
if (b == null)
|
||||
return null;
|
||||
return b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getInflowThreashold(String inter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public InflowTargetVO getAdapterInflow(String adapter) {
|
||||
return getAdapterInflowThreashold(adapter);
|
||||
}
|
||||
|
||||
public InflowTargetVO getInterfaceInflow(String inter) {
|
||||
return getInterfaceInflowThreashold(inter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String isGroupPass(String groupId) {
|
||||
CustomGroupBucket b = groupBucketList.get(groupId);
|
||||
|
||||
if (b == null)
|
||||
return null;
|
||||
|
||||
return b.tryConsume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowGroupVO getGroupInflowThreshold(String groupId) {
|
||||
return groupVoMap.get(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupIdByInterface(String interfaceId) {
|
||||
return interfaceToGroupMap.get(interfaceId);
|
||||
}
|
||||
|
||||
public InflowGroupVO getGroupInflow(String groupId) {
|
||||
return getGroupInflowThreshold(groupId);
|
||||
}
|
||||
|
||||
public static boolean isInflowTarget(InflowTargetVO inflowTarget) {
|
||||
if (inflowTarget == null || !inflowTarget.isActivate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return inflowTarget.getThresholdPerSecond() > 0 || (inflowTarget.getThreshold() > 0 && StringUtils
|
||||
.equalsAnyIgnoreCase(inflowTarget.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
|
||||
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
|
||||
|
||||
}
|
||||
|
||||
public static boolean isInflowGroupTarget(InflowGroupVO groupVo) {
|
||||
if (groupVo == null || !groupVo.isActivate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return groupVo.getThresholdPerSecond() > 0 || (groupVo.getThreshold() > 0 && StringUtils
|
||||
.equalsAnyIgnoreCase(groupVo.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
|
||||
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
|
||||
}
|
||||
|
||||
public static Duration getPeriod(String timeUnit) {
|
||||
if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_SECOND)) {
|
||||
return Duration.ofSeconds(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MINUTE)) {
|
||||
return Duration.ofMinutes(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_HOUR)) {
|
||||
return Duration.ofHours(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_DAY)) {
|
||||
return Duration.ofDays(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MONTH)) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
return Duration.ofDays(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,11 @@
|
||||
package com.eactive.eai.common.inflow;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.exception.ExceptionUtil;
|
||||
import com.eactive.eai.common.lifecycle.Lifecycle;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleException;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleListener;
|
||||
import com.eactive.eai.common.lifecycle.LifecycleSupport;
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.common.util.MessageUtil;
|
||||
import com.eactive.eai.message.service.InterfaceMapper;
|
||||
import com.ext.eai.common.stdmessage.STDMessageKeys;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.local.LocalBucket;
|
||||
import io.github.bucket4j.local.LocalBucketBuilder;
|
||||
|
||||
@Component
|
||||
public class InflowControlManager implements Lifecycle, Bucket {
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
public static final String QUOTA_TIMEUNIT_SECOND = "SEC";
|
||||
public static final String QUOTA_TIMEUNIT_MINUTE = "MIN";
|
||||
public static final String QUOTA_TIMEUNIT_HOUR = "HOU";
|
||||
public static final String QUOTA_TIMEUNIT_DAY = "DAY";
|
||||
public static final String QUOTA_TIMEUNIT_MONTH = "MON";
|
||||
|
||||
private volatile Map<String, CustomBucket> adapterBucketList = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, CustomBucket> interfaceBucketList = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, CustomGroupBucket> groupBucketList = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, String> interfaceToGroupMap = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, InflowGroupVO> groupVoMap = new ConcurrentHashMap<>();
|
||||
private boolean started;
|
||||
private LifecycleSupport lifecycle = new LifecycleSupport(this);
|
||||
|
||||
@Autowired
|
||||
private InflowControlDAO inflowControlDAO;
|
||||
public class InflowControlManager extends AbstractInflowControlManager {
|
||||
|
||||
public static synchronized InflowControlManager getInstance() {
|
||||
return ApplicationContextProvider.getContext().getBean(InflowControlManager.class);
|
||||
@@ -58,485 +14,4 @@ public class InflowControlManager implements Lifecycle, Bucket {
|
||||
public static synchronized Bucket getBucket() {
|
||||
return ApplicationContextProvider.getContext().getBean(InflowControlManager.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 유량제어 대상인 거래인지 체크한다.(사이트에 맞게 구성)
|
||||
*
|
||||
* @param eaiServerManager
|
||||
* @param eaiMessage
|
||||
* @return
|
||||
*/
|
||||
public static Boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) {
|
||||
InterfaceMapper mapper = eaiMessage.getMapper();
|
||||
String returnType = mapper.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
|
||||
|
||||
if (STDMessageKeys.SEND_RECV_CD_SEND.equals(returnType)) {
|
||||
return Boolean.valueOf(true);
|
||||
}
|
||||
|
||||
return Boolean.valueOf(false);
|
||||
}
|
||||
|
||||
public void start() throws LifecycleException {
|
||||
if (started)
|
||||
throw new LifecycleException("RECEAICMM201");
|
||||
|
||||
lifecycle.fireLifecycleEvent(STARTING_EVENT, this);
|
||||
try {
|
||||
init();
|
||||
} catch (Exception e) {
|
||||
throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAICMM202"));
|
||||
}
|
||||
started = true;
|
||||
lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
|
||||
}
|
||||
|
||||
private void init() throws Exception {
|
||||
initAdapter();
|
||||
initInterface();
|
||||
initGroup();
|
||||
}
|
||||
|
||||
private void initAdapter() throws Exception {
|
||||
Map<String, CustomBucket> newMap = new HashMap<>();
|
||||
List<InflowTargetVO> inflowAdapterVoList = inflowControlDAO.getInflowAdapterList();
|
||||
|
||||
for (InflowTargetVO inflowVo : inflowAdapterVoList) {
|
||||
if (InflowControlManager.isInflowTarget(inflowVo)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
|
||||
if (bucket != null) {
|
||||
newMap.put(inflowVo.getName(), bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.adapterBucketList = newMap;
|
||||
}
|
||||
|
||||
private void initInterface() throws Exception {
|
||||
Map<String, CustomBucket> newMap = new HashMap<>();
|
||||
List<InflowTargetVO> inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList();
|
||||
|
||||
for (InflowTargetVO inflowVo : inflowInterfaceVoList) {
|
||||
if (InflowControlManager.isInflowTarget(inflowVo)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowVo);
|
||||
if (bucket != null) {
|
||||
newMap.put(inflowVo.getName(), bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.interfaceBucketList = newMap;
|
||||
}
|
||||
|
||||
private void initGroup() throws Exception {
|
||||
Map<String, CustomGroupBucket> newGroupBucketList = new HashMap<>();
|
||||
Map<String, String> newInterfaceToGroupMap = new HashMap<>();
|
||||
Map<String, InflowGroupVO> newGroupVoMap = new HashMap<>();
|
||||
|
||||
List<InflowGroupVO> inflowGroupVoList = inflowControlDAO.getInflowGroupList();
|
||||
|
||||
for (InflowGroupVO groupVo : inflowGroupVoList) {
|
||||
if (isInflowGroupTarget(groupVo)) {
|
||||
CustomGroupBucket bucket = makeGroupBucket(groupVo);
|
||||
if (bucket != null) {
|
||||
newGroupBucketList.put(groupVo.getGroupId(), bucket);
|
||||
newGroupVoMap.put(groupVo.getGroupId(), groupVo);
|
||||
// 인터페이스 → 그룹 매핑 등록
|
||||
for (String interfaceId : groupVo.getInterfaceList()) {
|
||||
newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isInfo()) {
|
||||
logger.info("InflowControlManager] initGroup completed. groups=" + newGroupBucketList.size()
|
||||
+ ", mappings=" + newInterfaceToGroupMap.size());
|
||||
}
|
||||
|
||||
this.groupBucketList = newGroupBucketList;
|
||||
this.interfaceToGroupMap = newInterfaceToGroupMap;
|
||||
this.groupVoMap = newGroupVoMap;
|
||||
}
|
||||
|
||||
public synchronized void reloadAdapter() throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all Started ...");
|
||||
}
|
||||
initAdapter();
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadInterface() throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all Started ...");
|
||||
}
|
||||
initInterface();
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload all finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadAdapter(String adapter) throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload Started ...");
|
||||
}
|
||||
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByAdater(adapter);
|
||||
if (map != null) {
|
||||
Iterator<String> it = map.keySet().iterator();
|
||||
String key = "";
|
||||
while (it.hasNext()) {
|
||||
key = it.next();
|
||||
|
||||
InflowTargetVO inflowTarget = map.get(key);
|
||||
if (InflowControlManager.isInflowTarget(inflowTarget)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
|
||||
if (bucket != null) {
|
||||
adapterBucketList.put(inflowTarget.getName(), bucket);
|
||||
} else {
|
||||
adapterBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
} else {
|
||||
adapterBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadInterface(String inter) throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload Started ...");
|
||||
}
|
||||
Map<String, InflowTargetVO> map = inflowControlDAO.getInflowTargetByInterface(inter);
|
||||
if (map != null) {
|
||||
Iterator<String> it = map.keySet().iterator();
|
||||
String key = "";
|
||||
while (it.hasNext()) {
|
||||
key = it.next();
|
||||
|
||||
InflowTargetVO inflowTarget = map.get(key);
|
||||
if (InflowControlManager.isInflowTarget(inflowTarget)) {
|
||||
CustomBucket bucket = makeBucketUsingInflowVO(inflowTarget);
|
||||
if (bucket != null) {
|
||||
interfaceBucketList.put(inflowTarget.getName(), bucket);
|
||||
} else {
|
||||
interfaceBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
} else {
|
||||
interfaceBucketList.remove(inflowTarget.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reload finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadGroup() throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup all Started ...");
|
||||
}
|
||||
initGroup();
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup all finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void reloadGroup(String groupId) throws Exception {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup Started ... groupId=" + groupId);
|
||||
}
|
||||
Map<String, InflowGroupVO> map = inflowControlDAO.getInflowTargetByGroup(groupId);
|
||||
if (map != null) {
|
||||
// 기존 그룹에 속한 인터페이스 매핑 제거
|
||||
Iterator<Map.Entry<String, String>> mappingIt = interfaceToGroupMap.entrySet().iterator();
|
||||
while (mappingIt.hasNext()) {
|
||||
Map.Entry<String, String> entry = mappingIt.next();
|
||||
if (groupId.equals(entry.getValue())) {
|
||||
mappingIt.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<String> it = map.keySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
String key = it.next();
|
||||
InflowGroupVO groupVo = map.get(key);
|
||||
|
||||
if (isInflowGroupTarget(groupVo)) {
|
||||
CustomGroupBucket bucket = makeGroupBucket(groupVo);
|
||||
if (bucket != null) {
|
||||
groupBucketList.put(groupVo.getGroupId(), bucket);
|
||||
groupVoMap.put(groupVo.getGroupId(), groupVo);
|
||||
// 인터페이스 → 그룹 매핑 재등록
|
||||
for (String interfaceId : groupVo.getInterfaceList()) {
|
||||
interfaceToGroupMap.put(interfaceId, groupVo.getGroupId());
|
||||
}
|
||||
} else {
|
||||
groupBucketList.remove(groupVo.getGroupId());
|
||||
groupVoMap.remove(groupVo.getGroupId());
|
||||
}
|
||||
} else {
|
||||
groupBucketList.remove(groupVo.getGroupId());
|
||||
groupVoMap.remove(groupVo.getGroupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("InflowControlManager] reloadGroup finished ...");
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() throws LifecycleException {
|
||||
// Validate and update our current component state
|
||||
if (!started)
|
||||
throw new LifecycleException("RECEAICMM203");
|
||||
|
||||
// Notify our interested LifecycleListeners
|
||||
lifecycle.fireLifecycleEvent(STOPING_EVENT, this);
|
||||
|
||||
adapterBucketList = new ConcurrentHashMap<>();
|
||||
interfaceBucketList = new ConcurrentHashMap<>();
|
||||
groupBucketList = new ConcurrentHashMap<>();
|
||||
interfaceToGroupMap = new ConcurrentHashMap<>();
|
||||
groupVoMap = new ConcurrentHashMap<>();
|
||||
|
||||
started = false;
|
||||
// Notify our interested LifecycleListeners
|
||||
lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
|
||||
}
|
||||
|
||||
public void addLifecycleListener(LifecycleListener listener) {
|
||||
lifecycle.addLifecycleListener(listener);
|
||||
}
|
||||
|
||||
public LifecycleListener[] findLifecycleListeners() {
|
||||
return lifecycle.findLifecycleListeners();
|
||||
}
|
||||
|
||||
public void removeLifecycleListener(LifecycleListener listener) {
|
||||
lifecycle.removeLifecycleListener(listener);
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return this.started;
|
||||
}
|
||||
|
||||
public String[] getAdapterAllKeys() {
|
||||
Iterator<String> it = this.adapterBucketList.keySet().iterator();
|
||||
String[] svcCd = new String[this.adapterBucketList.size()];
|
||||
for (int i = 0; it.hasNext(); i++) {
|
||||
svcCd[i] = it.next();
|
||||
}
|
||||
Arrays.sort(svcCd);
|
||||
return svcCd;
|
||||
}
|
||||
|
||||
public String[] getInterfaceAllKeys() {
|
||||
Iterator<String> it = this.interfaceBucketList.keySet().iterator();
|
||||
String[] svcCd = new String[this.interfaceBucketList.size()];
|
||||
for (int i = 0; it.hasNext(); i++) {
|
||||
svcCd[i] = it.next();
|
||||
}
|
||||
Arrays.sort(svcCd);
|
||||
return svcCd;
|
||||
}
|
||||
|
||||
public synchronized void removeAdapter(String adapter) {
|
||||
adapterBucketList.remove(adapter);
|
||||
}
|
||||
|
||||
public synchronized void removeInterface(String inter) {
|
||||
interfaceBucketList.remove(inter);
|
||||
}
|
||||
|
||||
public synchronized void removeGroup(String groupId) {
|
||||
groupBucketList.remove(groupId);
|
||||
groupVoMap.remove(groupId);
|
||||
// 해당 그룹에 속한 인터페이스 매핑도 제거
|
||||
Iterator<Map.Entry<String, String>> it = interfaceToGroupMap.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, String> entry = it.next();
|
||||
if (groupId.equals(entry.getValue())) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getGroupAllKeys() {
|
||||
Iterator<String> it = this.groupBucketList.keySet().iterator();
|
||||
String[] groupIds = new String[this.groupBucketList.size()];
|
||||
for (int i = 0; it.hasNext(); i++) {
|
||||
groupIds[i] = it.next();
|
||||
}
|
||||
Arrays.sort(groupIds);
|
||||
return groupIds;
|
||||
}
|
||||
|
||||
public CustomGroupBucket getGroupBucket(String groupId) {
|
||||
return groupBucketList.get(groupId);
|
||||
}
|
||||
|
||||
private CustomBucket makeBucketUsingInflowVO(InflowTargetVO inflowTarget) {
|
||||
if (InflowControlManager.isInflowTarget(inflowTarget)) {
|
||||
LocalBucketBuilder builder = Bucket4j.builder();
|
||||
if (inflowTarget.getThresholdPerSecond() > 0) {
|
||||
builder.addLimit(Bandwidth.simple(inflowTarget.getThresholdPerSecond(), Duration.ofSeconds(1)));
|
||||
}
|
||||
|
||||
if (inflowTarget.getThreshold() > 0 && StringUtils.isNotBlank(inflowTarget.getThresholdTimeUnit())) {
|
||||
builder.addLimit(Bandwidth.simple(inflowTarget.getThreshold(),
|
||||
InflowControlManager.getPeriod(inflowTarget.getThresholdTimeUnit())));
|
||||
}
|
||||
|
||||
return new CustomBucket(builder.build(), inflowTarget);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 그룹용 버킷 생성 - 초당/추가 임계치를 별도 버킷으로 분리
|
||||
*/
|
||||
private CustomGroupBucket makeGroupBucket(InflowGroupVO groupVo) {
|
||||
if (!isInflowGroupTarget(groupVo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalBucket perSecondBucket = null;
|
||||
LocalBucket thresholdBucket = null;
|
||||
|
||||
// 초당 임계치 버킷 (별도)
|
||||
if (groupVo.getThresholdPerSecond() > 0) {
|
||||
perSecondBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(groupVo.getThresholdPerSecond(), Duration.ofSeconds(1)))
|
||||
.build();
|
||||
}
|
||||
|
||||
// 추가 임계치 버킷 (별도)
|
||||
if (groupVo.getThreshold() > 0 && StringUtils.isNotBlank(groupVo.getThresholdTimeUnit())) {
|
||||
thresholdBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(groupVo.getThreshold(),
|
||||
InflowControlManager.getPeriod(groupVo.getThresholdTimeUnit())))
|
||||
.build();
|
||||
}
|
||||
|
||||
return new CustomGroupBucket(perSecondBucket, thresholdBucket, groupVo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAdapterPass(String adapter) {
|
||||
CustomBucket b = adapterBucketList.get(adapter);
|
||||
|
||||
if (b == null)
|
||||
return true;
|
||||
|
||||
return b.getLocalBucket().tryConsume(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterfacePass(String inter) {
|
||||
CustomBucket b = interfaceBucketList.get(inter);
|
||||
|
||||
if (b == null)
|
||||
return true;
|
||||
|
||||
return b.getLocalBucket().tryConsume(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
|
||||
CustomBucket b = adapterBucketList.get(adapter);
|
||||
if (b == null)
|
||||
return null;
|
||||
return b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
|
||||
CustomBucket b = interfaceBucketList.get(inter);
|
||||
if (b == null)
|
||||
return null;
|
||||
return b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getInflowThreashold(String inter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public InflowTargetVO getAdapterInflow(String adapter) {
|
||||
return getAdapterInflowThreashold(adapter);
|
||||
}
|
||||
|
||||
public InflowTargetVO getInterfaceInflow(String inter) {
|
||||
return getInterfaceInflowThreashold(inter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String isGroupPass(String groupId) {
|
||||
CustomGroupBucket b = groupBucketList.get(groupId);
|
||||
|
||||
if (b == null)
|
||||
return null;
|
||||
|
||||
return b.tryConsume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowGroupVO getGroupInflowThreshold(String groupId) {
|
||||
return groupVoMap.get(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getGroupIdByInterface(String interfaceId) {
|
||||
return interfaceToGroupMap.get(interfaceId);
|
||||
}
|
||||
|
||||
public InflowGroupVO getGroupInflow(String groupId) {
|
||||
return getGroupInflowThreshold(groupId);
|
||||
}
|
||||
|
||||
public static boolean isInflowTarget(InflowTargetVO inflowTarget) {
|
||||
if (inflowTarget == null || !inflowTarget.isActivate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return inflowTarget.getThresholdPerSecond() > 0 || (inflowTarget.getThreshold() > 0 && StringUtils
|
||||
.equalsAnyIgnoreCase(inflowTarget.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
|
||||
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
|
||||
|
||||
}
|
||||
|
||||
public static boolean isInflowGroupTarget(InflowGroupVO groupVo) {
|
||||
if (groupVo == null || !groupVo.isActivate()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return groupVo.getThresholdPerSecond() > 0 || (groupVo.getThreshold() > 0 && StringUtils
|
||||
.equalsAnyIgnoreCase(groupVo.getThresholdTimeUnit(), QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_SECOND,
|
||||
QUOTA_TIMEUNIT_MINUTE, QUOTA_TIMEUNIT_HOUR, QUOTA_TIMEUNIT_DAY, QUOTA_TIMEUNIT_MONTH));
|
||||
}
|
||||
|
||||
public static Duration getPeriod(String timeUnit) {
|
||||
if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_SECOND)) {
|
||||
return Duration.ofSeconds(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MINUTE)) {
|
||||
return Duration.ofMinutes(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_HOUR)) {
|
||||
return Duration.ofHours(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_DAY)) {
|
||||
return Duration.ofDays(1);
|
||||
} else if (StringUtils.equalsIgnoreCase(timeUnit, QUOTA_TIMEUNIT_MONTH)) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
return Duration.ofDays(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import com.eactive.eai.common.message.EAIMessage;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.message.service.InterfaceMapper;
|
||||
import com.ext.eai.common.stdmessage.STDMessageKeys;
|
||||
|
||||
public class InflowControlUtil {
|
||||
private InflowControlUtil() {
|
||||
@@ -16,28 +19,70 @@ public class InflowControlUtil {
|
||||
|
||||
static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
private static volatile AbstractInflowControlManager cachedInflowControlManager;
|
||||
private static volatile Bucket cachedBucket;
|
||||
|
||||
/**
|
||||
* INFLOW.properties의 inflow.control.bucket.className 설정에 따라
|
||||
* 활성화된 InflowControlManager 구현체를 반환한다.
|
||||
*
|
||||
* getBean(InflowControlManager.class) 대신 설정된 구체 타입으로 조회하므로
|
||||
* InflowControlManager와 DualInflowControlManager가 모두 Bean으로 등록된
|
||||
* 환경에서도 NoUniqueBeanDefinitionException 없이 올바른 인스턴스를 반환한다.
|
||||
*
|
||||
* 최초 1회만 조회하고 이후에는 캐시된 인스턴스를 반환한다.
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static Bucket getBucket() {
|
||||
public static AbstractInflowControlManager getInflowControlManager() {
|
||||
if (cachedInflowControlManager == null) {
|
||||
synchronized (InflowControlUtil.class) {
|
||||
if (cachedInflowControlManager == null) {
|
||||
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
|
||||
String className = inFlowProp.getProperty("inflow.control.bucket.className");
|
||||
if (StringUtils.isBlank(className)
|
||||
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
|
||||
return InflowControlManager.getBucket();
|
||||
cachedInflowControlManager = InflowControlManager.getInstance();
|
||||
} else {
|
||||
try {
|
||||
Class clazz = Class.forName(className);
|
||||
cachedInflowControlManager = (AbstractInflowControlManager) ApplicationContextProvider.getContext().getBean(clazz);
|
||||
} catch (Exception e) {
|
||||
logger.error("getInflowControlManager error class= {}", className);
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cachedInflowControlManager;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static Bucket getBucket() {
|
||||
if (cachedBucket == null) {
|
||||
synchronized (InflowControlUtil.class) {
|
||||
if (cachedBucket == null) {
|
||||
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
|
||||
String className = inFlowProp.getProperty("inflow.control.bucket.className");
|
||||
if (StringUtils.isBlank(className)
|
||||
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
|
||||
cachedBucket = InflowControlManager.getBucket();
|
||||
} else {
|
||||
try {
|
||||
Class clazz = Class.forName(className);
|
||||
Method method = clazz.getMethod("getBucket", (Class<?>[]) null);
|
||||
return (Bucket) method.invoke(null, (Object[]) null);
|
||||
cachedBucket = (Bucket) method.invoke(null, (Object[]) null);
|
||||
} catch (Exception e) {
|
||||
logger.error("Method call error class= {}, method= getBucket", className);
|
||||
logger.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cachedBucket;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) {
|
||||
Properties inFlowProp = PropManager.getInstance().getProperties("INFLOW");
|
||||
String controlInflow = inFlowProp.getProperty("inflow.control.yn", "N");
|
||||
@@ -46,20 +91,17 @@ public class InflowControlUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
String className = inFlowProp.getProperty("inflow.control.bucket.className");
|
||||
if (StringUtils.isBlank(className)
|
||||
|| StringUtils.equals(className, "com.eactive.eai.common.inflow.InflowControlManager")) {
|
||||
return InflowControlManager.isTargetOfInflowControl(eaiServerManager, eaiMessage).booleanValue();
|
||||
} else {
|
||||
try {
|
||||
Class clazz = Class.forName(className);
|
||||
Method method = clazz.getMethod("isTargetOfInflowControl", EAIServerManager.class, EAIMessage.class);
|
||||
Boolean returnBoolean = (Boolean) method.invoke(null, eaiServerManager, eaiMessage);
|
||||
return returnBoolean.booleanValue();
|
||||
} catch (Exception e) {
|
||||
logger.error("Method call error class= {}, method= isTargetOfInflowControl", className);
|
||||
logger.error(e.getMessage());
|
||||
InterfaceMapper mapper = eaiMessage.getMapper();
|
||||
String returnType = mapper.getSendRecvDivision(eaiMessage.getStandardMessage()); // S|R
|
||||
|
||||
if (STDMessageKeys.SEND_RECV_CD_SEND.equals(returnType)) {
|
||||
return Boolean.valueOf(true);
|
||||
}
|
||||
|
||||
return Boolean.valueOf(false);
|
||||
} catch (Exception e) {
|
||||
logger.error("isTargetOfInflowControl call error", e.getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.eactive.eai.common.inflow.AbstractInflowControlManager;
|
||||
import com.eactive.eai.common.inflow.Bucket;
|
||||
import com.eactive.eai.common.inflow.CustomGroupBucket;
|
||||
import com.eactive.eai.common.inflow.InflowControlDAO;
|
||||
import com.eactive.eai.common.inflow.InflowControlManager;
|
||||
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.message.EAIMessage;
|
||||
import com.eactive.eai.common.server.EAIServerManager;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
|
||||
@@ -41,7 +37,7 @@ import io.github.bucket4j.local.LocalBucket;
|
||||
* <pre>inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.DualInflowControlManager</pre>
|
||||
*/
|
||||
@Component
|
||||
public class DualInflowControlManager extends InflowControlManager implements DualBucket {
|
||||
public class DualInflowControlManager extends AbstractInflowControlManager implements DualBucket {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
|
||||
|
||||
@@ -50,17 +46,13 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
return ApplicationContextProvider.getContext().getBean(DualInflowControlManager.class);
|
||||
}
|
||||
|
||||
public static Boolean isTargetOfInflowControl(EAIServerManager eaiServerManager, EAIMessage eaiMessage) {
|
||||
return InflowControlManager.isTargetOfInflowControl(eaiServerManager, eaiMessage);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private InflowControlDAO dualDao;
|
||||
|
||||
private volatile Map<String, DualCustomBucket> adapterBucketMap = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, DualCustomBucket> interfaceBucketMap = new ConcurrentHashMap<>();
|
||||
private volatile Map<String, DualCustomBucket> clientBucketMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<String, TargetMetrics> adapterMetricsMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> interfaceMetricsMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> clientMetricsMap = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TargetMetrics> groupMetricsMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static class TargetMetrics {
|
||||
@@ -114,7 +106,7 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
|
||||
private Map<String, DualCustomBucket> loadBucketMap(InflowType type) throws Exception {
|
||||
Map<String, DualCustomBucket> newMap = new HashMap<>();
|
||||
for (InflowTargetVO vo : dualDao.getInflowList(type)) {
|
||||
for (InflowTargetVO vo : inflowControlDAO.getInflowList(type)) {
|
||||
DualCustomBucket bucket = makeBucket(vo);
|
||||
if (bucket != null) newMap.put(vo.getName(), bucket);
|
||||
}
|
||||
@@ -123,34 +115,44 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
|
||||
@Override
|
||||
public synchronized void reloadAdapter() throws Exception {
|
||||
super.reloadAdapter();
|
||||
initAdapters();
|
||||
adapterMetricsMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reloadAdapter(String adapter) throws Exception {
|
||||
super.reloadAdapter(adapter);
|
||||
reloadBucketMap(adapterBucketMap, InflowType.ADAPTER, adapter);
|
||||
TargetMetrics m = adapterMetricsMap.get(adapter);
|
||||
if (m != null) m.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reloadInterface() throws Exception {
|
||||
super.reloadInterface();
|
||||
initInterfaces();
|
||||
interfaceMetricsMap.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reloadInterface(String inter) throws Exception {
|
||||
super.reloadInterface(inter);
|
||||
reloadBucketMap(interfaceBucketMap, InflowType.INTERFACE, inter);
|
||||
TargetMetrics m = interfaceMetricsMap.get(inter);
|
||||
if (m != null) m.reset();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,7 +169,7 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
}
|
||||
|
||||
private void reloadBucketMap(Map<String, DualCustomBucket> targetMap, InflowType type, String name) throws Exception {
|
||||
Map<String, InflowTargetVO> updated = dualDao.getInflowMap(type, name);
|
||||
Map<String, InflowTargetVO> updated = inflowControlDAO.getInflowMap(type, name);
|
||||
if (updated == null) return;
|
||||
for (InflowTargetVO vo : updated.values()) {
|
||||
DualCustomBucket bucket = makeBucket(vo);
|
||||
@@ -176,6 +178,26 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 어댑터/인터페이스/클라이언트 메트릭 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
public TargetMetrics getAdapterMetrics(String adapter) { return adapterMetricsMap.get(adapter); }
|
||||
public TargetMetrics getInterfaceMetrics(String inter) { return interfaceMetricsMap.get(inter); }
|
||||
public TargetMetrics getClientMetrics(String clientId) { return clientMetricsMap.get(clientId); }
|
||||
|
||||
public Map<String, TargetMetrics> getAllAdapterMetrics() { return Collections.unmodifiableMap(adapterMetricsMap); }
|
||||
public Map<String, TargetMetrics> getAllInterfaceMetrics() { return Collections.unmodifiableMap(interfaceMetricsMap); }
|
||||
public Map<String, TargetMetrics> getAllClientMetrics() { return Collections.unmodifiableMap(clientMetricsMap); }
|
||||
|
||||
public void resetAdapterMetrics(String adapter) { TargetMetrics m = adapterMetricsMap.get(adapter); if (m != null) m.reset(); }
|
||||
public void resetInterfaceMetrics(String inter) { TargetMetrics m = interfaceMetricsMap.get(inter); if (m != null) m.reset(); }
|
||||
public void resetClientMetrics(String clientId) { TargetMetrics m = clientMetricsMap.get(clientId); if (m != null) m.reset(); }
|
||||
|
||||
public void resetAllAdapterMetrics() { adapterMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
public void resetAllInterfaceMetrics() { interfaceMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
public void resetAllClientMetrics() { clientMetricsMap.values().forEach(TargetMetrics::reset); }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 그룹 메트릭 — isGroupPass() 카운팅 및 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -218,19 +240,35 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
@Override
|
||||
public String isAdapterPassDetail(String adapter) {
|
||||
DualCustomBucket b = adapterBucketMap.get(adapter);
|
||||
return (b == null) ? DualCustomBucket.RESULT_PASS : b.tryConsume();
|
||||
if (b == null) return DualCustomBucket.RESULT_PASS;
|
||||
String result = b.tryConsume();
|
||||
recordMetrics(adapterMetricsMap, adapter, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String isInterfacePassDetail(String inter) {
|
||||
DualCustomBucket b = interfaceBucketMap.get(inter);
|
||||
return (b == null) ? DualCustomBucket.RESULT_PASS : b.tryConsume();
|
||||
if (b == null) return DualCustomBucket.RESULT_PASS;
|
||||
String result = b.tryConsume();
|
||||
recordMetrics(interfaceMetricsMap, inter, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String isClientPassDetail(String clientId) {
|
||||
DualCustomBucket b = clientBucketMap.get(clientId);
|
||||
return (b == null) ? DualCustomBucket.RESULT_PASS : b.tryConsume();
|
||||
if (b == null) return DualCustomBucket.RESULT_PASS;
|
||||
String result = b.tryConsume();
|
||||
recordMetrics(clientMetricsMap, clientId, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void recordMetrics(ConcurrentHashMap<String, TargetMetrics> metricsMap, String key, String result) {
|
||||
TargetMetrics m = metricsMap.computeIfAbsent(key, k -> new TargetMetrics());
|
||||
if (result == null) m.allowed.incrementAndGet();
|
||||
else if (DualCustomBucket.RESULT_BLOCKED_PER_SECOND.equals(result)) m.rejectedPerSecond.incrementAndGet();
|
||||
else m.rejectedThreshold.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -255,6 +293,49 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
return isInterfacePassDetail(inter) == DualCustomBucket.RESULT_PASS;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 어댑터/인터페이스 키·VO·삭제 — Dual 맵 기준으로 오버라이드
|
||||
// (base의 adapterBucketList / interfaceBucketList 는 읽지 않아 DAO 이중 호출 제거)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public String[] getAdapterAllKeys() {
|
||||
String[] keys = adapterBucketMap.keySet().toArray(new String[0]);
|
||||
Arrays.sort(keys);
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getInterfaceAllKeys() {
|
||||
String[] keys = interfaceBucketMap.keySet().toArray(new String[0]);
|
||||
Arrays.sort(keys);
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getAdapterInflowThreashold(String adapter) {
|
||||
DualCustomBucket b = adapterBucketMap.get(adapter);
|
||||
return (b == null) ? null : b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InflowTargetVO getInterfaceInflowThreashold(String inter) {
|
||||
DualCustomBucket b = interfaceBucketMap.get(inter);
|
||||
return (b == null) ? null : b.getInflowTargetVo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeAdapter(String adapter) {
|
||||
adapterBucketMap.remove(adapter);
|
||||
adapterMetricsMap.remove(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeInterface(String inter) {
|
||||
interfaceBucketMap.remove(inter);
|
||||
interfaceMetricsMap.remove(inter);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// 모니터링용 접근자
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -288,7 +369,7 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private DualCustomBucket makeBucket(InflowTargetVO vo) {
|
||||
if (!InflowControlManager.isInflowTarget(vo)) {
|
||||
if (!AbstractInflowControlManager.isInflowTarget(vo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -304,7 +385,7 @@ public class DualInflowControlManager extends InflowControlManager implements Du
|
||||
if (vo.getThreshold() > 0 && StringUtils.isNotBlank(vo.getThresholdTimeUnit())) {
|
||||
thresholdBucket = Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(vo.getThreshold(),
|
||||
InflowControlManager.getPeriod(vo.getThresholdTimeUnit())))
|
||||
AbstractInflowControlManager.getPeriod(vo.getThresholdTimeUnit())))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.eactive.eai.agent.inflow;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.eactive.eai.agent.command.CommandException;
|
||||
import com.eactive.eai.common.inflow.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
|
||||
|
||||
/**
|
||||
* ReloadInflowClientControlCommand 단위 테스트.
|
||||
*
|
||||
* <p>InflowControlUtil의 cachedInflowControlManager 필드에 mock을 직접 주입하여
|
||||
* PropManager/ApplicationContext 없이 동작을 검증한다.
|
||||
*/
|
||||
class ReloadInflowClientControlCommandTest {
|
||||
|
||||
private DualInflowControlManager mockDualManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockDualManager = mock(DualInflowControlManager.class);
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", null);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", null);
|
||||
}
|
||||
|
||||
private ReloadInflowClientControlCommand commandWith(Object args) {
|
||||
ReloadInflowClientControlCommand cmd = new ReloadInflowClientControlCommand();
|
||||
cmd.setArgs(args);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// args 타입 검증 실패
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void execute_argsIsInteger_throwsCommandException() {
|
||||
// args 검증이 getInflowControlManager() 호출 이전에 발생하므로 캐시 설정 불필요
|
||||
assertThrows(CommandException.class, commandWith(Integer.valueOf(1))::execute);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DualInflowControlManager 비활성
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void execute_notDualManager_throwsCommandException() {
|
||||
InflowControlManager baseMgr = mock(InflowControlManager.class);
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", baseMgr);
|
||||
|
||||
assertThrows(CommandException.class, commandWith("CLIENT_A")::execute);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 전체 재로드 (ALL)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void execute_argsIsALL_callsReloadClientNoArgs() throws Exception {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", mockDualManager);
|
||||
|
||||
Object result = commandWith("ALL").execute();
|
||||
|
||||
assertEquals("success", result);
|
||||
verify(mockDualManager).reloadClient();
|
||||
verify(mockDualManager, never()).reloadClient(anyString());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 개별 재로드 (ClientId)
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void execute_argsIsClientId_callsReloadClientWithId() throws Exception {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", mockDualManager);
|
||||
|
||||
Object result = commandWith("CLIENT_A").execute();
|
||||
|
||||
assertEquals("success", result);
|
||||
verify(mockDualManager).reloadClient("CLIENT_A");
|
||||
verify(mockDualManager, never()).reloadClient();
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_differentClientId_callsReloadClientWithThatId() throws Exception {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", mockDualManager);
|
||||
|
||||
commandWith("CLIENT_B").execute();
|
||||
|
||||
verify(mockDualManager).reloadClient("CLIENT_B");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.eactive.eai.agent.inflow;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.eactive.eai.agent.command.CommandException;
|
||||
import com.eactive.eai.common.inflow.InflowControlManager;
|
||||
import com.eactive.eai.common.inflow.InflowControlUtil;
|
||||
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
|
||||
|
||||
/**
|
||||
* RemoveInflowClientControlCommand 단위 테스트.
|
||||
*
|
||||
* <p>InflowControlUtil의 cachedInflowControlManager 필드에 mock을 직접 주입하여
|
||||
* PropManager/ApplicationContext 없이 동작을 검증한다.
|
||||
*/
|
||||
class RemoveInflowClientControlCommandTest {
|
||||
|
||||
private DualInflowControlManager mockDualManager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockDualManager = mock(DualInflowControlManager.class);
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", null);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", null);
|
||||
}
|
||||
|
||||
private RemoveInflowClientControlCommand commandWith(Object args) {
|
||||
RemoveInflowClientControlCommand cmd = new RemoveInflowClientControlCommand();
|
||||
cmd.setArgs(args);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// args 타입 검증 실패
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void execute_argsIsInteger_throwsCommandException() {
|
||||
assertThrows(CommandException.class, commandWith(Integer.valueOf(1))::execute);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DualInflowControlManager 비활성
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void execute_notDualManager_throwsCommandException() {
|
||||
InflowControlManager baseMgr = mock(InflowControlManager.class);
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", baseMgr);
|
||||
|
||||
assertThrows(CommandException.class, commandWith("CLIENT_A")::execute);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 정상 제거
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void execute_validClientId_callsRemoveClient() throws CommandException {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", mockDualManager);
|
||||
|
||||
Object result = commandWith("CLIENT_A").execute();
|
||||
|
||||
assertEquals("success", result);
|
||||
verify(mockDualManager).removeClient("CLIENT_A");
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_differentClientId_callsRemoveClientWithThatId() throws Exception {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", mockDualManager);
|
||||
|
||||
commandWith("CLIENT_B").execute();
|
||||
|
||||
verify(mockDualManager).removeClient("CLIENT_B");
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_validClientId_doesNotCallReload() throws Exception {
|
||||
ReflectionTestUtils.setField(InflowControlUtil.class, "cachedInflowControlManager", mockDualManager);
|
||||
|
||||
commandWith("CLIENT_A").execute();
|
||||
|
||||
verify(mockDualManager, never()).reloadClient();
|
||||
verify(mockDualManager, never()).reloadClient(anyString());
|
||||
}
|
||||
}
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
package com.eactive.eai.common.inflow.dual;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.time.Duration;
|
||||
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;
|
||||
|
||||
/**
|
||||
* DualInflowControlManager의 Dual 맵 기반 오버라이드 메서드 단위 테스트.
|
||||
*
|
||||
* <p>검증 포인트:
|
||||
* - getAdapterAllKeys / getInterfaceAllKeys 가 Dual 맵(adapterBucketMap/interfaceBucketMap) 을 사용
|
||||
* - getAdapterInflowThreashold / getInterfaceInflowThreashold 가 Dual 맵의 VO 를 반환
|
||||
* - removeAdapter / removeInterface 가 Dual 맵에서 항목을 제거
|
||||
* - base 의 adapterBucketList / interfaceBucketList 상태와 무관하게 동작 (DAO 이중 호출 제거 효과)
|
||||
*/
|
||||
class DualInflowControlManagerBucketMethodsTest {
|
||||
|
||||
private DualInflowControlManager manager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
manager = new DualInflowControlManager();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private InflowTargetVO makeVO(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 bucket(long capacity) {
|
||||
return Bucket4j.builder()
|
||||
.addLimit(Bandwidth.simple(capacity, Duration.ofSeconds(1)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private DualCustomBucket dualBucket(String name) {
|
||||
return new DualCustomBucket(bucket(100), bucket(1000), makeVO(name));
|
||||
}
|
||||
|
||||
private void setAdapterMap(ConcurrentHashMap<String, DualCustomBucket> map) {
|
||||
ReflectionTestUtils.setField(manager, "adapterBucketMap", map);
|
||||
}
|
||||
|
||||
private void setInterfaceMap(ConcurrentHashMap<String, DualCustomBucket> map) {
|
||||
ReflectionTestUtils.setField(manager, "interfaceBucketMap", map);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// getAdapterAllKeys
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void getAdapterAllKeys_emptyMap_returnsEmptyArray() {
|
||||
setAdapterMap(new ConcurrentHashMap<>());
|
||||
|
||||
assertEquals(0, manager.getAdapterAllKeys().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAdapterAllKeys_twoEntries_returnsSortedKeys() {
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("ADAPTER_B", dualBucket("ADAPTER_B"));
|
||||
map.put("ADAPTER_A", dualBucket("ADAPTER_A"));
|
||||
setAdapterMap(map);
|
||||
|
||||
String[] keys = manager.getAdapterAllKeys();
|
||||
|
||||
assertArrayEquals(new String[]{"ADAPTER_A", "ADAPTER_B"}, keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAdapterAllKeys_usesDualMap_notBaseList() {
|
||||
// Dual 맵에만 항목 존재 — base 의 adapterBucketList 는 비어 있음
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("DUAL_ONLY", dualBucket("DUAL_ONLY"));
|
||||
setAdapterMap(map);
|
||||
|
||||
String[] keys = manager.getAdapterAllKeys();
|
||||
|
||||
assertEquals(1, keys.length);
|
||||
assertEquals("DUAL_ONLY", keys[0]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// getInterfaceAllKeys
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void getInterfaceAllKeys_emptyMap_returnsEmptyArray() {
|
||||
setInterfaceMap(new ConcurrentHashMap<>());
|
||||
|
||||
assertEquals(0, manager.getInterfaceAllKeys().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInterfaceAllKeys_threeEntries_returnsSortedKeys() {
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF_C", dualBucket("IF_C"));
|
||||
map.put("IF_A", dualBucket("IF_A"));
|
||||
map.put("IF_B", dualBucket("IF_B"));
|
||||
setInterfaceMap(map);
|
||||
|
||||
String[] keys = manager.getInterfaceAllKeys();
|
||||
|
||||
assertArrayEquals(new String[]{"IF_A", "IF_B", "IF_C"}, keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInterfaceAllKeys_usesDualMap_notBaseList() {
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("DUAL_IF", dualBucket("DUAL_IF"));
|
||||
setInterfaceMap(map);
|
||||
|
||||
String[] keys = manager.getInterfaceAllKeys();
|
||||
|
||||
assertEquals(1, keys.length);
|
||||
assertEquals("DUAL_IF", keys[0]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// getAdapterInflowThreashold
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void getAdapterInflowThreashold_found_returnsVO() {
|
||||
InflowTargetVO vo = makeVO("ADAPTER_A");
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("ADAPTER_A", new DualCustomBucket(bucket(10), bucket(100), vo));
|
||||
setAdapterMap(map);
|
||||
|
||||
InflowTargetVO result = manager.getAdapterInflowThreashold("ADAPTER_A");
|
||||
|
||||
assertSame(vo, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAdapterInflowThreashold_notFound_returnsNull() {
|
||||
setAdapterMap(new ConcurrentHashMap<>());
|
||||
|
||||
assertNull(manager.getAdapterInflowThreashold("MISSING"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAdapterInflowThreashold_usesDualMap_notBaseList() {
|
||||
// base adapterBucketList 에는 항목 없고 Dual 맵에만 존재
|
||||
InflowTargetVO vo = makeVO("ADAPTER_A");
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("ADAPTER_A", new DualCustomBucket(null, null, vo));
|
||||
setAdapterMap(map);
|
||||
|
||||
assertSame(vo, manager.getAdapterInflowThreashold("ADAPTER_A"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// getInterfaceInflowThreashold
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void getInterfaceInflowThreashold_found_returnsVO() {
|
||||
InflowTargetVO vo = makeVO("IF_001");
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF_001", new DualCustomBucket(bucket(10), bucket(100), vo));
|
||||
setInterfaceMap(map);
|
||||
|
||||
assertSame(vo, manager.getInterfaceInflowThreashold("IF_001"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInterfaceInflowThreashold_notFound_returnsNull() {
|
||||
setInterfaceMap(new ConcurrentHashMap<>());
|
||||
|
||||
assertNull(manager.getInterfaceInflowThreashold("MISSING"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getInterfaceInflowThreashold_usesDualMap_notBaseList() {
|
||||
InflowTargetVO vo = makeVO("IF_001");
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF_001", new DualCustomBucket(null, null, vo));
|
||||
setInterfaceMap(map);
|
||||
|
||||
assertSame(vo, manager.getInterfaceInflowThreashold("IF_001"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// removeAdapter
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void removeAdapter_existingKey_removedFromDualMap() {
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("ADAPTER_A", dualBucket("ADAPTER_A"));
|
||||
map.put("ADAPTER_B", dualBucket("ADAPTER_B"));
|
||||
setAdapterMap(map);
|
||||
|
||||
manager.removeAdapter("ADAPTER_A");
|
||||
|
||||
assertNull(manager.getAdapterBucket("ADAPTER_A"));
|
||||
assertNotNull(manager.getAdapterBucket("ADAPTER_B"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAdapter_keysReflectRemoval() {
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("ADAPTER_A", dualBucket("ADAPTER_A"));
|
||||
map.put("ADAPTER_B", dualBucket("ADAPTER_B"));
|
||||
setAdapterMap(map);
|
||||
|
||||
manager.removeAdapter("ADAPTER_A");
|
||||
|
||||
String[] keys = manager.getAdapterAllKeys();
|
||||
assertEquals(1, keys.length);
|
||||
assertEquals("ADAPTER_B", keys[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeAdapter_missingKey_noException() {
|
||||
setAdapterMap(new ConcurrentHashMap<>());
|
||||
|
||||
assertDoesNotThrow(() -> manager.removeAdapter("NOT_EXISTS"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// removeInterface
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void removeInterface_existingKey_removedFromDualMap() {
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF_001", dualBucket("IF_001"));
|
||||
map.put("IF_002", dualBucket("IF_002"));
|
||||
setInterfaceMap(map);
|
||||
|
||||
manager.removeInterface("IF_001");
|
||||
|
||||
assertNull(manager.getInterfaceBucket("IF_001"));
|
||||
assertNotNull(manager.getInterfaceBucket("IF_002"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeInterface_keysReflectRemoval() {
|
||||
ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF_001", dualBucket("IF_001"));
|
||||
map.put("IF_002", dualBucket("IF_002"));
|
||||
setInterfaceMap(map);
|
||||
|
||||
manager.removeInterface("IF_001");
|
||||
|
||||
String[] keys = manager.getInterfaceAllKeys();
|
||||
assertEquals(1, keys.length);
|
||||
assertEquals("IF_002", keys[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeInterface_missingKey_noException() {
|
||||
setInterfaceMap(new ConcurrentHashMap<>());
|
||||
|
||||
assertDoesNotThrow(() -> manager.removeInterface("NOT_EXISTS"));
|
||||
}
|
||||
}
|
||||
+361
-3
@@ -12,16 +12,18 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import com.eactive.eai.common.inflow.CustomGroupBucket;
|
||||
import com.eactive.eai.common.inflow.InflowGroupVO;
|
||||
import com.eactive.eai.common.inflow.InflowTargetVO;
|
||||
|
||||
import io.github.bucket4j.Bandwidth;
|
||||
import io.github.bucket4j.Bucket4j;
|
||||
import io.github.bucket4j.local.LocalBucket;
|
||||
|
||||
/**
|
||||
* DualInflowControlManager 그룹 메트릭 단위 테스트.
|
||||
* DualInflowControlManager 메트릭 단위 테스트.
|
||||
*
|
||||
* <p>start() 없이 groupBucketList를 ReflectionTestUtils로 직접 주입하여
|
||||
* isGroupPass() 카운팅과 getGroupMetrics / reset 메서드를 검증한다.
|
||||
* <p>start() 없이 버킷 맵을 ReflectionTestUtils로 직접 주입하여
|
||||
* isAdapterPassDetail / isInterfacePassDetail / isClientPassDetail / isGroupPass() 의
|
||||
* 카운팅 동작과 getXxxMetrics / resetXxxMetrics 메서드를 검증한다.
|
||||
*/
|
||||
class DualInflowControlManagerMetricsTest {
|
||||
|
||||
@@ -68,6 +70,40 @@ class DualInflowControlManagerMetricsTest {
|
||||
return new CustomGroupBucket(stableBucket(100), exhaustedBucket(1), makeGroupVO(groupId, groupId + "-name"));
|
||||
}
|
||||
|
||||
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 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 injectAdapterMap(Map<String, DualCustomBucket> map) {
|
||||
ReflectionTestUtils.setField(manager, "adapterBucketMap", map);
|
||||
}
|
||||
|
||||
private void injectInterfaceMap(Map<String, DualCustomBucket> map) {
|
||||
ReflectionTestUtils.setField(manager, "interfaceBucketMap", map);
|
||||
}
|
||||
|
||||
private void injectClientMap(Map<String, DualCustomBucket> map) {
|
||||
ReflectionTestUtils.setField(manager, "clientBucketMap", map);
|
||||
}
|
||||
|
||||
private void injectGroupBucketList(Map<String, CustomGroupBucket> map) {
|
||||
ReflectionTestUtils.setField(manager, "groupBucketList", map);
|
||||
}
|
||||
@@ -315,4 +351,326 @@ class DualInflowControlManagerMetricsTest {
|
||||
assertNotNull(manager.getGroupMetrics("G1"));
|
||||
assertNotNull(manager.getGroupMetrics("G2"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// isAdapterPassDetail() — 카운터 증가
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void isAdapterPassDetail_bucketNotRegistered_noMetricsCreated() {
|
||||
// 버킷 없는 어댑터는 메트릭 맵에 항목을 생성하지 않음
|
||||
manager.isAdapterPassDetail("UNKNOWN");
|
||||
|
||||
assertNull(manager.getAdapterMetrics("UNKNOWN"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAdapterPassDetail_pass_incrementsAllowed() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("A1", passDualBucket("A1"));
|
||||
injectAdapterMap(map);
|
||||
|
||||
String result = manager.isAdapterPassDetail("A1");
|
||||
|
||||
assertNull(result);
|
||||
DualInflowControlManager.TargetMetrics m = manager.getAdapterMetrics("A1");
|
||||
assertNotNull(m);
|
||||
assertEquals(1, m.allowed.get());
|
||||
assertEquals(0, m.rejectedPerSecond.get());
|
||||
assertEquals(0, m.rejectedThreshold.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAdapterPassDetail_blockedPerSecond_incrementsRejectedPerSecond() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("A1", blockedPerSecondDualBucket("A1"));
|
||||
injectAdapterMap(map);
|
||||
|
||||
String result = manager.isAdapterPassDetail("A1");
|
||||
|
||||
assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, result);
|
||||
DualInflowControlManager.TargetMetrics m = manager.getAdapterMetrics("A1");
|
||||
assertEquals(0, m.allowed.get());
|
||||
assertEquals(1, m.rejectedPerSecond.get());
|
||||
assertEquals(0, m.rejectedThreshold.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAdapterPassDetail_blockedThreshold_incrementsRejectedThreshold() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("A1", blockedThresholdDualBucket("A1"));
|
||||
injectAdapterMap(map);
|
||||
|
||||
String result = manager.isAdapterPassDetail("A1");
|
||||
|
||||
assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, result);
|
||||
DualInflowControlManager.TargetMetrics m = manager.getAdapterMetrics("A1");
|
||||
assertEquals(0, m.allowed.get());
|
||||
assertEquals(0, m.rejectedPerSecond.get());
|
||||
assertEquals(1, m.rejectedThreshold.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAdapterPassDetail_multipleCalls_countersAccumulate() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
LocalBucket b = stableBucket(3);
|
||||
map.put("A1", new DualCustomBucket(b, null, makeTargetVO("A1")));
|
||||
injectAdapterMap(map);
|
||||
|
||||
for (int i = 0; i < 5; i++) manager.isAdapterPassDetail("A1");
|
||||
|
||||
DualInflowControlManager.TargetMetrics m = manager.getAdapterMetrics("A1");
|
||||
assertEquals(3, m.allowed.get());
|
||||
assertEquals(2, m.rejectedPerSecond.get());
|
||||
assertEquals(5, m.allowed.get() + m.rejectedPerSecond.get() + m.rejectedThreshold.get());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// isInterfacePassDetail() — 카운터 증가
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void isInterfacePassDetail_bucketNotRegistered_noMetricsCreated() {
|
||||
manager.isInterfacePassDetail("UNKNOWN");
|
||||
|
||||
assertNull(manager.getInterfaceMetrics("UNKNOWN"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isInterfacePassDetail_pass_incrementsAllowed() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF1", passDualBucket("IF1"));
|
||||
injectInterfaceMap(map);
|
||||
|
||||
manager.isInterfacePassDetail("IF1");
|
||||
|
||||
assertEquals(1, manager.getInterfaceMetrics("IF1").allowed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isInterfacePassDetail_blockedPerSecond_incrementsRejectedPerSecond() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF1", blockedPerSecondDualBucket("IF1"));
|
||||
injectInterfaceMap(map);
|
||||
|
||||
manager.isInterfacePassDetail("IF1");
|
||||
|
||||
assertEquals(1, manager.getInterfaceMetrics("IF1").rejectedPerSecond.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isInterfacePassDetail_differentInterfaces_independentMetrics() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF1", passDualBucket("IF1"));
|
||||
map.put("IF2", blockedPerSecondDualBucket("IF2"));
|
||||
injectInterfaceMap(map);
|
||||
|
||||
manager.isInterfacePassDetail("IF1");
|
||||
manager.isInterfacePassDetail("IF1");
|
||||
manager.isInterfacePassDetail("IF2");
|
||||
|
||||
assertEquals(2, manager.getInterfaceMetrics("IF1").allowed.get());
|
||||
assertEquals(1, manager.getInterfaceMetrics("IF2").rejectedPerSecond.get());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// isClientPassDetail() — 카운터 증가
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void isClientPassDetail_bucketNotRegistered_noMetricsCreated() {
|
||||
manager.isClientPassDetail("UNKNOWN");
|
||||
|
||||
assertNull(manager.getClientMetrics("UNKNOWN"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isClientPassDetail_pass_incrementsAllowed() {
|
||||
Map<String, DualCustomBucket> 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<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", blockedThresholdDualBucket("C1"));
|
||||
injectClientMap(map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
|
||||
assertEquals(1, manager.getClientMetrics("C1").rejectedThreshold.get());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// getAllXxxMetrics — 조회 및 불변성
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void getAllAdapterMetrics_afterCalls_containsEntries() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("A1", passDualBucket("A1"));
|
||||
map.put("A2", passDualBucket("A2"));
|
||||
injectAdapterMap(map);
|
||||
|
||||
manager.isAdapterPassDetail("A1");
|
||||
manager.isAdapterPassDetail("A2");
|
||||
|
||||
Map<String, DualInflowControlManager.TargetMetrics> all = manager.getAllAdapterMetrics();
|
||||
assertEquals(2, all.size());
|
||||
assertTrue(all.containsKey("A1"));
|
||||
assertTrue(all.containsKey("A2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllAdapterMetrics_returnsUnmodifiableView() {
|
||||
Map<String, DualInflowControlManager.TargetMetrics> all = manager.getAllAdapterMetrics();
|
||||
assertThrows(UnsupportedOperationException.class, () -> all.put("NEW", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllInterfaceMetrics_empty_returnsEmptyMap() {
|
||||
assertTrue(manager.getAllInterfaceMetrics().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllClientMetrics_empty_returnsEmptyMap() {
|
||||
assertTrue(manager.getAllClientMetrics().isEmpty());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// resetXxxMetrics / resetAllXxxMetrics
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void resetAdapterMetrics_resetsTargetAdapter() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("A1", passDualBucket("A1"));
|
||||
map.put("A2", passDualBucket("A2"));
|
||||
injectAdapterMap(map);
|
||||
|
||||
manager.isAdapterPassDetail("A1");
|
||||
manager.isAdapterPassDetail("A1");
|
||||
manager.isAdapterPassDetail("A2");
|
||||
|
||||
manager.resetAdapterMetrics("A1");
|
||||
|
||||
assertEquals(0, manager.getAdapterMetrics("A1").allowed.get());
|
||||
assertEquals(1, manager.getAdapterMetrics("A2").allowed.get()); // 영향 없음
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetAdapterMetrics_nonExistent_noException() {
|
||||
assertDoesNotThrow(() -> manager.resetAdapterMetrics("NONE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetAllAdapterMetrics_resetsAll() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("A1", passDualBucket("A1"));
|
||||
map.put("A2", passDualBucket("A2"));
|
||||
injectAdapterMap(map);
|
||||
|
||||
manager.isAdapterPassDetail("A1");
|
||||
manager.isAdapterPassDetail("A2");
|
||||
manager.resetAllAdapterMetrics();
|
||||
|
||||
assertEquals(0, manager.getAdapterMetrics("A1").allowed.get());
|
||||
assertEquals(0, manager.getAdapterMetrics("A2").allowed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetInterfaceMetrics_resetsTargetInterface() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF1", passDualBucket("IF1"));
|
||||
injectInterfaceMap(map);
|
||||
|
||||
manager.isInterfacePassDetail("IF1");
|
||||
manager.isInterfacePassDetail("IF1");
|
||||
manager.resetInterfaceMetrics("IF1");
|
||||
|
||||
assertEquals(0, manager.getInterfaceMetrics("IF1").allowed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetAllInterfaceMetrics_emptyMap_noException() {
|
||||
assertDoesNotThrow(() -> manager.resetAllInterfaceMetrics());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resetClientMetrics_resetsTargetClient() {
|
||||
Map<String, DualCustomBucket> 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<String, DualCustomBucket> 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());
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// remove 시 메트릭 자동 삭제
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
void removeAdapter_cleansUpMetrics() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("A1", passDualBucket("A1"));
|
||||
ReflectionTestUtils.setField(manager, "adapterBucketMap", map);
|
||||
|
||||
manager.isAdapterPassDetail("A1");
|
||||
assertNotNull(manager.getAdapterMetrics("A1"));
|
||||
|
||||
manager.removeAdapter("A1");
|
||||
|
||||
assertNull(manager.getAdapterMetrics("A1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeInterface_cleansUpMetrics() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("IF1", passDualBucket("IF1"));
|
||||
ReflectionTestUtils.setField(manager, "interfaceBucketMap", map);
|
||||
|
||||
manager.isInterfacePassDetail("IF1");
|
||||
assertNotNull(manager.getInterfaceMetrics("IF1"));
|
||||
|
||||
manager.removeInterface("IF1");
|
||||
|
||||
assertNull(manager.getInterfaceMetrics("IF1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeClient_cleansUpMetrics() {
|
||||
Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
|
||||
map.put("C1", passDualBucket("C1"));
|
||||
ReflectionTestUtils.setField(manager, "clientBucketMap", map);
|
||||
|
||||
manager.isClientPassDetail("C1");
|
||||
assertNotNull(manager.getClientMetrics("C1"));
|
||||
|
||||
manager.removeClient("C1");
|
||||
|
||||
assertNull(manager.getClientMetrics("C1"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user