- Update SentinelApiClient to support new-added command APIs - Add controller for cluster config - Add flow controller v2 for global rule pulling / pushing - Extract dynamic rule provider and publisher interface for customized extensions - Add basic cluster config service - Add basic Nacos integration (in test as an example) Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -40,6 +40,12 @@ | |||
<version>${project.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.alibaba.csp</groupId> | |||
<artifactId>sentinel-datasource-nacos</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-web</artifactId> | |||
@@ -18,6 +18,11 @@ package com.taobao.csp.sentinel.dashboard; | |||
import org.springframework.boot.SpringApplication; | |||
import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
/** | |||
* Sentinel dashboard application. | |||
* | |||
* @author Carpenter Lee | |||
*/ | |||
@SpringBootApplication | |||
public class DashboardApplication { | |||
@@ -22,6 +22,7 @@ import java.net.URLEncoder; | |||
import java.nio.charset.Charset; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import java.util.concurrent.CompletableFuture; | |||
import java.util.concurrent.CountDownLatch; | |||
import java.util.concurrent.TimeUnit; | |||
@@ -42,6 +43,11 @@ import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntit | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterServerStateVO; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterStateSimpleEntity; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; | |||
import com.taobao.csp.sentinel.dashboard.util.RuleUtils; | |||
import org.apache.http.HttpResponse; | |||
import org.apache.http.client.methods.HttpGet; | |||
@@ -76,6 +82,18 @@ public class SentinelApiClient { | |||
private static final String GET_PARAM_RULE_PATH = "getParamFlowRules"; | |||
private static final String SET_PARAM_RULE_PATH = "setParamFlowRules"; | |||
private static final String FETCH_CLUSTER_MODE_PATH = "getClusterMode"; | |||
private static final String MODIFY_CLUSTER_MODE_PATH = "setClusterMode"; | |||
private static final String FETCH_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/fetchConfig"; | |||
private static final String MODIFY_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/modifyConfig"; | |||
private static final String FETCH_CLUSTER_SERVER_ALL_CONFIG_PATH = "cluster/server/fetchConfig"; | |||
private static final String FETCH_CLUSTER_SERVER_BASIC_INFO_PATH = "cluster/server/info"; | |||
private static final String MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH = "cluster/server/modifyTransportConfig"; | |||
private static final String MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH = "cluster/server/modifyFlowConfig"; | |||
private static final String MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH = "cluster/server/modifyNamespaceSet"; | |||
private static final String FLOW_RULE_TYPE = "flow"; | |||
private static final String DEGRADE_RULE_TYPE = "degrade"; | |||
private static final String SYSTEM_RULE_TYPE = "system"; | |||
@@ -485,4 +503,175 @@ public class SentinelApiClient { | |||
future.completeExceptionally(ex); | |||
return future; | |||
} | |||
// Cluster related | |||
public CompletableFuture<ClusterStateSimpleEntity> fetchClusterMode(String app, String ip, int port) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(FETCH_CLUSTER_MODE_PATH); | |||
return executeCommand(FETCH_CLUSTER_MODE_PATH, uriBuilder.build()) | |||
.thenApply(r -> JSON.parseObject(r, ClusterStateSimpleEntity.class)); | |||
} catch (Exception ex) { | |||
logger.warn("Error when fetching cluster mode", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
public CompletableFuture<Void> modifyClusterMode(String app, String ip, int port, int mode) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(MODIFY_CLUSTER_MODE_PATH) | |||
.setParameter("mode", String.valueOf(mode)); | |||
return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build()) | |||
.thenCompose(e -> { | |||
if ("success".equals(e)) { | |||
return CompletableFuture.completedFuture(null); | |||
} else { | |||
logger.warn("Error when modifying cluster mode: " + e); | |||
return newFailedFuture(new RuntimeException(e)); | |||
} | |||
}); | |||
} catch (Exception ex) { | |||
logger.warn("Error when modifying cluster mode", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
public CompletableFuture<ClusterClientConfig> fetchClusterClientConfig(String app, String ip, int port) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(FETCH_CLUSTER_CLIENT_CONFIG_PATH); | |||
return executeCommand(FETCH_CLUSTER_CLIENT_CONFIG_PATH, uriBuilder.build()) | |||
.thenApply(r -> JSON.parseObject(r, ClusterClientConfig.class)); | |||
} catch (Exception ex) { | |||
logger.warn("Error when fetching cluster client config", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
public CompletableFuture<Void> modifyClusterClientConfig(String app, String ip, int port, ClusterClientConfig config) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(MODIFY_CLUSTER_CLIENT_CONFIG_PATH) | |||
.setParameter("data", JSON.toJSONString(config)); | |||
return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build()) | |||
.thenCompose(e -> { | |||
if ("success".equals(e)) { | |||
return CompletableFuture.completedFuture(null); | |||
} else { | |||
logger.warn("Error when modifying cluster client config: " + e); | |||
return newFailedFuture(new RuntimeException(e)); | |||
} | |||
}); | |||
} catch (Exception ex) { | |||
logger.warn("Error when modifying cluster client config", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
public CompletableFuture<Void> modifyClusterServerFlowConfig(String app, String ip, int port, ServerFlowConfig config) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH) | |||
.setParameter("data", JSON.toJSONString(config)); | |||
return executeCommand(MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH, uriBuilder.build()) | |||
.thenCompose(e -> { | |||
if ("success".equals(e)) { | |||
return CompletableFuture.completedFuture(null); | |||
} else { | |||
logger.warn("Error when modifying cluster server flow config: " + e); | |||
return newFailedFuture(new RuntimeException(e)); | |||
} | |||
}); | |||
} catch (Exception ex) { | |||
logger.warn("Error when modifying cluster server flow config", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
public CompletableFuture<Void> modifyClusterServerTransportConfig(String app, String ip, int port, ServerTransportConfig config) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH) | |||
.setParameter("port", config.getPort().toString()) | |||
.setParameter("idleSeconds", config.getIdleSeconds().toString()); | |||
return executeCommand(MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH, uriBuilder.build()) | |||
.thenCompose(e -> { | |||
if ("success".equals(e)) { | |||
return CompletableFuture.completedFuture(null); | |||
} else { | |||
logger.warn("Error when modifying cluster server transport config: " + e); | |||
return newFailedFuture(new RuntimeException(e)); | |||
} | |||
}); | |||
} catch (Exception ex) { | |||
logger.warn("Error when modifying cluster server transport config", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
public CompletableFuture<Void> modifyClusterServerNamespaceSet(String app, String ip, int port, Set<String> set) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH) | |||
.setParameter("data", JSON.toJSONString(set)); | |||
return executeCommand(MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH, uriBuilder.build()) | |||
.thenCompose(e -> { | |||
if ("success".equals(e)) { | |||
return CompletableFuture.completedFuture(null); | |||
} else { | |||
logger.warn("Error when modifying cluster server NamespaceSet: " + e); | |||
return newFailedFuture(new RuntimeException(e)); | |||
} | |||
}); | |||
} catch (Exception ex) { | |||
logger.warn("Error when modifying cluster server NamespaceSet", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
public CompletableFuture<ClusterServerStateVO> fetchClusterServerBasicInfo(String app, String ip, int port) { | |||
if (StringUtil.isBlank(ip) || port <= 0) { | |||
return newFailedFuture(new IllegalArgumentException("Invalid parameter")); | |||
} | |||
try { | |||
URIBuilder uriBuilder = new URIBuilder(); | |||
uriBuilder.setScheme("http").setHost(ip).setPort(port) | |||
.setPath(FETCH_CLUSTER_SERVER_BASIC_INFO_PATH); | |||
return executeCommand(FETCH_CLUSTER_SERVER_BASIC_INFO_PATH, uriBuilder.build()) | |||
.thenApply(r -> JSON.parseObject(r, ClusterServerStateVO.class)); | |||
} catch (Exception ex) { | |||
logger.warn("Error when fetching cluster sever all config and basic info", ex); | |||
return newFailedFuture(ex); | |||
} | |||
} | |||
} |
@@ -15,6 +15,8 @@ | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.config; | |||
import javax.servlet.Filter; | |||
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; | |||
import org.slf4j.Logger; | |||
@@ -31,7 +33,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter | |||
*/ | |||
@Configuration | |||
public class WebConfig extends WebMvcConfigurerAdapter { | |||
private static Logger logger = LoggerFactory.getLogger(WebConfig.class); | |||
private final Logger logger = LoggerFactory.getLogger(WebConfig.class); | |||
@Override | |||
public void addResourceHandlers(ResourceHandlerRegistry registry) { | |||
@@ -46,19 +49,17 @@ public class WebConfig extends WebMvcConfigurerAdapter { | |||
/** | |||
* Add {@link CommonFilter} to the server, this is the simplest way to use Sentinel | |||
* for Web application. | |||
* | |||
* @return | |||
*/ | |||
@Bean | |||
public FilterRegistrationBean sentinelFilterRegistration() { | |||
logger.info("sentinelFilterRegistration(), add CommonFilter"); | |||
FilterRegistrationBean registration = new FilterRegistrationBean(); | |||
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); | |||
registration.setFilter(new CommonFilter()); | |||
registration.addUrlPatterns("/*"); | |||
registration.addInitParameter("paramName", "paramValue"); | |||
registration.setName("sentinelFilter"); | |||
registration.setOrder(1); | |||
logger.info("Sentinel servlet CommonFilter registered"); | |||
return registration; | |||
} | |||
@@ -17,6 +17,7 @@ package com.taobao.csp.sentinel.dashboard.datasource.entity.rule; | |||
import java.util.Date; | |||
import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; | |||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; | |||
/** | |||
@@ -49,6 +50,13 @@ public class FlowRuleEntity implements RuleEntity { | |||
* max queueing time in rate limiter behavior | |||
*/ | |||
private Integer maxQueueingTimeMs; | |||
private boolean clusterMode; | |||
/** | |||
* Flow rule config for cluster mode. | |||
*/ | |||
private ClusterFlowConfig clusterConfig; | |||
private Date gmtCreate; | |||
private Date gmtModified; | |||
@@ -66,6 +74,8 @@ public class FlowRuleEntity implements RuleEntity { | |||
entity.setControlBehavior(rule.getControlBehavior()); | |||
entity.setWarmUpPeriodSec(rule.getWarmUpPeriodSec()); | |||
entity.setMaxQueueingTimeMs(rule.getMaxQueueingTimeMs()); | |||
entity.setClusterMode(rule.isClusterMode()); | |||
entity.setClusterConfig(rule.getClusterConfig()); | |||
return entity; | |||
} | |||
@@ -178,6 +188,24 @@ public class FlowRuleEntity implements RuleEntity { | |||
this.maxQueueingTimeMs = maxQueueingTimeMs; | |||
} | |||
public boolean isClusterMode() { | |||
return clusterMode; | |||
} | |||
public FlowRuleEntity setClusterMode(boolean clusterMode) { | |||
this.clusterMode = clusterMode; | |||
return this; | |||
} | |||
public ClusterFlowConfig getClusterConfig() { | |||
return clusterConfig; | |||
} | |||
public FlowRuleEntity setClusterConfig(ClusterFlowConfig clusterConfig) { | |||
this.clusterConfig = clusterConfig; | |||
return this; | |||
} | |||
@Override | |||
public Date getGmtCreate() { | |||
return gmtCreate; | |||
@@ -212,6 +240,8 @@ public class FlowRuleEntity implements RuleEntity { | |||
if (this.maxQueueingTimeMs != null) { | |||
flowRule.setMaxQueueingTimeMs(maxQueueingTimeMs); | |||
} | |||
flowRule.setClusterMode(clusterMode); | |||
flowRule.setClusterConfig(clusterConfig); | |||
return flowRule; | |||
} | |||
@@ -17,6 +17,7 @@ package com.taobao.csp.sentinel.dashboard.datasource.entity.rule; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
@@ -73,4 +74,14 @@ public class ParamFlowRuleEntity extends AbstractRuleEntity<ParamFlowRule> { | |||
public List<ParamFlowItem> getParamFlowItemList() { | |||
return rule.getParamFlowItemList(); | |||
} | |||
@JsonIgnore | |||
public boolean isClusterMode() { | |||
return rule.isClusterMode(); | |||
} | |||
@JsonIgnore | |||
public ParamFlowClusterConfig getClusterConfig() { | |||
return rule.getClusterConfig(); | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterClientModifyRequest implements ClusterModifyRequest { | |||
private String app; | |||
private String ip; | |||
private Integer port; | |||
private Integer mode; | |||
private ClusterClientConfig clientConfig; | |||
@Override | |||
public String getApp() { | |||
return app; | |||
} | |||
public ClusterClientModifyRequest setApp(String app) { | |||
this.app = app; | |||
return this; | |||
} | |||
@Override | |||
public String getIp() { | |||
return ip; | |||
} | |||
public ClusterClientModifyRequest setIp(String ip) { | |||
this.ip = ip; | |||
return this; | |||
} | |||
@Override | |||
public Integer getPort() { | |||
return port; | |||
} | |||
public ClusterClientModifyRequest setPort(Integer port) { | |||
this.port = port; | |||
return this; | |||
} | |||
@Override | |||
public Integer getMode() { | |||
return mode; | |||
} | |||
public ClusterClientModifyRequest setMode(Integer mode) { | |||
this.mode = mode; | |||
return this; | |||
} | |||
public ClusterClientConfig getClientConfig() { | |||
return clientConfig; | |||
} | |||
public ClusterClientModifyRequest setClientConfig( | |||
ClusterClientConfig clientConfig) { | |||
this.clientConfig = clientConfig; | |||
return this; | |||
} | |||
} |
@@ -0,0 +1,44 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterClientStateVO { | |||
private ClusterClientConfig clientConfig; | |||
public ClusterClientConfig getClientConfig() { | |||
return clientConfig; | |||
} | |||
public ClusterClientStateVO setClientConfig( | |||
ClusterClientConfig clientConfig) { | |||
this.clientConfig = clientConfig; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterClientStateVO{" + | |||
"clientConfig=" + clientConfig + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public interface ClusterModifyRequest { | |||
String getApp(); | |||
String getIp(); | |||
Integer getPort(); | |||
Integer getMode(); | |||
} |
@@ -0,0 +1,119 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
import java.util.Set; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterServerModifyRequest implements ClusterModifyRequest { | |||
private String app; | |||
private String ip; | |||
private Integer port; | |||
private Integer mode; | |||
private ServerFlowConfig flowConfig; | |||
private ServerTransportConfig transportConfig; | |||
private Set<String> namespaceSet; | |||
@Override | |||
public String getApp() { | |||
return app; | |||
} | |||
public ClusterServerModifyRequest setApp(String app) { | |||
this.app = app; | |||
return this; | |||
} | |||
@Override | |||
public String getIp() { | |||
return ip; | |||
} | |||
public ClusterServerModifyRequest setIp(String ip) { | |||
this.ip = ip; | |||
return this; | |||
} | |||
@Override | |||
public Integer getPort() { | |||
return port; | |||
} | |||
public ClusterServerModifyRequest setPort(Integer port) { | |||
this.port = port; | |||
return this; | |||
} | |||
@Override | |||
public Integer getMode() { | |||
return mode; | |||
} | |||
public ClusterServerModifyRequest setMode(Integer mode) { | |||
this.mode = mode; | |||
return this; | |||
} | |||
public ServerFlowConfig getFlowConfig() { | |||
return flowConfig; | |||
} | |||
public ClusterServerModifyRequest setFlowConfig( | |||
ServerFlowConfig flowConfig) { | |||
this.flowConfig = flowConfig; | |||
return this; | |||
} | |||
public ServerTransportConfig getTransportConfig() { | |||
return transportConfig; | |||
} | |||
public ClusterServerModifyRequest setTransportConfig( | |||
ServerTransportConfig transportConfig) { | |||
this.transportConfig = transportConfig; | |||
return this; | |||
} | |||
public Set<String> getNamespaceSet() { | |||
return namespaceSet; | |||
} | |||
public ClusterServerModifyRequest setNamespaceSet(Set<String> namespaceSet) { | |||
this.namespaceSet = namespaceSet; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterServerModifyRequest{" + | |||
"app='" + app + '\'' + | |||
", ip='" + ip + '\'' + | |||
", port=" + port + | |||
", mode=" + mode + | |||
", flowConfig=" + flowConfig + | |||
", transportConfig=" + transportConfig + | |||
", namespaceSet=" + namespaceSet + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
import java.util.List; | |||
import java.util.Set; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterServerStateVO { | |||
private ServerTransportConfig transport; | |||
private ServerFlowConfig flow; | |||
private Set<String> namespaceSet; | |||
private Integer port; | |||
private List<ConnectionGroupVO> connection; | |||
public ServerTransportConfig getTransport() { | |||
return transport; | |||
} | |||
public ClusterServerStateVO setTransport( | |||
ServerTransportConfig transport) { | |||
this.transport = transport; | |||
return this; | |||
} | |||
public ServerFlowConfig getFlow() { | |||
return flow; | |||
} | |||
public ClusterServerStateVO setFlow(ServerFlowConfig flow) { | |||
this.flow = flow; | |||
return this; | |||
} | |||
public Set<String> getNamespaceSet() { | |||
return namespaceSet; | |||
} | |||
public ClusterServerStateVO setNamespaceSet(Set<String> namespaceSet) { | |||
this.namespaceSet = namespaceSet; | |||
return this; | |||
} | |||
public Integer getPort() { | |||
return port; | |||
} | |||
public ClusterServerStateVO setPort(Integer port) { | |||
this.port = port; | |||
return this; | |||
} | |||
public List<ConnectionGroupVO> getConnection() { | |||
return connection; | |||
} | |||
public ClusterServerStateVO setConnection( | |||
List<ConnectionGroupVO> connection) { | |||
this.connection = connection; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterServerStateVO{" + | |||
"transport=" + transport + | |||
", flow=" + flow + | |||
", namespaceSet=" + namespaceSet + | |||
", port=" + port + | |||
", connection=" + connection + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterStateSimpleEntity { | |||
private Integer mode; | |||
private Long lastModified; | |||
private Boolean clientAvailable; | |||
private Boolean serverAvailable; | |||
public Integer getMode() { | |||
return mode; | |||
} | |||
public ClusterStateSimpleEntity setMode(Integer mode) { | |||
this.mode = mode; | |||
return this; | |||
} | |||
public Long getLastModified() { | |||
return lastModified; | |||
} | |||
public ClusterStateSimpleEntity setLastModified(Long lastModified) { | |||
this.lastModified = lastModified; | |||
return this; | |||
} | |||
public Boolean getClientAvailable() { | |||
return clientAvailable; | |||
} | |||
public ClusterStateSimpleEntity setClientAvailable(Boolean clientAvailable) { | |||
this.clientAvailable = clientAvailable; | |||
return this; | |||
} | |||
public Boolean getServerAvailable() { | |||
return serverAvailable; | |||
} | |||
public ClusterStateSimpleEntity setServerAvailable(Boolean serverAvailable) { | |||
this.serverAvailable = serverAvailable; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterStateSimpleEntity{" + | |||
"mode=" + mode + | |||
", lastModified=" + lastModified + | |||
", clientAvailable=" + clientAvailable + | |||
", serverAvailable=" + serverAvailable + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterUniversalStateVO { | |||
private ClusterStateSimpleEntity stateInfo; | |||
private ClusterClientStateVO client; | |||
private ClusterServerStateVO server; | |||
public ClusterClientStateVO getClient() { | |||
return client; | |||
} | |||
public ClusterUniversalStateVO setClient(ClusterClientStateVO client) { | |||
this.client = client; | |||
return this; | |||
} | |||
public ClusterServerStateVO getServer() { | |||
return server; | |||
} | |||
public ClusterUniversalStateVO setServer(ClusterServerStateVO server) { | |||
this.server = server; | |||
return this; | |||
} | |||
public ClusterStateSimpleEntity getStateInfo() { | |||
return stateInfo; | |||
} | |||
public ClusterUniversalStateVO setStateInfo( | |||
ClusterStateSimpleEntity stateInfo) { | |||
this.stateInfo = stateInfo; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterUniversalStateVO{" + | |||
"stateInfo=" + stateInfo + | |||
", client=" + client + | |||
", server=" + server + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ConnectionDescriptorVO { | |||
private String address; | |||
private String host; | |||
public String getAddress() { | |||
return address; | |||
} | |||
public ConnectionDescriptorVO setAddress(String address) { | |||
this.address = address; | |||
return this; | |||
} | |||
public String getHost() { | |||
return host; | |||
} | |||
public ConnectionDescriptorVO setHost(String host) { | |||
this.host = host; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ConnectionDescriptorVO{" + | |||
"address='" + address + '\'' + | |||
", host='" + host + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster; | |||
import java.util.List; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ConnectionGroupVO { | |||
private String namespace; | |||
private List<ConnectionDescriptorVO> connectionSet; | |||
private Integer connectedCount; | |||
public String getNamespace() { | |||
return namespace; | |||
} | |||
public ConnectionGroupVO setNamespace(String namespace) { | |||
this.namespace = namespace; | |||
return this; | |||
} | |||
public List<ConnectionDescriptorVO> getConnectionSet() { | |||
return connectionSet; | |||
} | |||
public ConnectionGroupVO setConnectionSet( | |||
List<ConnectionDescriptorVO> connectionSet) { | |||
this.connectionSet = connectionSet; | |||
return this; | |||
} | |||
public Integer getConnectedCount() { | |||
return connectedCount; | |||
} | |||
public ConnectionGroupVO setConnectedCount(Integer connectedCount) { | |||
this.connectedCount = connectedCount; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ConnectionGroupVO{" + | |||
"namespace='" + namespace + '\'' + | |||
", connectionSet=" + connectionSet + | |||
", connectedCount=" + connectedCount + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,75 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster.config; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ClusterClientConfig { | |||
private String serverHost; | |||
private Integer serverPort; | |||
private Integer requestTimeout; | |||
private Integer connectTimeout; | |||
public String getServerHost() { | |||
return serverHost; | |||
} | |||
public ClusterClientConfig setServerHost(String serverHost) { | |||
this.serverHost = serverHost; | |||
return this; | |||
} | |||
public Integer getServerPort() { | |||
return serverPort; | |||
} | |||
public ClusterClientConfig setServerPort(Integer serverPort) { | |||
this.serverPort = serverPort; | |||
return this; | |||
} | |||
public Integer getRequestTimeout() { | |||
return requestTimeout; | |||
} | |||
public ClusterClientConfig setRequestTimeout(Integer requestTimeout) { | |||
this.requestTimeout = requestTimeout; | |||
return this; | |||
} | |||
public Integer getConnectTimeout() { | |||
return connectTimeout; | |||
} | |||
public ClusterClientConfig setConnectTimeout(Integer connectTimeout) { | |||
this.connectTimeout = connectTimeout; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterClientConfig{" + | |||
"serverHost='" + serverHost + '\'' + | |||
", serverPort=" + serverPort + | |||
", requestTimeout=" + requestTimeout + | |||
", connectTimeout=" + connectTimeout + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,95 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster.config; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ServerFlowConfig { | |||
public static final double DEFAULT_EXCEED_COUNT = 1.0d; | |||
public static final double DEFAULT_MAX_OCCUPY_RATIO = 1.0d; | |||
public static final int DEFAULT_INTERVAL_MS = 1000; | |||
public static final int DEFAULT_SAMPLE_COUNT= 10; | |||
private final String namespace; | |||
private Double exceedCount = DEFAULT_EXCEED_COUNT; | |||
private Double maxOccupyRatio = DEFAULT_MAX_OCCUPY_RATIO; | |||
private Integer intervalMs = DEFAULT_INTERVAL_MS; | |||
private Integer sampleCount = DEFAULT_SAMPLE_COUNT; | |||
public ServerFlowConfig() { | |||
this("default"); | |||
} | |||
public ServerFlowConfig(String namespace) { | |||
this.namespace = namespace; | |||
} | |||
public String getNamespace() { | |||
return namespace; | |||
} | |||
public Double getExceedCount() { | |||
return exceedCount; | |||
} | |||
public ServerFlowConfig setExceedCount(Double exceedCount) { | |||
this.exceedCount = exceedCount; | |||
return this; | |||
} | |||
public Double getMaxOccupyRatio() { | |||
return maxOccupyRatio; | |||
} | |||
public ServerFlowConfig setMaxOccupyRatio(Double maxOccupyRatio) { | |||
this.maxOccupyRatio = maxOccupyRatio; | |||
return this; | |||
} | |||
public Integer getIntervalMs() { | |||
return intervalMs; | |||
} | |||
public ServerFlowConfig setIntervalMs(Integer intervalMs) { | |||
this.intervalMs = intervalMs; | |||
return this; | |||
} | |||
public Integer getSampleCount() { | |||
return sampleCount; | |||
} | |||
public ServerFlowConfig setSampleCount(Integer sampleCount) { | |||
this.sampleCount = sampleCount; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ServerFlowConfig{" + | |||
"namespace='" + namespace + '\'' + | |||
", exceedCount=" + exceedCount + | |||
", maxOccupyRatio=" + maxOccupyRatio + | |||
", intervalMs=" + intervalMs + | |||
", sampleCount=" + sampleCount + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.domain.cluster.config; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public class ServerTransportConfig { | |||
public static final int DEFAULT_PORT = 8730; | |||
public static final int DEFAULT_IDLE_SECONDS = 600; | |||
private Integer port; | |||
private Integer idleSeconds; | |||
public ServerTransportConfig() { | |||
this(DEFAULT_PORT, DEFAULT_IDLE_SECONDS); | |||
} | |||
public ServerTransportConfig(Integer port, Integer idleSeconds) { | |||
this.port = port; | |||
this.idleSeconds = idleSeconds; | |||
} | |||
public Integer getPort() { | |||
return port; | |||
} | |||
public ServerTransportConfig setPort(Integer port) { | |||
this.port = port; | |||
return this; | |||
} | |||
public Integer getIdleSeconds() { | |||
return idleSeconds; | |||
} | |||
public ServerTransportConfig setIdleSeconds(Integer idleSeconds) { | |||
this.idleSeconds = idleSeconds; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ServerTransportConfig{" + | |||
"port=" + port + | |||
", idleSeconds=" + idleSeconds + | |||
'}'; | |||
} | |||
} |
@@ -17,6 +17,8 @@ package com.taobao.csp.sentinel.dashboard.repository.rule; | |||
import java.util.concurrent.atomic.AtomicLong; | |||
import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import org.springframework.stereotype.Component; | |||
@@ -34,4 +36,18 @@ public class InMemFlowRuleStore extends InMemoryRuleRepositoryAdapter<FlowRuleEn | |||
protected long nextId() { | |||
return ids.incrementAndGet(); | |||
} | |||
@Override | |||
protected FlowRuleEntity preProcess(FlowRuleEntity entity) { | |||
if (entity != null && entity.isClusterMode()) { | |||
ClusterFlowConfig config = entity.getClusterConfig(); | |||
if (config == null) { | |||
config = new ClusterFlowConfig(); | |||
entity.setClusterConfig(config); | |||
} | |||
// Set cluster rule id. | |||
config.setFlowId(entity.getId()); | |||
} | |||
return entity; | |||
} | |||
} |
@@ -17,6 +17,8 @@ package com.taobao.csp.sentinel.dashboard.repository.rule; | |||
import java.util.concurrent.atomic.AtomicLong; | |||
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity; | |||
import org.springframework.stereotype.Component; | |||
@@ -33,4 +35,17 @@ public class InMemParamFlowRuleStore extends InMemoryRuleRepositoryAdapter<Param | |||
protected long nextId() { | |||
return ids.incrementAndGet(); | |||
} | |||
@Override | |||
protected ParamFlowRuleEntity preProcess(ParamFlowRuleEntity entity) { | |||
if (entity != null && entity.isClusterMode()) { | |||
ParamFlowClusterConfig config = entity.getClusterConfig(); | |||
if (config == null) { | |||
config = new ParamFlowClusterConfig(); | |||
} | |||
// Set cluster rule id. | |||
config.setFlowId(entity.getId()); | |||
} | |||
return entity; | |||
} | |||
} |
@@ -19,7 +19,8 @@ import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.stream.Collectors; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | |||
@@ -28,12 +29,15 @@ import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | |||
* @author leyou | |||
*/ | |||
public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implements RuleRepository<T, Long> { | |||
/** | |||
* {@code <machine, <id, rule>>} | |||
*/ | |||
private Map<MachineInfo, Map<Long, T>> machineRules = new ConcurrentHashMap<>(16); | |||
private Map<Long, T> allRules = new ConcurrentHashMap<>(16); | |||
private Map<String, Map<Long, T>> appRules = new ConcurrentHashMap<>(16); | |||
private static final int MAX_RULES_SIZE = 10000; | |||
@Override | |||
@@ -41,17 +45,25 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem | |||
if (entity.getId() == null) { | |||
entity.setId(nextId()); | |||
} | |||
allRules.put(entity.getId(), entity); | |||
machineRules.computeIfAbsent(MachineInfo.of(entity.getApp(), entity.getIp(), entity.getPort()), | |||
e -> new ConcurrentHashMap<>(32)) | |||
.put(entity.getId(), entity); | |||
return entity; | |||
T processedEntity = preProcess(entity); | |||
if (processedEntity != null) { | |||
allRules.put(processedEntity.getId(), processedEntity); | |||
machineRules.computeIfAbsent(MachineInfo.of(processedEntity.getApp(), processedEntity.getIp(), | |||
processedEntity.getPort()), e -> new ConcurrentHashMap<>(32)) | |||
.put(processedEntity.getId(), processedEntity); | |||
appRules.computeIfAbsent(processedEntity.getApp(), v -> new ConcurrentHashMap<>(32)) | |||
.put(processedEntity.getId(), processedEntity); | |||
} | |||
return processedEntity; | |||
} | |||
@Override | |||
public List<T> saveAll(List<T> rules) { | |||
// TODO: check here. | |||
allRules.clear(); | |||
machineRules.clear(); | |||
appRules.clear(); | |||
if (rules == null) { | |||
return null; | |||
@@ -67,6 +79,9 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem | |||
public T delete(Long id) { | |||
T entity = allRules.remove(id); | |||
if (entity != null) { | |||
if (appRules.get(entity.getApp()) != null) { | |||
appRules.get(entity.getApp()).remove(id); | |||
} | |||
machineRules.get(MachineInfo.of(entity.getApp(), entity.getIp(), entity.getPort())).remove(id); | |||
} | |||
return entity; | |||
@@ -86,6 +101,20 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem | |||
return new ArrayList<>(entities.values()); | |||
} | |||
@Override | |||
public List<T> findAllByApp(String appName) { | |||
AssertUtil.notEmpty(appName, "appName cannot be empty"); | |||
Map<Long, T> entities = appRules.get(appName); | |||
if (entities == null) { | |||
return new ArrayList<>(); | |||
} | |||
return new ArrayList<>(entities.values()); | |||
} | |||
protected T preProcess(T entity) { | |||
return entity; | |||
} | |||
/** | |||
* Get next unused id. | |||
* | |||
@@ -25,6 +25,7 @@ import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | |||
* @author leyou | |||
*/ | |||
public interface RuleRepository<T, ID> { | |||
/** | |||
* Save one. | |||
* | |||
@@ -65,6 +66,15 @@ public interface RuleRepository<T, ID> { | |||
*/ | |||
List<T> findAllByMachine(MachineInfo machineInfo); | |||
/** | |||
* Find all by application. | |||
* | |||
* @param appName valid app name | |||
* @return all rules of the application | |||
* @since 1.4.0 | |||
*/ | |||
List<T> findAllByApp(String appName); | |||
///** | |||
// * Find all by app and enable switch. | |||
// * @param app | |||
@@ -0,0 +1,25 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public interface DynamicRuleProvider<T> { | |||
T getRules(String appName) throws Exception; | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public interface DynamicRulePublisher<T> { | |||
/** | |||
* Publish rules to remote rule configuration center for given application name. | |||
* | |||
* @param app app name | |||
* @param rules list of rules to push | |||
* @throws Exception if some error occurs | |||
*/ | |||
void publish(String app, T rules) throws Exception; | |||
} |
@@ -0,0 +1,67 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.discovery.AppManagement; | |||
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | |||
import com.taobao.csp.sentinel.dashboard.util.MachineUtil; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Component; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
@Component("flowRuleDefaultProvider") | |||
public class FlowRuleApiProvider implements DynamicRuleProvider<List<FlowRuleEntity>> { | |||
@Autowired | |||
private SentinelApiClient sentinelApiClient; | |||
@Autowired | |||
private AppManagement appManagement; | |||
@Override | |||
public List<FlowRuleEntity> getRules(String appName) throws Exception { | |||
if (StringUtil.isBlank(appName)) { | |||
return new ArrayList<>(); | |||
} | |||
List<MachineInfo> list = appManagement.getDetailApp(appName).getMachines() | |||
.stream() | |||
.filter(MachineUtil::isMachineHealth) | |||
.sorted((e1, e2) -> { | |||
if (e1.getTimestamp().before(e2.getTimestamp())) { | |||
return 1; | |||
} else if (e1.getTimestamp().after(e2.getTimestamp())) { | |||
return -1; | |||
} else { | |||
return 0; | |||
} | |||
}).collect(Collectors.toList()); | |||
if (list.isEmpty()) { | |||
return new ArrayList<>(); | |||
} else { | |||
MachineInfo machine = list.get(0); | |||
return sentinelApiClient.fetchFlowRuleOfMachine(machine.getApp(), machine.getIp(), machine.getPort()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule; | |||
import java.util.List; | |||
import java.util.Set; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.discovery.AppManagement; | |||
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | |||
import com.taobao.csp.sentinel.dashboard.util.MachineUtil; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Component; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@Component("flowRuleDefaultPublisher") | |||
public class FlowRuleApiPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { | |||
@Autowired | |||
private SentinelApiClient sentinelApiClient; | |||
@Autowired | |||
private AppManagement appManagement; | |||
@Override | |||
public void publish(String app, List<FlowRuleEntity> rules) throws Exception { | |||
if (StringUtil.isBlank(app)) { | |||
return; | |||
} | |||
if (rules == null || rules.isEmpty()) { | |||
return; | |||
} | |||
Set<MachineInfo> set = appManagement.getDetailApp(app).getMachines(); | |||
for (MachineInfo machine : set) { | |||
if (!MachineUtil.isMachineHealth(machine)) { | |||
continue; | |||
} | |||
// TODO: parse the results | |||
sentinelApiClient.setFlowRuleOfMachine(app, machine.getIp(), machine.getPort(), rules); | |||
} | |||
} | |||
} |
@@ -0,0 +1,116 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.service; | |||
import java.util.Set; | |||
import java.util.concurrent.CompletableFuture; | |||
import java.util.concurrent.ExecutionException; | |||
import com.alibaba.csp.sentinel.cluster.ClusterStateManager; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterClientModifyRequest; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterClientStateVO; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterServerModifyRequest; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterUniversalStateVO; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@Service | |||
public class ClusterConfigService { | |||
@Autowired | |||
private SentinelApiClient sentinelApiClient; | |||
public CompletableFuture<Void> modifyClusterClientConfig(ClusterClientModifyRequest request) { | |||
if (notClientRequestValid(request)) { | |||
throw new IllegalArgumentException("Invalid request"); | |||
} | |||
String app = request.getApp(); | |||
String ip = request.getIp(); | |||
int port = request.getPort(); | |||
return sentinelApiClient.modifyClusterClientConfig(app, ip, port, request.getClientConfig()) | |||
.thenCompose(v -> sentinelApiClient.modifyClusterMode(app, ip, port, ClusterStateManager.CLUSTER_CLIENT)); | |||
} | |||
private boolean notClientRequestValid(/*@NonNull */ ClusterClientModifyRequest request) { | |||
ClusterClientConfig config = request.getClientConfig(); | |||
return config == null || StringUtil.isEmpty(config.getServerHost()) | |||
|| config.getServerPort() == null || config.getServerPort() <= 0 | |||
|| config.getRequestTimeout() == null || config.getRequestTimeout() <= 0; | |||
} | |||
public CompletableFuture<Void> modifyClusterServerConfig(ClusterServerModifyRequest request) { | |||
ServerTransportConfig transportConfig = request.getTransportConfig(); | |||
ServerFlowConfig flowConfig = request.getFlowConfig(); | |||
Set<String> namespaceSet = request.getNamespaceSet(); | |||
if (invalidTransportConfig(transportConfig)) { | |||
throw new IllegalArgumentException("Invalid transport config in request"); | |||
} | |||
if (invalidFlowConfig(flowConfig)) { | |||
throw new IllegalArgumentException("Invalid flow config in request"); | |||
} | |||
if (namespaceSet == null) { | |||
throw new IllegalArgumentException("namespace set cannot be null"); | |||
} | |||
String app = request.getApp(); | |||
String ip = request.getIp(); | |||
int port = request.getPort(); | |||
return sentinelApiClient.modifyClusterServerNamespaceSet(app, ip, port, namespaceSet) | |||
.thenCompose(v -> sentinelApiClient.modifyClusterServerTransportConfig(app, ip, port, transportConfig)) | |||
.thenCompose(v -> sentinelApiClient.modifyClusterServerFlowConfig(app, ip, port, flowConfig)) | |||
.thenCompose(v -> sentinelApiClient.modifyClusterMode(app, ip, port, ClusterStateManager.CLUSTER_SERVER)); | |||
} | |||
public CompletableFuture<ClusterUniversalStateVO> getClusterUniversalState(String app, String ip, int port) { | |||
return sentinelApiClient.fetchClusterMode(app, ip, port) | |||
.thenApply(e -> new ClusterUniversalStateVO().setStateInfo(e)) | |||
.thenCompose(vo -> { | |||
if (vo.getStateInfo().getClientAvailable()) { | |||
return sentinelApiClient.fetchClusterClientConfig(app, ip, port) | |||
.thenApply(cc -> vo.setClient(new ClusterClientStateVO().setClientConfig(cc))); | |||
} else { | |||
return CompletableFuture.completedFuture(vo); | |||
} | |||
}).thenCompose(vo -> { | |||
if (vo.getStateInfo().getServerAvailable()) { | |||
return sentinelApiClient.fetchClusterServerBasicInfo(app, ip, port) | |||
.thenApply(vo::setServer); | |||
} else { | |||
return CompletableFuture.completedFuture(vo); | |||
} | |||
}); | |||
} | |||
private boolean invalidTransportConfig(ServerTransportConfig transportConfig) { | |||
return transportConfig == null || transportConfig.getPort() == null || transportConfig.getPort() <= 0 | |||
|| transportConfig.getIdleSeconds() == null || transportConfig.getIdleSeconds() <= 0; | |||
} | |||
private boolean invalidFlowConfig(ServerFlowConfig flowConfig) { | |||
return flowConfig == null || flowConfig.getSampleCount() == null || flowConfig.getSampleCount() <= 0 | |||
|| flowConfig.getIntervalMs() == null || flowConfig.getIntervalMs() <= 0 | |||
|| flowConfig.getIntervalMs() % flowConfig.getSampleCount() != 0; | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.util; | |||
import com.taobao.csp.sentinel.dashboard.discovery.MachineDiscovery; | |||
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | |||
/** | |||
* @author Eric Zhao | |||
*/ | |||
public final class MachineUtil { | |||
public static boolean isMachineHealth(MachineInfo machine) { | |||
if (machine == null) { | |||
return false; | |||
} | |||
return System.currentTimeMillis() - machine.getTimestamp().getTime() < MachineDiscovery.MAX_CLIENT_LIVE_TIME_MS; | |||
} | |||
} |
@@ -0,0 +1,182 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.view; | |||
import java.util.Optional; | |||
import java.util.concurrent.ExecutionException; | |||
import com.alibaba.csp.sentinel.cluster.ClusterStateManager; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import com.alibaba.fastjson.JSON; | |||
import com.alibaba.fastjson.JSONObject; | |||
import com.taobao.csp.sentinel.dashboard.client.CommandNotFoundException; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.SentinelVersion; | |||
import com.taobao.csp.sentinel.dashboard.discovery.AppManagement; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterClientModifyRequest; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterModifyRequest; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterServerModifyRequest; | |||
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterUniversalStateVO; | |||
import com.taobao.csp.sentinel.dashboard.service.ClusterConfigService; | |||
import com.taobao.csp.sentinel.dashboard.util.VersionUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
import org.springframework.web.bind.annotation.PostMapping; | |||
import org.springframework.web.bind.annotation.RequestBody; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RequestParam; | |||
import org.springframework.web.bind.annotation.RestController; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@RestController | |||
@RequestMapping(value = "/cluster") | |||
public class ClusterConfigController { | |||
private final Logger logger = LoggerFactory.getLogger(ClusterConfigController.class); | |||
private final SentinelVersion version140 = new SentinelVersion().setMajorVersion(1).setMinorVersion(4); | |||
@Autowired | |||
private AppManagement appManagement; | |||
@Autowired | |||
private ClusterConfigService clusterConfigService; | |||
@PostMapping("/config/modify") | |||
public Result<Boolean> apiModifyClusterConfig(@RequestBody String payload) { | |||
if (StringUtil.isBlank(payload)) { | |||
return Result.ofFail(-1, "empty request body"); | |||
} | |||
try { | |||
JSONObject body = JSON.parseObject(payload); | |||
if (body.containsKey(KEY_MODE)) { | |||
int mode = body.getInteger(KEY_MODE); | |||
switch (mode) { | |||
case ClusterStateManager.CLUSTER_CLIENT: | |||
ClusterClientModifyRequest data = JSON.parseObject(payload, ClusterClientModifyRequest.class); | |||
Result<Boolean> res = checkValidRequest(data); | |||
if (res != null) { | |||
return res; | |||
} | |||
clusterConfigService.modifyClusterClientConfig(data).get(); | |||
return Result.ofSuccess(true); | |||
case ClusterStateManager.CLUSTER_SERVER: | |||
ClusterServerModifyRequest d = JSON.parseObject(payload, ClusterServerModifyRequest.class); | |||
Result<Boolean> r = checkValidRequest(d); | |||
if (r != null) { | |||
return r; | |||
} | |||
// TODO: bad design here, should refactor! | |||
clusterConfigService.modifyClusterServerConfig(d).get(); | |||
return Result.ofSuccess(true); | |||
default: | |||
return Result.ofFail(-1, "invalid mode"); | |||
} | |||
} | |||
return Result.ofFail(-1, "invalid parameter"); | |||
} catch (ExecutionException ex) { | |||
logger.error("Error when modifying cluster config", ex.getCause()); | |||
if (isNotSupported(ex.getCause())) { | |||
return unsupportedVersion(); | |||
} else { | |||
return Result.ofThrowable(-1, ex.getCause()); | |||
} | |||
} catch (Throwable ex) { | |||
logger.error("Error when modifying cluster config", ex); | |||
return Result.ofFail(-1, ex.getMessage()); | |||
} | |||
} | |||
@GetMapping("/state") | |||
public Result<ClusterUniversalStateVO> apiGetClusterState(@RequestParam String app, | |||
@RequestParam String ip, | |||
@RequestParam Integer port) { | |||
if (StringUtil.isEmpty(app)) { | |||
return Result.ofFail(-1, "app cannot be null or empty"); | |||
} | |||
if (StringUtil.isEmpty(ip)) { | |||
return Result.ofFail(-1, "ip cannot be null or empty"); | |||
} | |||
if (port == null || port <= 0) { | |||
return Result.ofFail(-1, "Invalid parameter: port"); | |||
} | |||
if (!checkIfSupported(app, ip, port)) { | |||
return unsupportedVersion(); | |||
} | |||
try { | |||
return clusterConfigService.getClusterUniversalState(app, ip, port) | |||
.thenApply(Result::ofSuccess) | |||
.get(); | |||
} catch (ExecutionException ex) { | |||
logger.error("Error when fetching cluster state", ex.getCause()); | |||
if (isNotSupported(ex.getCause())) { | |||
return unsupportedVersion(); | |||
} else { | |||
return Result.ofThrowable(-1, ex.getCause()); | |||
} | |||
} catch (Throwable throwable) { | |||
logger.error("Error when fetching cluster state", throwable); | |||
return Result.ofFail(-1, throwable.getMessage()); | |||
} | |||
} | |||
private boolean isNotSupported(Throwable ex) { | |||
return ex instanceof CommandNotFoundException; | |||
} | |||
private boolean checkIfSupported(String app, String ip, int port) { | |||
try { | |||
return Optional.ofNullable(appManagement.getDetailApp(app)) | |||
.flatMap(e -> e.getMachine(ip, port)) | |||
.flatMap(m -> VersionUtils.parseVersion(m.getVersion()) | |||
.map(v -> v.greaterOrEqual(version140))) | |||
.orElse(true); | |||
// If error occurred or cannot retrieve machine info, return true. | |||
} catch (Exception ex) { | |||
return true; | |||
} | |||
} | |||
private Result<Boolean> checkValidRequest(ClusterModifyRequest request) { | |||
if (StringUtil.isEmpty(request.getApp())) { | |||
return Result.ofFail(-1, "app cannot be empty"); | |||
} | |||
if (StringUtil.isEmpty(request.getIp())) { | |||
return Result.ofFail(-1, "ip cannot be empty"); | |||
} | |||
if (request.getPort() == null || request.getPort() < 0) { | |||
return Result.ofFail(-1, "invalid port"); | |||
} | |||
if (request.getMode() == null || request.getMode() < 0) { | |||
return Result.ofFail(-1, "invalid mode"); | |||
} | |||
if (!checkIfSupported(request.getApp(), request.getIp(), request.getPort())) { | |||
return unsupportedVersion(); | |||
} | |||
return null; | |||
} | |||
private <R> Result<R> unsupportedVersion() { | |||
return Result.ofFail(4041, "Sentinel client not supported for cluster flow control (unsupported version or dependency absent)"); | |||
} | |||
private static final String KEY_MODE = "mode"; | |||
} |
@@ -20,36 +20,44 @@ import java.util.List; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; | |||
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; | |||
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemFlowRuleStore; | |||
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.http.MediaType; | |||
import org.springframework.stereotype.Controller; | |||
import org.springframework.web.bind.annotation.DeleteMapping; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
import org.springframework.web.bind.annotation.PostMapping; | |||
import org.springframework.web.bind.annotation.PutMapping; | |||
import org.springframework.web.bind.annotation.RequestBody; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.ResponseBody; | |||
import org.springframework.web.bind.annotation.RequestParam; | |||
import org.springframework.web.bind.annotation.RestController; | |||
/** | |||
* Flow rule controller. | |||
* | |||
* @author leyou | |||
* @author Eric Zhao | |||
*/ | |||
@Controller | |||
@RequestMapping(value = "/flow", produces = MediaType.APPLICATION_JSON_VALUE) | |||
public class FlowController { | |||
private static Logger logger = LoggerFactory.getLogger(FlowController.class); | |||
@RestController | |||
@RequestMapping(value = "/v1/flow") | |||
public class FlowControllerV1 { | |||
private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class); | |||
@Autowired | |||
private InMemFlowRuleStore repository; | |||
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository; | |||
@Autowired | |||
private SentinelApiClient sentinelApiClient; | |||
@ResponseBody | |||
@RequestMapping("/rules.json") | |||
Result<List<FlowRuleEntity>> queryMachineRules(String app, String ip, Integer port) { | |||
@GetMapping("/rules") | |||
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app, | |||
@RequestParam String ip, | |||
@RequestParam Integer port) { | |||
if (StringUtil.isEmpty(app)) { | |||
return Result.ofFail(-1, "app can't be null or empty"); | |||
} | |||
@@ -64,88 +72,82 @@ public class FlowController { | |||
rules = repository.saveAll(rules); | |||
return Result.ofSuccess(rules); | |||
} catch (Throwable throwable) { | |||
logger.error("queryApps error:", throwable); | |||
logger.error("Error when querying flow rules", throwable); | |||
return Result.ofThrowable(-1, throwable); | |||
} | |||
} | |||
@ResponseBody | |||
@RequestMapping("/new.json") | |||
Result<?> add(String app, String ip, Integer port, String limitApp, String resource, Integer grade, | |||
Double count, Integer strategy, String refResource, | |||
Integer controlBehavior, Integer warmUpPeriodSec, Integer maxQueueingTimeMs) { | |||
if (StringUtil.isBlank(app)) { | |||
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) { | |||
if (StringUtil.isBlank(entity.getApp())) { | |||
return Result.ofFail(-1, "app can't be null or empty"); | |||
} | |||
if (StringUtil.isBlank(ip)) { | |||
if (StringUtil.isBlank(entity.getIp())) { | |||
return Result.ofFail(-1, "ip can't be null or empty"); | |||
} | |||
if (port == null) { | |||
if (entity.getPort() == null) { | |||
return Result.ofFail(-1, "port can't be null"); | |||
} | |||
if (StringUtil.isBlank(limitApp)) { | |||
if (StringUtil.isBlank(entity.getLimitApp())) { | |||
return Result.ofFail(-1, "limitApp can't be null or empty"); | |||
} | |||
if (StringUtil.isBlank(resource)) { | |||
if (StringUtil.isBlank(entity.getResource())) { | |||
return Result.ofFail(-1, "resource can't be null or empty"); | |||
} | |||
if (grade == null) { | |||
if (entity.getGrade() == null) { | |||
return Result.ofFail(-1, "grade can't be null"); | |||
} | |||
if (grade != 0 && grade != 1) { | |||
return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got"); | |||
if (entity.getGrade() != 0 && entity.getGrade() != 1) { | |||
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got"); | |||
} | |||
if (count == null) { | |||
return Result.ofFail(-1, "count can't be null"); | |||
if (entity.getCount() == null || entity.getCount() < 0) { | |||
return Result.ofFail(-1, "count should be at lease zero"); | |||
} | |||
if (strategy == null) { | |||
if (entity.getStrategy() == null) { | |||
return Result.ofFail(-1, "strategy can't be null"); | |||
} | |||
if (strategy != 0 && StringUtil.isBlank(refResource)) { | |||
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) { | |||
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0"); | |||
} | |||
if (controlBehavior == null) { | |||
if (entity.getControlBehavior() == null) { | |||
return Result.ofFail(-1, "controlBehavior can't be null"); | |||
} | |||
if (controlBehavior == 1 && warmUpPeriodSec == null) { | |||
int controlBehavior = entity.getControlBehavior(); | |||
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) { | |||
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1"); | |||
} | |||
if (controlBehavior == 2 && maxQueueingTimeMs == null) { | |||
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) { | |||
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2"); | |||
} | |||
FlowRuleEntity entity = new FlowRuleEntity(); | |||
entity.setApp(app.trim()); | |||
entity.setIp(ip.trim()); | |||
entity.setPort(port); | |||
entity.setLimitApp(limitApp.trim()); | |||
entity.setResource(resource.trim()); | |||
entity.setGrade(grade); | |||
entity.setCount(count); | |||
entity.setStrategy(strategy); | |||
entity.setControlBehavior(controlBehavior); | |||
entity.setWarmUpPeriodSec(warmUpPeriodSec); | |||
entity.setMaxQueueingTimeMs(maxQueueingTimeMs); | |||
if (strategy != 0) { | |||
entity.setRefResource(refResource.trim()); | |||
if (entity.isClusterMode() && entity.getClusterConfig() == null) { | |||
return Result.ofFail(-1, "cluster config should be valid"); | |||
} | |||
return null; | |||
} | |||
@PostMapping("/rule") | |||
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) { | |||
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity); | |||
if (checkResult != null) { | |||
return checkResult; | |||
} | |||
entity.setId(null); | |||
Date date = new Date(); | |||
entity.setGmtCreate(date); | |||
entity.setGmtModified(date); | |||
try { | |||
entity = repository.save(entity); | |||
} catch (Throwable throwable) { | |||
logger.error("add error:", throwable); | |||
logger.error("Failed to add flow rule", throwable); | |||
return Result.ofThrowable(-1, throwable); | |||
} | |||
if (!publishRules(app, ip, port)) { | |||
logger.info("publish flow rules fail after rule add"); | |||
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { | |||
logger.error("Publish flow rules failed after rule add"); | |||
} | |||
return Result.ofSuccess(entity); | |||
} | |||
@ResponseBody | |||
@RequestMapping("/save.json") | |||
Result<?> updateIfNotNull(Long id, String app, | |||
@PutMapping("/save.json") | |||
public Result<FlowRuleEntity> updateIfNotNull(Long id, String app, | |||
String limitApp, String resource, Integer grade, | |||
Double count, Integer strategy, String refResource, | |||
Integer controlBehavior, Integer warmUpPeriodSec, Integer maxQueueingTimeMs) { | |||
@@ -221,8 +223,7 @@ public class FlowController { | |||
return Result.ofSuccess(entity); | |||
} | |||
@ResponseBody | |||
@RequestMapping("/delete.json") | |||
@DeleteMapping("/delete.json") | |||
Result<?> delete(Long id) { | |||
if (id == null) { | |||
return Result.ofFail(-1, "id can't be null"); |
@@ -0,0 +1,209 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.view; | |||
import java.util.Date; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter; | |||
import com.taobao.csp.sentinel.dashboard.rule.DynamicRuleProvider; | |||
import com.taobao.csp.sentinel.dashboard.rule.DynamicRulePublisher; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.beans.factory.annotation.Qualifier; | |||
import org.springframework.web.bind.annotation.DeleteMapping; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
import org.springframework.web.bind.annotation.PathVariable; | |||
import org.springframework.web.bind.annotation.PostMapping; | |||
import org.springframework.web.bind.annotation.PutMapping; | |||
import org.springframework.web.bind.annotation.RequestBody; | |||
import org.springframework.web.bind.annotation.RequestMapping; | |||
import org.springframework.web.bind.annotation.RequestParam; | |||
import org.springframework.web.bind.annotation.RestController; | |||
/** | |||
* Flow rule controller (v2). | |||
* | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@RestController | |||
@RequestMapping(value = "/v2/flow") | |||
public class FlowControllerV2 { | |||
private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class); | |||
@Autowired | |||
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository; | |||
@Autowired | |||
@Qualifier("flowRuleDefaultProvider") | |||
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider; | |||
@Autowired | |||
@Qualifier("flowRuleDefaultPublisher") | |||
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher; | |||
@GetMapping("/rules") | |||
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app) { | |||
if (StringUtil.isEmpty(app)) { | |||
return Result.ofFail(-1, "app can't be null or empty"); | |||
} | |||
try { | |||
List<FlowRuleEntity> rules = ruleProvider.getRules(app); | |||
if (rules != null && !rules.isEmpty()) { | |||
for (FlowRuleEntity entity : rules) { | |||
entity.setApp(app); | |||
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) { | |||
entity.setId(entity.getClusterConfig().getFlowId()); | |||
} | |||
} | |||
} | |||
rules = repository.saveAll(rules); | |||
return Result.ofSuccess(rules); | |||
} catch (Throwable throwable) { | |||
logger.error("Error when querying flow rules", throwable); | |||
return Result.ofThrowable(-1, throwable); | |||
} | |||
} | |||
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) { | |||
if (entity == null) { | |||
return Result.ofFail(-1, "invalid body"); | |||
} | |||
if (StringUtil.isBlank(entity.getApp())) { | |||
return Result.ofFail(-1, "app can't be null or empty"); | |||
} | |||
if (StringUtil.isBlank(entity.getLimitApp())) { | |||
return Result.ofFail(-1, "limitApp can't be null or empty"); | |||
} | |||
if (StringUtil.isBlank(entity.getResource())) { | |||
return Result.ofFail(-1, "resource can't be null or empty"); | |||
} | |||
if (entity.getGrade() == null) { | |||
return Result.ofFail(-1, "grade can't be null"); | |||
} | |||
if (entity.getGrade() != 0 && entity.getGrade() != 1) { | |||
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got"); | |||
} | |||
if (entity.getCount() == null || entity.getCount() < 0) { | |||
return Result.ofFail(-1, "count should be at lease zero"); | |||
} | |||
if (entity.getStrategy() == null) { | |||
return Result.ofFail(-1, "strategy can't be null"); | |||
} | |||
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) { | |||
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0"); | |||
} | |||
if (entity.getControlBehavior() == null) { | |||
return Result.ofFail(-1, "controlBehavior can't be null"); | |||
} | |||
int controlBehavior = entity.getControlBehavior(); | |||
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) { | |||
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1"); | |||
} | |||
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) { | |||
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2"); | |||
} | |||
if (entity.isClusterMode() && entity.getClusterConfig() == null) { | |||
return Result.ofFail(-1, "cluster config should be valid"); | |||
} | |||
return null; | |||
} | |||
@PostMapping("/rule") | |||
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) { | |||
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity); | |||
if (checkResult != null) { | |||
return checkResult; | |||
} | |||
entity.setId(null); | |||
Date date = new Date(); | |||
entity.setGmtCreate(date); | |||
entity.setGmtModified(date); | |||
try { | |||
entity = repository.save(entity); | |||
publishRules(entity.getApp()); | |||
} catch (Throwable throwable) { | |||
logger.error("Failed to add flow rule", throwable); | |||
return Result.ofThrowable(-1, throwable); | |||
} | |||
return Result.ofSuccess(entity); | |||
} | |||
@PutMapping("/rule/{id}") | |||
public Result<FlowRuleEntity> apiUpdateFlowRule(@PathVariable("id") Long id, @RequestBody FlowRuleEntity entity) { | |||
if (id == null || id <= 0) { | |||
return Result.ofFail(-1, "Invalid id"); | |||
} | |||
FlowRuleEntity oldEntity = repository.findById(id); | |||
if (oldEntity == null) { | |||
return Result.ofFail(-1, "id " + id + " does not exist"); | |||
} | |||
if (entity == null) { | |||
return Result.ofFail(-1, "invalid body"); | |||
} | |||
entity.setApp(oldEntity.getApp()); | |||
entity.setIp(oldEntity.getIp()); | |||
entity.setPort(oldEntity.getPort()); | |||
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity); | |||
if (checkResult != null) { | |||
return checkResult; | |||
} | |||
entity.setId(id); | |||
Date date = new Date(); | |||
entity.setGmtCreate(oldEntity.getGmtCreate()); | |||
entity.setGmtModified(date); | |||
try { | |||
entity = repository.save(entity); | |||
if (entity == null) { | |||
return Result.ofFail(-1, "save entity fail"); | |||
} | |||
publishRules(oldEntity.getApp()); | |||
} catch (Throwable throwable) { | |||
logger.error("Failed to update flow rule", throwable); | |||
return Result.ofThrowable(-1, throwable); | |||
} | |||
return Result.ofSuccess(entity); | |||
} | |||
@DeleteMapping("/rule/{id}") | |||
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) { | |||
if (id == null || id <= 0) { | |||
return Result.ofFail(-1, "Invalid id"); | |||
} | |||
FlowRuleEntity oldEntity = repository.findById(id); | |||
if (oldEntity == null) { | |||
return Result.ofSuccess(null); | |||
} | |||
try { | |||
repository.delete(id); | |||
publishRules(oldEntity.getApp()); | |||
} catch (Exception e) { | |||
return Result.ofFail(-1, e.getMessage()); | |||
} | |||
return Result.ofSuccess(id); | |||
} | |||
private void publishRules(/*@NonNull*/ String app) throws Exception { | |||
List<FlowRuleEntity> rules = repository.findAllByApp(app); | |||
rulePublisher.publish(app, rules); | |||
} | |||
} |
@@ -84,7 +84,8 @@ public class MetricController { | |||
return Result.ofFail(-1, "time intervalMs is too big, must <= 1h"); | |||
} | |||
List<String> resources = metricStore.listResourcesOfApp(app); | |||
logger.info("queryTopResourceMetric(), resources.size()={}", resources.size()); | |||
logger.debug("queryTopResourceMetric(), resources.size()={}", resources.size()); | |||
if (resources == null || resources.isEmpty()) { | |||
return Result.ofSuccess(null); | |||
} | |||
@@ -107,17 +108,17 @@ public class MetricController { | |||
Math.min(pageIndex * pageSize, resources.size())); | |||
} | |||
final Map<String, Iterable<MetricVo>> map = new ConcurrentHashMap<>(); | |||
logger.info("topResource={}", topResource); | |||
logger.debug("topResource={}", topResource); | |||
long time = System.currentTimeMillis(); | |||
for (final String resource : topResource) { | |||
List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween( | |||
app, resource, startTime, endTime); | |||
logger.info("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size()); | |||
logger.debug("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size()); | |||
List<MetricVo> vos = MetricVo.fromMetricEntities(entities, resource); | |||
Iterable<MetricVo> vosSorted = sortMetricVoAndDistinct(vos); | |||
map.put(resource, vosSorted); | |||
} | |||
logger.info("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time); | |||
logger.debug("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time); | |||
Map<String, Object> resultMap = new HashMap<>(16); | |||
resultMap.put("totalCount", resources.size()); | |||
resultMap.put("totalPage", totalPage); | |||
@@ -179,6 +179,10 @@ public class ParamFlowRuleController { | |||
if (id == null || id <= 0) { | |||
return Result.ofFail(-1, "Invalid id"); | |||
} | |||
ParamFlowRuleEntity oldEntity = repository.findById(id); | |||
if (oldEntity == null) { | |||
return Result.ofFail(-1, "id " + id + " does not exist"); | |||
} | |||
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity); | |||
if (checkResult != null) { | |||
return checkResult; | |||
@@ -188,7 +192,7 @@ public class ParamFlowRuleController { | |||
} | |||
entity.setId(id); | |||
Date date = new Date(); | |||
entity.setGmtCreate(null); | |||
entity.setGmtCreate(oldEntity.getGmtCreate()); | |||
entity.setGmtModified(date); | |||
try { | |||
entity = repository.save(entity); | |||
@@ -0,0 +1,51 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule.nacos; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.datasource.Converter; | |||
import com.alibaba.csp.sentinel.util.StringUtil; | |||
import com.alibaba.nacos.api.config.ConfigService; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.rule.DynamicRuleProvider; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Component; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@Component("flowRuleNacosProvider") | |||
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> { | |||
@Autowired | |||
private ConfigService configService; | |||
@Autowired | |||
private Converter<String, List<FlowRuleEntity>> converter; | |||
@Override | |||
public List<FlowRuleEntity> getRules(String appName) throws Exception { | |||
String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, | |||
NacosConfigUtil.GROUP_ID, 3000); | |||
if (StringUtil.isEmpty(rules)) { | |||
return new ArrayList<>(); | |||
} | |||
return converter.convert(rules); | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule.nacos; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.datasource.Converter; | |||
import com.alibaba.csp.sentinel.util.AssertUtil; | |||
import com.alibaba.nacos.api.config.ConfigService; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import com.taobao.csp.sentinel.dashboard.rule.DynamicRulePublisher; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Component; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@Component("flowRuleNacosPublisher") | |||
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> { | |||
@Autowired | |||
private ConfigService configService; | |||
@Autowired | |||
private Converter<List<FlowRuleEntity>, String> converter; | |||
@Override | |||
public void publish(String app, List<FlowRuleEntity> rules) throws Exception { | |||
AssertUtil.notEmpty(app, "app name cannot be empty"); | |||
if (rules == null) { | |||
return; | |||
} | |||
configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX, | |||
NacosConfigUtil.GROUP_ID, converter.convert(rules)); | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule.nacos; | |||
import java.util.List; | |||
import com.alibaba.csp.sentinel.datasource.Converter; | |||
import com.alibaba.fastjson.JSON; | |||
import com.alibaba.nacos.api.config.ConfigFactory; | |||
import com.alibaba.nacos.api.config.ConfigService; | |||
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
@Configuration | |||
public class NacosConfig { | |||
@Bean | |||
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() { | |||
return JSON::toJSONString; | |||
} | |||
@Bean | |||
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() { | |||
return s -> JSON.parseArray(s, FlowRuleEntity.class); | |||
} | |||
@Bean | |||
public ConfigService nacosConfigService() throws Exception { | |||
return ConfigFactory.createConfigService("localhost"); | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
/* | |||
* Copyright 1999-2018 Alibaba Group Holding Ltd. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
*/ | |||
package com.taobao.csp.sentinel.dashboard.rule.nacos; | |||
/** | |||
* @author Eric Zhao | |||
* @since 1.4.0 | |||
*/ | |||
public final class NacosConfigUtil { | |||
public static final String GROUP_ID = "SENTINEL_GROUP"; | |||
public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules"; | |||
public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules"; | |||
/** | |||
* cc for `cluster-client` | |||
*/ | |||
public static final String CLIENT_CONFIG_DATA_ID_POSTFIX = "-cc-config"; | |||
/** | |||
* cs for `cluster-server` | |||
*/ | |||
public static final String SERVER_TRANSPORT_CONFIG_DATA_ID_POSTFIX = "-cs-transport-config"; | |||
public static final String SERVER_FLOW_CONFIG_DATA_ID_POSTFIX = "-cs-flow-config"; | |||
public static final String SERVER_NAMESPACE_SET_DATA_ID_POSTFIX = "-cs-namespace-set"; | |||
private NacosConfigUtil() {} | |||
} |