diff --git a/sentinel-dashboard/pom.xml b/sentinel-dashboard/pom.xml
index f91dd6b2..401b7b1f 100755
--- a/sentinel-dashboard/pom.xml
+++ b/sentinel-dashboard/pom.xml
@@ -40,6 +40,12 @@
${project.version}
+
+ com.alibaba.csp
+ sentinel-datasource-nacos
+ test
+
+
org.springframework.boot
spring-boot-starter-web
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java
index 5a921727..dc6d2697 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java
@@ -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 {
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java
index ee5ec099..6e82e787 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java
@@ -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 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 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 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 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 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 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 modifyClusterServerNamespaceSet(String app, String ip, int port, Set 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 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);
+ }
+ }
}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/config/WebConfig.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/config/WebConfig.java
index ba9b095d..6fa051f0 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/config/WebConfig.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/config/WebConfig.java
@@ -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 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;
}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java
index 98f976b8..eb2eced9 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java
@@ -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;
}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java
index fde8cfb4..3924f1fd 100644
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java
@@ -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 {
public List getParamFlowItemList() {
return rule.getParamFlowItemList();
}
+
+ @JsonIgnore
+ public boolean isClusterMode() {
+ return rule.isClusterMode();
+ }
+
+ @JsonIgnore
+ public ParamFlowClusterConfig getClusterConfig() {
+ return rule.getClusterConfig();
+ }
}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientModifyRequest.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientModifyRequest.java
new file mode 100644
index 00000000..924e2fd9
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientModifyRequest.java
@@ -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;
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientStateVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientStateVO.java
new file mode 100644
index 00000000..3e9e73f4
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientStateVO.java
@@ -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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterModifyRequest.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterModifyRequest.java
new file mode 100644
index 00000000..60fd8325
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterModifyRequest.java
@@ -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();
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerModifyRequest.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerModifyRequest.java
new file mode 100644
index 00000000..a621187a
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerModifyRequest.java
@@ -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 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 getNamespaceSet() {
+ return namespaceSet;
+ }
+
+ public ClusterServerModifyRequest setNamespaceSet(Set 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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerStateVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerStateVO.java
new file mode 100644
index 00000000..5a687893
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerStateVO.java
@@ -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 namespaceSet;
+
+ private Integer port;
+ private List 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 getNamespaceSet() {
+ return namespaceSet;
+ }
+
+ public ClusterServerStateVO setNamespaceSet(Set namespaceSet) {
+ this.namespaceSet = namespaceSet;
+ return this;
+ }
+
+ public Integer getPort() {
+ return port;
+ }
+
+ public ClusterServerStateVO setPort(Integer port) {
+ this.port = port;
+ return this;
+ }
+
+ public List getConnection() {
+ return connection;
+ }
+
+ public ClusterServerStateVO setConnection(
+ List connection) {
+ this.connection = connection;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "ClusterServerStateVO{" +
+ "transport=" + transport +
+ ", flow=" + flow +
+ ", namespaceSet=" + namespaceSet +
+ ", port=" + port +
+ ", connection=" + connection +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSimpleEntity.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSimpleEntity.java
new file mode 100644
index 00000000..52577c60
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSimpleEntity.java
@@ -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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterUniversalStateVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterUniversalStateVO.java
new file mode 100644
index 00000000..8038bcc5
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterUniversalStateVO.java
@@ -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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionDescriptorVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionDescriptorVO.java
new file mode 100644
index 00000000..415aaf6f
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionDescriptorVO.java
@@ -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 + '\'' +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionGroupVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionGroupVO.java
new file mode 100644
index 00000000..5618c883
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionGroupVO.java
@@ -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 connectionSet;
+ private Integer connectedCount;
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public ConnectionGroupVO setNamespace(String namespace) {
+ this.namespace = namespace;
+ return this;
+ }
+
+ public List getConnectionSet() {
+ return connectionSet;
+ }
+
+ public ConnectionGroupVO setConnectionSet(
+ List 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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ClusterClientConfig.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ClusterClientConfig.java
new file mode 100644
index 00000000..61304798
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ClusterClientConfig.java
@@ -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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerFlowConfig.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerFlowConfig.java
new file mode 100644
index 00000000..f0e2d888
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerFlowConfig.java
@@ -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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerTransportConfig.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerTransportConfig.java
new file mode 100644
index 00000000..dbf03be3
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerTransportConfig.java
@@ -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 +
+ '}';
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java
index 87d1c009..84742111 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java
@@ -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 implements RuleRepository {
+
/**
* {@code >}
*/
private Map> machineRules = new ConcurrentHashMap<>(16);
private Map allRules = new ConcurrentHashMap<>(16);
+ private Map> appRules = new ConcurrentHashMap<>(16);
+
private static final int MAX_RULES_SIZE = 10000;
@Override
@@ -41,17 +45,25 @@ public abstract class InMemoryRuleRepositoryAdapter 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 saveAll(List rules) {
+ // TODO: check here.
allRules.clear();
machineRules.clear();
+ appRules.clear();
if (rules == null) {
return null;
@@ -67,6 +79,9 @@ public abstract class InMemoryRuleRepositoryAdapter 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 implem
return new ArrayList<>(entities.values());
}
+ @Override
+ public List findAllByApp(String appName) {
+ AssertUtil.notEmpty(appName, "appName cannot be empty");
+ Map 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.
*
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/RuleRepository.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/RuleRepository.java
index cb50669f..6cd3f792 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/RuleRepository.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/RuleRepository.java
@@ -25,6 +25,7 @@ import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
* @author leyou
*/
public interface RuleRepository {
+
/**
* Save one.
*
@@ -65,6 +66,15 @@ public interface RuleRepository {
*/
List findAllByMachine(MachineInfo machineInfo);
+ /**
+ * Find all by application.
+ *
+ * @param appName valid app name
+ * @return all rules of the application
+ * @since 1.4.0
+ */
+ List findAllByApp(String appName);
+
///**
// * Find all by app and enable switch.
// * @param app
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRuleProvider.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRuleProvider.java
new file mode 100644
index 00000000..20a1e937
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRuleProvider.java
@@ -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 getRules(String appName) throws Exception;
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRulePublisher.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRulePublisher.java
new file mode 100644
index 00000000..007bb863
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRulePublisher.java
@@ -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 {
+
+ /**
+ * 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;
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiProvider.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiProvider.java
new file mode 100644
index 00000000..6978d2e7
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiProvider.java
@@ -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> {
+
+ @Autowired
+ private SentinelApiClient sentinelApiClient;
+ @Autowired
+ private AppManagement appManagement;
+
+ @Override
+ public List getRules(String appName) throws Exception {
+ if (StringUtil.isBlank(appName)) {
+ return new ArrayList<>();
+ }
+ List 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());
+ }
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiPublisher.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiPublisher.java
new file mode 100644
index 00000000..05530101
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiPublisher.java
@@ -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> {
+
+ @Autowired
+ private SentinelApiClient sentinelApiClient;
+ @Autowired
+ private AppManagement appManagement;
+
+ @Override
+ public void publish(String app, List rules) throws Exception {
+ if (StringUtil.isBlank(app)) {
+ return;
+ }
+ if (rules == null || rules.isEmpty()) {
+ return;
+ }
+ Set 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);
+ }
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterConfigService.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterConfigService.java
new file mode 100644
index 00000000..ea334963
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterConfigService.java
@@ -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 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 modifyClusterServerConfig(ClusterServerModifyRequest request) {
+ ServerTransportConfig transportConfig = request.getTransportConfig();
+ ServerFlowConfig flowConfig = request.getFlowConfig();
+ Set 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 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;
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtil.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtil.java
new file mode 100644
index 00000000..ac94dcd6
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtil.java
@@ -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;
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ClusterConfigController.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ClusterConfigController.java
new file mode 100644
index 00000000..b4f1d86d
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ClusterConfigController.java
@@ -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 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 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 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 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 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 Result 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";
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowController.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java
similarity index 71%
rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowController.java
rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java
index 0a6026e3..9813896f 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowController.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java
@@ -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 repository;
@Autowired
private SentinelApiClient sentinelApiClient;
- @ResponseBody
- @RequestMapping("/rules.json")
- Result> queryMachineRules(String app, String ip, Integer port) {
+ @GetMapping("/rules")
+ public Result> 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 Result 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 apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
+ Result 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 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");
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV2.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV2.java
new file mode 100755
index 00000000..52e3cf93
--- /dev/null
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV2.java
@@ -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 repository;
+
+ @Autowired
+ @Qualifier("flowRuleDefaultProvider")
+ private DynamicRuleProvider> ruleProvider;
+ @Autowired
+ @Qualifier("flowRuleDefaultPublisher")
+ private DynamicRulePublisher> rulePublisher;
+
+ @GetMapping("/rules")
+ public Result> apiQueryMachineRules(@RequestParam String app) {
+ if (StringUtil.isEmpty(app)) {
+ return Result.ofFail(-1, "app can't be null or empty");
+ }
+ try {
+ List 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 Result 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 apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
+ Result 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 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 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 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 rules = repository.findAllByApp(app);
+ rulePublisher.publish(app, rules);
+ }
+}
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/MetricController.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/MetricController.java
index bd683c01..ee336664 100755
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/MetricController.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/MetricController.java
@@ -84,7 +84,8 @@ public class MetricController {
return Result.ofFail(-1, "time intervalMs is too big, must <= 1h");
}
List 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> map = new ConcurrentHashMap<>();
- logger.info("topResource={}", topResource);
+ logger.debug("topResource={}", topResource);
long time = System.currentTimeMillis();
for (final String resource : topResource) {
List 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 vos = MetricVo.fromMetricEntities(entities, resource);
Iterable 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 resultMap = new HashMap<>(16);
resultMap.put("totalCount", resources.size());
resultMap.put("totalPage", totalPage);
diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java
index 817fdafc..c24b605d 100644
--- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java
+++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java
@@ -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 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);
diff --git a/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosProvider.java b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosProvider.java
new file mode 100644
index 00000000..0c5fe27e
--- /dev/null
+++ b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosProvider.java
@@ -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> {
+
+ @Autowired
+ private ConfigService configService;
+ @Autowired
+ private Converter> converter;
+
+ @Override
+ public List 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);
+ }
+}
diff --git a/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosPublisher.java b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosPublisher.java
new file mode 100644
index 00000000..4b3bf840
--- /dev/null
+++ b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosPublisher.java
@@ -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> {
+
+ @Autowired
+ private ConfigService configService;
+ @Autowired
+ private Converter, String> converter;
+
+ @Override
+ public void publish(String app, List 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));
+ }
+}
diff --git a/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfig.java b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfig.java
new file mode 100644
index 00000000..146b8697
--- /dev/null
+++ b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfig.java
@@ -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, String> flowRuleEntityEncoder() {
+ return JSON::toJSONString;
+ }
+
+ @Bean
+ public Converter> flowRuleEntityDecoder() {
+ return s -> JSON.parseArray(s, FlowRuleEntity.class);
+ }
+
+ @Bean
+ public ConfigService nacosConfigService() throws Exception {
+ return ConfigFactory.createConfigService("localhost");
+ }
+}
diff --git a/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfigUtil.java b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfigUtil.java
new file mode 100644
index 00000000..d17d2475
--- /dev/null
+++ b/sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfigUtil.java
@@ -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() {}
+}