package com.dianping.pigeon.remoting.invoker.process; import com.dianping.pigeon.config.ConfigChangeListener; import com.dianping.pigeon.config.ConfigManager; import com.dianping.pigeon.config.ConfigManagerLoader; import com.dianping.pigeon.log.Logger; import com.dianping.pigeon.log.LoggerLoader; import com.dianping.pigeon.remoting.invoker.process.filter.FaultInjectionFilter; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang.StringUtils; import java.io.IOException; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * Created by chenchongze on 17/4/18. */ public enum FaultInjectionManager { INSTANCE; private static final ConfigManager configManager = ConfigManagerLoader.getConfigManager(); private static final Logger logger = LoggerLoader.getLogger(FaultInjectionFilter.class); private static final ObjectMapper mapper = new ObjectMapper(); private static final String KEY_FAULT_INJECTION_ENABLE = "pigeon.fault.injection.enable"; private volatile static boolean isFaultInjectionEnable = configManager.getBooleanValue(KEY_FAULT_INJECTION_ENABLE, false); private static final String KEY_FAULT_INJECTION_CONFIGS = "pigeon.fault.injection.configs"; private static final Random random = new Random(); private volatile static Map<String, FaultInjectionConfig> configMap = new HashMap<String, FaultInjectionConfig>(); static { try { String configsStr = configManager.getStringValue(KEY_FAULT_INJECTION_CONFIGS, "{}"); refreshFaultInjectionConfigs(configsStr); } catch (Throwable t) { logger.warn("failed to parse pigeon fault injection configs, please check!"); } configManager.registerConfigChangeListener(new InnerGroupChangeListener()); } FaultInjectionManager() {} public boolean isEnable(String requestKey) { if (isFaultInjectionEnable) { FaultInjectionConfig config = configMap.get(requestKey); if (config != null) { return config.getEnable(); } } return false; } public FaultInjectionAction getAction(String requestKey) { FaultInjectionConfig config = configMap.get(requestKey); if (config != null) { FaultInjectionAction action = new FaultInjectionAction(); if (!config.getSample() || (config.getSample() && random(config.getSampleRate()))) { action.setType(FaultInjectionType.getType(config.getType())); if (FaultInjectionType.DELAY.equals(action.getType())) { // 延迟类型 if (config.getRandomDelay()) { // 延迟时间随机 action.setDelay(random.nextInt(config.getMaxDelay())); } else { // 延迟时间固定 action.setDelay(config.getMaxDelay()); } } return action; } } return new FaultInjectionAction(); } public static class FaultInjectionAction { private FaultInjectionType type = FaultInjectionType.NONE; private int delay = 0; public FaultInjectionType getType() { return type; } public void setType(FaultInjectionType type) { this.type = type; } public int getDelay() { return delay; } public void setDelay(int delay) { this.delay = delay; } } public static class FaultInjectionConfig implements Serializable { private boolean enable = true; private String type = FaultInjectionType.EXCEPTION.getType(); // 1. exception 2. delay private int maxDelay; // time_unit ms private boolean randomDelay = false; // 是否随机延时(0-maxDelay ms) private boolean sample = false; // 是否按比例采样注入错误 private float sampleRate; // 采样注入比率 public FaultInjectionConfig() {} public boolean getEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } public String getType() { return type; } public void setType(String type) { this.type = type; } public int getMaxDelay() { return maxDelay; } public void setMaxDelay(int maxDelay) { this.maxDelay = maxDelay; } public boolean getRandomDelay() { return randomDelay; } public void setRandomDelay(boolean randomDelay) { this.randomDelay = randomDelay; } public boolean getSample() { return sample; } public void setSample(boolean sample) { this.sample = sample; } public float getSampleRate() { return sampleRate; } public void setSampleRate(float sampleRate) { this.sampleRate = sampleRate; } } public enum FaultInjectionType { NONE("none"), EXCEPTION("exception"), DELAY("delay"); private final String type; FaultInjectionType(String type) { this.type = type; } public String getType() { return type; } public static FaultInjectionType getType(String typeName) { if (NONE.getType().equals(typeName)) { return NONE; } else if (EXCEPTION.getType().equals(typeName)) { return EXCEPTION; } else if (DELAY.getType().equals(typeName)) { return DELAY; } else { throw new IllegalArgumentException("invalid type name: " + typeName); } } } public static class FaultInjectionException extends RuntimeException { public FaultInjectionException() {} public FaultInjectionException (String msg) { super(msg); } } private boolean random(float percent) { return random.nextInt(100) < percent * 100; } private static void refreshFaultInjectionConfigs(String info) throws IOException { if (StringUtils.isNotBlank(info)) { JavaType type = getCollectionType(HashMap.class, Map.class, String.class, FaultInjectionConfig.class); configMap = mapper.readValue(info, type); } } public static JavaType getCollectionType(Class<?> collectionClass, Class<?> collectionInterfaceClass, Class<?>... elementClasses) { return mapper.getTypeFactory().constructParametrizedType(collectionClass, collectionInterfaceClass, elementClasses); } private static class InnerGroupChangeListener implements ConfigChangeListener { @Override public void onKeyUpdated(String key, String value) { if (key.endsWith(KEY_FAULT_INJECTION_ENABLE)) { try { isFaultInjectionEnable = Boolean.valueOf(value); logger.info("set " + KEY_FAULT_INJECTION_ENABLE + " value: " + value); } catch (Throwable t) { logger.warn("set " + KEY_FAULT_INJECTION_ENABLE + " failed!", t); } } else if (key.endsWith(KEY_FAULT_INJECTION_CONFIGS)) { try { refreshFaultInjectionConfigs(value); logger.info("set " + KEY_FAULT_INJECTION_CONFIGS + " value: " + value); } catch (Throwable t) { logger.warn("set " + KEY_FAULT_INJECTION_CONFIGS + " failed!", t); } } } @Override public void onKeyAdded(String key, String value) { } @Override public void onKeyRemoved(String key) { } } }