Browse Source

Add dashboard logic for Sentinel cluster flow control

- Update SentinelApiClient to support new-added command APIs
- Add controller for cluster config
- Add flow controller v2 for global rule pulling / pushing
- Extract dynamic rule provider and publisher interface for customized extensions
- Add basic cluster config service
- Add basic Nacos integration (in test as an example)

Signed-off-by: Eric Zhao <sczyh16@gmail.com>
master
Eric Zhao 6 years ago
parent
commit
8961927a12
37 changed files with 2168 additions and 73 deletions
  1. +6
    -0
      sentinel-dashboard/pom.xml
  2. +5
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java
  3. +189
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java
  4. +7
    -6
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/config/WebConfig.java
  5. +30
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java
  6. +11
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java
  7. +82
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientModifyRequest.java
  8. +44
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientStateVO.java
  9. +31
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterModifyRequest.java
  10. +119
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerModifyRequest.java
  11. +94
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerStateVO.java
  12. +74
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSimpleEntity.java
  13. +64
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterUniversalStateVO.java
  14. +53
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionDescriptorVO.java
  15. +66
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionGroupVO.java
  16. +75
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ClusterClientConfig.java
  17. +95
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerFlowConfig.java
  18. +64
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerTransportConfig.java
  19. +16
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java
  20. +15
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemParamFlowRuleStore.java
  21. +35
    -6
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemoryRuleRepositoryAdapter.java
  22. +10
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/RuleRepository.java
  23. +25
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRuleProvider.java
  24. +32
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRulePublisher.java
  25. +67
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiProvider.java
  26. +61
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiPublisher.java
  27. +116
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterConfigService.java
  28. +32
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtil.java
  29. +182
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ClusterConfigController.java
  30. +57
    -56
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java
  31. +209
    -0
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV2.java
  32. +5
    -4
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/MetricController.java
  33. +5
    -1
      sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java
  34. +51
    -0
      sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosProvider.java
  35. +50
    -0
      sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosPublisher.java
  36. +50
    -0
      sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfig.java
  37. +41
    -0
      sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfigUtil.java

+ 6
- 0
sentinel-dashboard/pom.xml View File

@@ -40,6 +40,12 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>


+ 5
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/DashboardApplication.java View File

@@ -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 {



+ 189
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/SentinelApiClient.java View File

@@ -22,6 +22,7 @@ import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -42,6 +43,11 @@ import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntit
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.SystemRuleEntity;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterServerStateVO;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterStateSimpleEntity;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;
import com.taobao.csp.sentinel.dashboard.util.RuleUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
@@ -76,6 +82,18 @@ public class SentinelApiClient {
private static final String GET_PARAM_RULE_PATH = "getParamFlowRules";
private static final String SET_PARAM_RULE_PATH = "setParamFlowRules";

private static final String FETCH_CLUSTER_MODE_PATH = "getClusterMode";
private static final String MODIFY_CLUSTER_MODE_PATH = "setClusterMode";
private static final String FETCH_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/fetchConfig";
private static final String MODIFY_CLUSTER_CLIENT_CONFIG_PATH = "cluster/client/modifyConfig";

private static final String FETCH_CLUSTER_SERVER_ALL_CONFIG_PATH = "cluster/server/fetchConfig";
private static final String FETCH_CLUSTER_SERVER_BASIC_INFO_PATH = "cluster/server/info";

private static final String MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH = "cluster/server/modifyTransportConfig";
private static final String MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH = "cluster/server/modifyFlowConfig";
private static final String MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH = "cluster/server/modifyNamespaceSet";

private static final String FLOW_RULE_TYPE = "flow";
private static final String DEGRADE_RULE_TYPE = "degrade";
private static final String SYSTEM_RULE_TYPE = "system";
@@ -485,4 +503,175 @@ public class SentinelApiClient {
future.completeExceptionally(ex);
return future;
}

// Cluster related

public CompletableFuture<ClusterStateSimpleEntity> fetchClusterMode(String app, String ip, int port) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(FETCH_CLUSTER_MODE_PATH);
return executeCommand(FETCH_CLUSTER_MODE_PATH, uriBuilder.build())
.thenApply(r -> JSON.parseObject(r, ClusterStateSimpleEntity.class));
} catch (Exception ex) {
logger.warn("Error when fetching cluster mode", ex);
return newFailedFuture(ex);
}
}

public CompletableFuture<Void> modifyClusterMode(String app, String ip, int port, int mode) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(MODIFY_CLUSTER_MODE_PATH)
.setParameter("mode", String.valueOf(mode));
return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build())
.thenCompose(e -> {
if ("success".equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster mode: " + e);
return newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster mode", ex);
return newFailedFuture(ex);
}
}

public CompletableFuture<ClusterClientConfig> fetchClusterClientConfig(String app, String ip, int port) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(FETCH_CLUSTER_CLIENT_CONFIG_PATH);
return executeCommand(FETCH_CLUSTER_CLIENT_CONFIG_PATH, uriBuilder.build())
.thenApply(r -> JSON.parseObject(r, ClusterClientConfig.class));
} catch (Exception ex) {
logger.warn("Error when fetching cluster client config", ex);
return newFailedFuture(ex);
}
}

public CompletableFuture<Void> modifyClusterClientConfig(String app, String ip, int port, ClusterClientConfig config) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(MODIFY_CLUSTER_CLIENT_CONFIG_PATH)
.setParameter("data", JSON.toJSONString(config));
return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build())
.thenCompose(e -> {
if ("success".equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster client config: " + e);
return newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster client config", ex);
return newFailedFuture(ex);
}
}

public CompletableFuture<Void> modifyClusterServerFlowConfig(String app, String ip, int port, ServerFlowConfig config) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH)
.setParameter("data", JSON.toJSONString(config));
return executeCommand(MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH, uriBuilder.build())
.thenCompose(e -> {
if ("success".equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster server flow config: " + e);
return newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster server flow config", ex);
return newFailedFuture(ex);
}
}

public CompletableFuture<Void> modifyClusterServerTransportConfig(String app, String ip, int port, ServerTransportConfig config) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH)
.setParameter("port", config.getPort().toString())
.setParameter("idleSeconds", config.getIdleSeconds().toString());
return executeCommand(MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH, uriBuilder.build())
.thenCompose(e -> {
if ("success".equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster server transport config: " + e);
return newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster server transport config", ex);
return newFailedFuture(ex);
}
}

public CompletableFuture<Void> modifyClusterServerNamespaceSet(String app, String ip, int port, Set<String> set) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH)
.setParameter("data", JSON.toJSONString(set));
return executeCommand(MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH, uriBuilder.build())
.thenCompose(e -> {
if ("success".equals(e)) {
return CompletableFuture.completedFuture(null);
} else {
logger.warn("Error when modifying cluster server NamespaceSet: " + e);
return newFailedFuture(new RuntimeException(e));
}
});
} catch (Exception ex) {
logger.warn("Error when modifying cluster server NamespaceSet", ex);
return newFailedFuture(ex);
}
}

public CompletableFuture<ClusterServerStateVO> fetchClusterServerBasicInfo(String app, String ip, int port) {
if (StringUtil.isBlank(ip) || port <= 0) {
return newFailedFuture(new IllegalArgumentException("Invalid parameter"));
}
try {
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.setScheme("http").setHost(ip).setPort(port)
.setPath(FETCH_CLUSTER_SERVER_BASIC_INFO_PATH);
return executeCommand(FETCH_CLUSTER_SERVER_BASIC_INFO_PATH, uriBuilder.build())
.thenApply(r -> JSON.parseObject(r, ClusterServerStateVO.class));
} catch (Exception ex) {
logger.warn("Error when fetching cluster sever all config and basic info", ex);
return newFailedFuture(ex);
}
}
}

+ 7
- 6
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/config/WebConfig.java View File

@@ -15,6 +15,8 @@
*/
package com.taobao.csp.sentinel.dashboard.config;

import javax.servlet.Filter;

import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;

import org.slf4j.Logger;
@@ -31,7 +33,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
private static Logger logger = LoggerFactory.getLogger(WebConfig.class);

private final Logger logger = LoggerFactory.getLogger(WebConfig.class);

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
@@ -46,19 +49,17 @@ public class WebConfig extends WebMvcConfigurerAdapter {
/**
* Add {@link CommonFilter} to the server, this is the simplest way to use Sentinel
* for Web application.
*
* @return
*/
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
logger.info("sentinelFilterRegistration(), add CommonFilter");
FilterRegistrationBean registration = new FilterRegistrationBean();
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("sentinelFilter");
registration.setOrder(1);

logger.info("Sentinel servlet CommonFilter registered");

return registration;
}



+ 30
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/FlowRuleEntity.java View File

@@ -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;
}



+ 11
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/datasource/entity/rule/ParamFlowRuleEntity.java View File

@@ -17,6 +17,7 @@ package com.taobao.csp.sentinel.dashboard.datasource.entity.rule;

import java.util.List;

import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.util.AssertUtil;
@@ -73,4 +74,14 @@ public class ParamFlowRuleEntity extends AbstractRuleEntity<ParamFlowRule> {
public List<ParamFlowItem> getParamFlowItemList() {
return rule.getParamFlowItemList();
}

@JsonIgnore
public boolean isClusterMode() {
return rule.isClusterMode();
}

@JsonIgnore
public ParamFlowClusterConfig getClusterConfig() {
return rule.getClusterConfig();
}
}

+ 82
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientModifyRequest.java View File

@@ -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;
}
}

+ 44
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientStateVO.java View File

@@ -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 +
'}';
}
}

+ 31
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterModifyRequest.java View File

@@ -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();
}

+ 119
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerModifyRequest.java View File

@@ -0,0 +1,119 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.domain.cluster;

import java.util.Set;

import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;

/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterServerModifyRequest implements ClusterModifyRequest {

private String app;
private String ip;
private Integer port;

private Integer mode;
private ServerFlowConfig flowConfig;
private ServerTransportConfig transportConfig;
private Set<String> namespaceSet;

@Override
public String getApp() {
return app;
}

public ClusterServerModifyRequest setApp(String app) {
this.app = app;
return this;
}

@Override
public String getIp() {
return ip;
}

public ClusterServerModifyRequest setIp(String ip) {
this.ip = ip;
return this;
}

@Override
public Integer getPort() {
return port;
}

public ClusterServerModifyRequest setPort(Integer port) {
this.port = port;
return this;
}

@Override
public Integer getMode() {
return mode;
}

public ClusterServerModifyRequest setMode(Integer mode) {
this.mode = mode;
return this;
}

public ServerFlowConfig getFlowConfig() {
return flowConfig;
}

public ClusterServerModifyRequest setFlowConfig(
ServerFlowConfig flowConfig) {
this.flowConfig = flowConfig;
return this;
}

public ServerTransportConfig getTransportConfig() {
return transportConfig;
}

public ClusterServerModifyRequest setTransportConfig(
ServerTransportConfig transportConfig) {
this.transportConfig = transportConfig;
return this;
}

public Set<String> getNamespaceSet() {
return namespaceSet;
}

public ClusterServerModifyRequest setNamespaceSet(Set<String> namespaceSet) {
this.namespaceSet = namespaceSet;
return this;
}

@Override
public String toString() {
return "ClusterServerModifyRequest{" +
"app='" + app + '\'' +
", ip='" + ip + '\'' +
", port=" + port +
", mode=" + mode +
", flowConfig=" + flowConfig +
", transportConfig=" + transportConfig +
", namespaceSet=" + namespaceSet +
'}';
}
}

+ 94
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerStateVO.java View File

@@ -0,0 +1,94 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.domain.cluster;

import java.util.List;
import java.util.Set;

import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;

/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ClusterServerStateVO {

private ServerTransportConfig transport;
private ServerFlowConfig flow;
private Set<String> namespaceSet;

private Integer port;
private List<ConnectionGroupVO> connection;

public ServerTransportConfig getTransport() {
return transport;
}

public ClusterServerStateVO setTransport(
ServerTransportConfig transport) {
this.transport = transport;
return this;
}

public ServerFlowConfig getFlow() {
return flow;
}

public ClusterServerStateVO setFlow(ServerFlowConfig flow) {
this.flow = flow;
return this;
}

public Set<String> getNamespaceSet() {
return namespaceSet;
}

public ClusterServerStateVO setNamespaceSet(Set<String> namespaceSet) {
this.namespaceSet = namespaceSet;
return this;
}

public Integer getPort() {
return port;
}

public ClusterServerStateVO setPort(Integer port) {
this.port = port;
return this;
}

public List<ConnectionGroupVO> getConnection() {
return connection;
}

public ClusterServerStateVO setConnection(
List<ConnectionGroupVO> connection) {
this.connection = connection;
return this;
}

@Override
public String toString() {
return "ClusterServerStateVO{" +
"transport=" + transport +
", flow=" + flow +
", namespaceSet=" + namespaceSet +
", port=" + port +
", connection=" + connection +
'}';
}
}

+ 74
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSimpleEntity.java View File

@@ -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 +
'}';
}
}

+ 64
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterUniversalStateVO.java View File

@@ -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 +
'}';
}
}

+ 53
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionDescriptorVO.java View File

@@ -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 + '\'' +
'}';
}
}

+ 66
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ConnectionGroupVO.java View File

@@ -0,0 +1,66 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.domain.cluster;

import java.util.List;

/**
* @author Eric Zhao
* @since 1.4.0
*/
public class ConnectionGroupVO {

private String namespace;
private List<ConnectionDescriptorVO> connectionSet;
private Integer connectedCount;

public String getNamespace() {
return namespace;
}

public ConnectionGroupVO setNamespace(String namespace) {
this.namespace = namespace;
return this;
}

public List<ConnectionDescriptorVO> getConnectionSet() {
return connectionSet;
}

public ConnectionGroupVO setConnectionSet(
List<ConnectionDescriptorVO> connectionSet) {
this.connectionSet = connectionSet;
return this;
}

public Integer getConnectedCount() {
return connectedCount;
}

public ConnectionGroupVO setConnectedCount(Integer connectedCount) {
this.connectedCount = connectedCount;
return this;
}

@Override
public String toString() {
return "ConnectionGroupVO{" +
"namespace='" + namespace + '\'' +
", connectionSet=" + connectionSet +
", connectedCount=" + connectedCount +
'}';
}
}

+ 75
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ClusterClientConfig.java View File

@@ -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 +
'}';
}
}

+ 95
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerFlowConfig.java View File

@@ -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 +
'}';
}
}

+ 64
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/config/ServerTransportConfig.java View File

@@ -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 +
'}';
}
}

+ 16
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemFlowRuleStore.java View File

@@ -17,6 +17,8 @@ package com.taobao.csp.sentinel.dashboard.repository.rule;

import java.util.concurrent.atomic.AtomicLong;

import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import org.springframework.stereotype.Component;

@@ -34,4 +36,18 @@ public class InMemFlowRuleStore extends InMemoryRuleRepositoryAdapter<FlowRuleEn
protected long nextId() {
return ids.incrementAndGet();
}

@Override
protected FlowRuleEntity preProcess(FlowRuleEntity entity) {
if (entity != null && entity.isClusterMode()) {
ClusterFlowConfig config = entity.getClusterConfig();
if (config == null) {
config = new ClusterFlowConfig();
entity.setClusterConfig(config);
}
// Set cluster rule id.
config.setFlowId(entity.getId());
}
return entity;
}
}

+ 15
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemParamFlowRuleStore.java View File

@@ -17,6 +17,8 @@ package com.taobao.csp.sentinel.dashboard.repository.rule;

import java.util.concurrent.atomic.AtomicLong;

import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowClusterConfig;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.ParamFlowRuleEntity;
import org.springframework.stereotype.Component;

@@ -33,4 +35,17 @@ public class InMemParamFlowRuleStore extends InMemoryRuleRepositoryAdapter<Param
protected long nextId() {
return ids.incrementAndGet();
}

@Override
protected ParamFlowRuleEntity preProcess(ParamFlowRuleEntity entity) {
if (entity != null && entity.isClusterMode()) {
ParamFlowClusterConfig config = entity.getClusterConfig();
if (config == null) {
config = new ParamFlowClusterConfig();
}
// Set cluster rule id.
config.setFlowId(entity.getId());
}
return entity;
}
}

+ 35
- 6
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/InMemoryRuleRepositoryAdapter.java View File

@@ -19,7 +19,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import com.alibaba.csp.sentinel.util.AssertUtil;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.RuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
@@ -28,12 +29,15 @@ import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
* @author leyou
*/
public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implements RuleRepository<T, Long> {

/**
* {@code <machine, <id, rule>>}
*/
private Map<MachineInfo, Map<Long, T>> machineRules = new ConcurrentHashMap<>(16);
private Map<Long, T> allRules = new ConcurrentHashMap<>(16);

private Map<String, Map<Long, T>> appRules = new ConcurrentHashMap<>(16);

private static final int MAX_RULES_SIZE = 10000;

@Override
@@ -41,17 +45,25 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem
if (entity.getId() == null) {
entity.setId(nextId());
}
allRules.put(entity.getId(), entity);
machineRules.computeIfAbsent(MachineInfo.of(entity.getApp(), entity.getIp(), entity.getPort()),
e -> new ConcurrentHashMap<>(32))
.put(entity.getId(), entity);
return entity;
T processedEntity = preProcess(entity);
if (processedEntity != null) {
allRules.put(processedEntity.getId(), processedEntity);
machineRules.computeIfAbsent(MachineInfo.of(processedEntity.getApp(), processedEntity.getIp(),
processedEntity.getPort()), e -> new ConcurrentHashMap<>(32))
.put(processedEntity.getId(), processedEntity);
appRules.computeIfAbsent(processedEntity.getApp(), v -> new ConcurrentHashMap<>(32))
.put(processedEntity.getId(), processedEntity);
}

return processedEntity;
}

@Override
public List<T> saveAll(List<T> rules) {
// TODO: check here.
allRules.clear();
machineRules.clear();
appRules.clear();

if (rules == null) {
return null;
@@ -67,6 +79,9 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem
public T delete(Long id) {
T entity = allRules.remove(id);
if (entity != null) {
if (appRules.get(entity.getApp()) != null) {
appRules.get(entity.getApp()).remove(id);
}
machineRules.get(MachineInfo.of(entity.getApp(), entity.getIp(), entity.getPort())).remove(id);
}
return entity;
@@ -86,6 +101,20 @@ public abstract class InMemoryRuleRepositoryAdapter<T extends RuleEntity> implem
return new ArrayList<>(entities.values());
}

@Override
public List<T> findAllByApp(String appName) {
AssertUtil.notEmpty(appName, "appName cannot be empty");
Map<Long, T> entities = appRules.get(appName);
if (entities == null) {
return new ArrayList<>();
}
return new ArrayList<>(entities.values());
}

protected T preProcess(T entity) {
return entity;
}

/**
* Get next unused id.
*


+ 10
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/repository/rule/RuleRepository.java View File

@@ -25,6 +25,7 @@ import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
* @author leyou
*/
public interface RuleRepository<T, ID> {

/**
* Save one.
*
@@ -65,6 +66,15 @@ public interface RuleRepository<T, ID> {
*/
List<T> findAllByMachine(MachineInfo machineInfo);

/**
* Find all by application.
*
* @param appName valid app name
* @return all rules of the application
* @since 1.4.0
*/
List<T> findAllByApp(String appName);

///**
// * Find all by app and enable switch.
// * @param app


+ 25
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRuleProvider.java View File

@@ -0,0 +1,25 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.rule;

/**
* @author Eric Zhao
* @since 1.4.0
*/
public interface DynamicRuleProvider<T> {

T getRules(String appName) throws Exception;
}

+ 32
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/DynamicRulePublisher.java View File

@@ -0,0 +1,32 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.rule;

/**
* @author Eric Zhao
* @since 1.4.0
*/
public interface DynamicRulePublisher<T> {

/**
* Publish rules to remote rule configuration center for given application name.
*
* @param app app name
* @param rules list of rules to push
* @throws Exception if some error occurs
*/
void publish(String app, T rules) throws Exception;
}

+ 67
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiProvider.java View File

@@ -0,0 +1,67 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.rule;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.AppManagement;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.util.MachineUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* @author Eric Zhao
*/
@Component("flowRuleDefaultProvider")
public class FlowRuleApiProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {

@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;

@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
if (StringUtil.isBlank(appName)) {
return new ArrayList<>();
}
List<MachineInfo> list = appManagement.getDetailApp(appName).getMachines()
.stream()
.filter(MachineUtil::isMachineHealth)
.sorted((e1, e2) -> {
if (e1.getTimestamp().before(e2.getTimestamp())) {
return 1;
} else if (e1.getTimestamp().after(e2.getTimestamp())) {
return -1;
} else {
return 0;
}
}).collect(Collectors.toList());
if (list.isEmpty()) {
return new ArrayList<>();
} else {
MachineInfo machine = list.get(0);
return sentinelApiClient.fetchFlowRuleOfMachine(machine.getApp(), machine.getIp(), machine.getPort());
}
}
}

+ 61
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/rule/FlowRuleApiPublisher.java View File

@@ -0,0 +1,61 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.rule;

import java.util.List;
import java.util.Set;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.AppManagement;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.util.MachineUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("flowRuleDefaultPublisher")
public class FlowRuleApiPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {

@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;

@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
if (StringUtil.isBlank(app)) {
return;
}
if (rules == null || rules.isEmpty()) {
return;
}
Set<MachineInfo> set = appManagement.getDetailApp(app).getMachines();

for (MachineInfo machine : set) {
if (!MachineUtil.isMachineHealth(machine)) {
continue;
}
// TODO: parse the results
sentinelApiClient.setFlowRuleOfMachine(app, machine.getIp(), machine.getPort(), rules);
}
}
}

+ 116
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterConfigService.java View File

@@ -0,0 +1,116 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.service;

import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterClientModifyRequest;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterClientStateVO;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterServerModifyRequest;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterUniversalStateVO;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author Eric Zhao
* @since 1.4.0
*/
@Service
public class ClusterConfigService {

@Autowired
private SentinelApiClient sentinelApiClient;

public CompletableFuture<Void> modifyClusterClientConfig(ClusterClientModifyRequest request) {
if (notClientRequestValid(request)) {
throw new IllegalArgumentException("Invalid request");
}
String app = request.getApp();
String ip = request.getIp();
int port = request.getPort();
return sentinelApiClient.modifyClusterClientConfig(app, ip, port, request.getClientConfig())
.thenCompose(v -> sentinelApiClient.modifyClusterMode(app, ip, port, ClusterStateManager.CLUSTER_CLIENT));
}

private boolean notClientRequestValid(/*@NonNull */ ClusterClientModifyRequest request) {
ClusterClientConfig config = request.getClientConfig();
return config == null || StringUtil.isEmpty(config.getServerHost())
|| config.getServerPort() == null || config.getServerPort() <= 0
|| config.getRequestTimeout() == null || config.getRequestTimeout() <= 0;
}

public CompletableFuture<Void> modifyClusterServerConfig(ClusterServerModifyRequest request) {
ServerTransportConfig transportConfig = request.getTransportConfig();
ServerFlowConfig flowConfig = request.getFlowConfig();
Set<String> namespaceSet = request.getNamespaceSet();
if (invalidTransportConfig(transportConfig)) {
throw new IllegalArgumentException("Invalid transport config in request");
}
if (invalidFlowConfig(flowConfig)) {
throw new IllegalArgumentException("Invalid flow config in request");
}
if (namespaceSet == null) {
throw new IllegalArgumentException("namespace set cannot be null");
}
String app = request.getApp();
String ip = request.getIp();
int port = request.getPort();
return sentinelApiClient.modifyClusterServerNamespaceSet(app, ip, port, namespaceSet)
.thenCompose(v -> sentinelApiClient.modifyClusterServerTransportConfig(app, ip, port, transportConfig))
.thenCompose(v -> sentinelApiClient.modifyClusterServerFlowConfig(app, ip, port, flowConfig))
.thenCompose(v -> sentinelApiClient.modifyClusterMode(app, ip, port, ClusterStateManager.CLUSTER_SERVER));
}

public CompletableFuture<ClusterUniversalStateVO> getClusterUniversalState(String app, String ip, int port) {
return sentinelApiClient.fetchClusterMode(app, ip, port)
.thenApply(e -> new ClusterUniversalStateVO().setStateInfo(e))
.thenCompose(vo -> {
if (vo.getStateInfo().getClientAvailable()) {
return sentinelApiClient.fetchClusterClientConfig(app, ip, port)
.thenApply(cc -> vo.setClient(new ClusterClientStateVO().setClientConfig(cc)));
} else {
return CompletableFuture.completedFuture(vo);
}
}).thenCompose(vo -> {
if (vo.getStateInfo().getServerAvailable()) {
return sentinelApiClient.fetchClusterServerBasicInfo(app, ip, port)
.thenApply(vo::setServer);
} else {
return CompletableFuture.completedFuture(vo);
}
});
}

private boolean invalidTransportConfig(ServerTransportConfig transportConfig) {
return transportConfig == null || transportConfig.getPort() == null || transportConfig.getPort() <= 0
|| transportConfig.getIdleSeconds() == null || transportConfig.getIdleSeconds() <= 0;
}

private boolean invalidFlowConfig(ServerFlowConfig flowConfig) {
return flowConfig == null || flowConfig.getSampleCount() == null || flowConfig.getSampleCount() <= 0
|| flowConfig.getIntervalMs() == null || flowConfig.getIntervalMs() <= 0
|| flowConfig.getIntervalMs() % flowConfig.getSampleCount() != 0;
}
}

+ 32
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtil.java View File

@@ -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;
}
}

+ 182
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ClusterConfigController.java View File

@@ -0,0 +1,182 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.view;

import java.util.Optional;
import java.util.concurrent.ExecutionException;

import com.alibaba.csp.sentinel.cluster.ClusterStateManager;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import com.taobao.csp.sentinel.dashboard.client.CommandNotFoundException;
import com.taobao.csp.sentinel.dashboard.datasource.entity.SentinelVersion;
import com.taobao.csp.sentinel.dashboard.discovery.AppManagement;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterClientModifyRequest;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterModifyRequest;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterServerModifyRequest;
import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterUniversalStateVO;
import com.taobao.csp.sentinel.dashboard.service.ClusterConfigService;
import com.taobao.csp.sentinel.dashboard.util.VersionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Eric Zhao
* @since 1.4.0
*/
@RestController
@RequestMapping(value = "/cluster")
public class ClusterConfigController {

private final Logger logger = LoggerFactory.getLogger(ClusterConfigController.class);

private final SentinelVersion version140 = new SentinelVersion().setMajorVersion(1).setMinorVersion(4);

@Autowired
private AppManagement appManagement;

@Autowired
private ClusterConfigService clusterConfigService;

@PostMapping("/config/modify")
public Result<Boolean> apiModifyClusterConfig(@RequestBody String payload) {
if (StringUtil.isBlank(payload)) {
return Result.ofFail(-1, "empty request body");
}
try {
JSONObject body = JSON.parseObject(payload);
if (body.containsKey(KEY_MODE)) {
int mode = body.getInteger(KEY_MODE);
switch (mode) {
case ClusterStateManager.CLUSTER_CLIENT:
ClusterClientModifyRequest data = JSON.parseObject(payload, ClusterClientModifyRequest.class);
Result<Boolean> res = checkValidRequest(data);
if (res != null) {
return res;
}
clusterConfigService.modifyClusterClientConfig(data).get();
return Result.ofSuccess(true);
case ClusterStateManager.CLUSTER_SERVER:
ClusterServerModifyRequest d = JSON.parseObject(payload, ClusterServerModifyRequest.class);
Result<Boolean> r = checkValidRequest(d);
if (r != null) {
return r;
}
// TODO: bad design here, should refactor!
clusterConfigService.modifyClusterServerConfig(d).get();
return Result.ofSuccess(true);
default:
return Result.ofFail(-1, "invalid mode");
}
}
return Result.ofFail(-1, "invalid parameter");
} catch (ExecutionException ex) {
logger.error("Error when modifying cluster config", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable ex) {
logger.error("Error when modifying cluster config", ex);
return Result.ofFail(-1, ex.getMessage());
}
}

@GetMapping("/state")
public Result<ClusterUniversalStateVO> apiGetClusterState(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
if (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
return clusterConfigService.getClusterUniversalState(app, ip, port)
.thenApply(Result::ofSuccess)
.get();
} catch (ExecutionException ex) {
logger.error("Error when fetching cluster state", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when fetching cluster state", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}

private boolean isNotSupported(Throwable ex) {
return ex instanceof CommandNotFoundException;
}

private boolean checkIfSupported(String app, String ip, int port) {
try {
return Optional.ofNullable(appManagement.getDetailApp(app))
.flatMap(e -> e.getMachine(ip, port))
.flatMap(m -> VersionUtils.parseVersion(m.getVersion())
.map(v -> v.greaterOrEqual(version140)))
.orElse(true);
// If error occurred or cannot retrieve machine info, return true.
} catch (Exception ex) {
return true;
}
}

private Result<Boolean> checkValidRequest(ClusterModifyRequest request) {
if (StringUtil.isEmpty(request.getApp())) {
return Result.ofFail(-1, "app cannot be empty");
}
if (StringUtil.isEmpty(request.getIp())) {
return Result.ofFail(-1, "ip cannot be empty");
}
if (request.getPort() == null || request.getPort() < 0) {
return Result.ofFail(-1, "invalid port");
}
if (request.getMode() == null || request.getMode() < 0) {
return Result.ofFail(-1, "invalid mode");
}
if (!checkIfSupported(request.getApp(), request.getIp(), request.getPort())) {
return unsupportedVersion();
}
return null;
}

private <R> Result<R> unsupportedVersion() {
return Result.ofFail(4041, "Sentinel client not supported for cluster flow control (unsupported version or dependency absent)");
}

private static final String KEY_MODE = "mode";
}

sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowController.java → sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java View File

@@ -20,36 +20,44 @@ import java.util.List;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo;
import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient;
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemFlowRuleStore;
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* Flow rule controller.
*
* @author leyou
* @author Eric Zhao
*/
@Controller
@RequestMapping(value = "/flow", produces = MediaType.APPLICATION_JSON_VALUE)
public class FlowController {
private static Logger logger = LoggerFactory.getLogger(FlowController.class);
@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {

private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);

@Autowired
private InMemFlowRuleStore repository;
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

@Autowired
private SentinelApiClient sentinelApiClient;

@ResponseBody
@RequestMapping("/rules.json")
Result<List<FlowRuleEntity>> queryMachineRules(String app, String ip, Integer port) {
@GetMapping("/rules")
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
@@ -64,88 +72,82 @@ public class FlowController {
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("queryApps error:", throwable);
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}

@ResponseBody
@RequestMapping("/new.json")
Result<?> add(String app, String ip, Integer port, String limitApp, String resource, Integer grade,
Double count, Integer strategy, String refResource,
Integer controlBehavior, Integer warmUpPeriodSec, Integer maxQueueingTimeMs) {
if (StringUtil.isBlank(app)) {
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(ip)) {
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (port == null) {
if (entity.getPort() == null) {
return Result.ofFail(-1, "port can't be null");
}
if (StringUtil.isBlank(limitApp)) {
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(resource)) {
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (grade == null) {
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (grade != 0 && grade != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (count == null) {
return Result.ofFail(-1, "count can't be null");
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (strategy == null) {
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (strategy != 0 && StringUtil.isBlank(refResource)) {
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (controlBehavior == null) {
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
if (controlBehavior == 1 && warmUpPeriodSec == null) {
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && maxQueueingTimeMs == null) {
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
FlowRuleEntity entity = new FlowRuleEntity();
entity.setApp(app.trim());
entity.setIp(ip.trim());
entity.setPort(port);
entity.setLimitApp(limitApp.trim());
entity.setResource(resource.trim());
entity.setGrade(grade);
entity.setCount(count);
entity.setStrategy(strategy);
entity.setControlBehavior(controlBehavior);
entity.setWarmUpPeriodSec(warmUpPeriodSec);
entity.setMaxQueueingTimeMs(maxQueueingTimeMs);
if (strategy != 0) {
entity.setRefResource(refResource.trim());
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}

@PostMapping("/rule")
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
} catch (Throwable throwable) {
logger.error("add error:", throwable);
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(app, ip, port)) {
logger.info("publish flow rules fail after rule add");
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.error("Publish flow rules failed after rule add");
}
return Result.ofSuccess(entity);
}

@ResponseBody
@RequestMapping("/save.json")
Result<?> updateIfNotNull(Long id, String app,
@PutMapping("/save.json")
public Result<FlowRuleEntity> updateIfNotNull(Long id, String app,
String limitApp, String resource, Integer grade,
Double count, Integer strategy, String refResource,
Integer controlBehavior, Integer warmUpPeriodSec, Integer maxQueueingTimeMs) {
@@ -221,8 +223,7 @@ public class FlowController {
return Result.ofSuccess(entity);
}

@ResponseBody
@RequestMapping("/delete.json")
@DeleteMapping("/delete.json")
Result<?> delete(Long id) {
if (id == null) {
return Result.ofFail(-1, "id can't be null");

+ 209
- 0
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV2.java View File

@@ -0,0 +1,209 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.view;

import java.util.Date;
import java.util.List;

import com.alibaba.csp.sentinel.util.StringUtil;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import com.taobao.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.taobao.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* Flow rule controller (v2).
*
* @author Eric Zhao
* @since 1.4.0
*/
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {

private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);

@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;

@Autowired
@Qualifier("flowRuleDefaultProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleDefaultPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;

@GetMapping("/rules")
public Result<List<FlowRuleEntity>> apiQueryMachineRules(@RequestParam String app) {
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
try {
List<FlowRuleEntity> rules = ruleProvider.getRules(app);
if (rules != null && !rules.isEmpty()) {
for (FlowRuleEntity entity : rules) {
entity.setApp(app);
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
entity.setId(entity.getClusterConfig().getFlowId());
}
}
}
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}

private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}

@PostMapping("/rule")
public Result<FlowRuleEntity> apiAddFlowRule(@RequestBody FlowRuleEntity entity) {
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}

@PutMapping("/rule/{id}")
public Result<FlowRuleEntity> apiUpdateFlowRule(@PathVariable("id") Long id, @RequestBody FlowRuleEntity entity) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
entity.setApp(oldEntity.getApp());
entity.setIp(oldEntity.getIp());
entity.setPort(oldEntity.getPort());
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}

entity.setId(id);
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail");
}
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to update flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}

@DeleteMapping("/rule/{id}")
public Result<Long> apiDeleteRule(@PathVariable("id") Long id) {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
try {
repository.delete(id);
publishRules(oldEntity.getApp());
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
return Result.ofSuccess(id);
}

private void publishRules(/*@NonNull*/ String app) throws Exception {
List<FlowRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}

+ 5
- 4
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/MetricController.java View File

@@ -84,7 +84,8 @@ public class MetricController {
return Result.ofFail(-1, "time intervalMs is too big, must <= 1h");
}
List<String> resources = metricStore.listResourcesOfApp(app);
logger.info("queryTopResourceMetric(), resources.size()={}", resources.size());
logger.debug("queryTopResourceMetric(), resources.size()={}", resources.size());

if (resources == null || resources.isEmpty()) {
return Result.ofSuccess(null);
}
@@ -107,17 +108,17 @@ public class MetricController {
Math.min(pageIndex * pageSize, resources.size()));
}
final Map<String, Iterable<MetricVo>> map = new ConcurrentHashMap<>();
logger.info("topResource={}", topResource);
logger.debug("topResource={}", topResource);
long time = System.currentTimeMillis();
for (final String resource : topResource) {
List<MetricEntity> entities = metricStore.queryByAppAndResourceBetween(
app, resource, startTime, endTime);
logger.info("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size());
logger.debug("resource={}, entities.size()={}", resource, entities == null ? "null" : entities.size());
List<MetricVo> vos = MetricVo.fromMetricEntities(entities, resource);
Iterable<MetricVo> vosSorted = sortMetricVoAndDistinct(vos);
map.put(resource, vosSorted);
}
logger.info("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time);
logger.debug("queryTopResourceMetric() total query time={} ms", System.currentTimeMillis() - time);
Map<String, Object> resultMap = new HashMap<>(16);
resultMap.put("totalCount", resources.size());
resultMap.put("totalPage", totalPage);


+ 5
- 1
sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ParamFlowRuleController.java View File

@@ -179,6 +179,10 @@ public class ParamFlowRuleController {
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
@@ -188,7 +192,7 @@ public class ParamFlowRuleController {
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(null);
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);


+ 51
- 0
sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosProvider.java View File

@@ -0,0 +1,51 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.rule.nacos;

import java.util.ArrayList;
import java.util.List;

import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.nacos.api.config.ConfigService;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("flowRuleNacosProvider")
public class FlowRuleNacosProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {

@Autowired
private ConfigService configService;
@Autowired
private Converter<String, List<FlowRuleEntity>> converter;

@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String rules = configService.getConfig(appName + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, 3000);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return converter.convert(rules);
}
}

+ 50
- 0
sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/FlowRuleNacosPublisher.java View File

@@ -0,0 +1,50 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.rule.nacos;

import java.util.List;

import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.nacos.api.config.ConfigService;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.taobao.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* @author Eric Zhao
* @since 1.4.0
*/
@Component("flowRuleNacosPublisher")
public class FlowRuleNacosPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {

@Autowired
private ConfigService configService;
@Autowired
private Converter<List<FlowRuleEntity>, String> converter;

@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
configService.publishConfig(app + NacosConfigUtil.FLOW_DATA_ID_POSTFIX,
NacosConfigUtil.GROUP_ID, converter.convert(rules));
}
}

+ 50
- 0
sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfig.java View File

@@ -0,0 +1,50 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.taobao.csp.sentinel.dashboard.rule.nacos;

import java.util.List;

import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.ConfigFactory;
import com.alibaba.nacos.api.config.ConfigService;

import com.taobao.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Eric Zhao
* @since 1.4.0
*/
@Configuration
public class NacosConfig {

@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}

@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}

@Bean
public ConfigService nacosConfigService() throws Exception {
return ConfigFactory.createConfigService("localhost");
}
}

+ 41
- 0
sentinel-dashboard/src/test/java/com/taobao/csp/sentinel/dashboard/rule/nacos/NacosConfigUtil.java View File

@@ -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() {}
}

Loading…
Cancel
Save