From 8d413e16451fb5b7b045de202adb525a5f9522f4 Mon Sep 17 00:00:00 2001 From: Eric Zhao Date: Fri, 4 Jan 2019 14:31:39 +0800 Subject: [PATCH] Polish Sentinel dashboard backend for cluster flow control enhancement - Add cluster token server management controller and service for app - Other enhancements and fixes Signed-off-by: Eric Zhao --- sentinel-dashboard/pom.xml | 12 +- .../dashboard/DashboardApplication.java | 7 + .../CommandFailedException.java} | 21 +- .../client/CommandNotFoundException.java | 6 + .../dashboard/client/SentinelApiClient.java | 232 ++++++++--------- .../dashboard/discovery/MachineDiscovery.java | 2 +- .../dashboard/discovery/MachineInfo.java | 4 + .../cluster/ClusterAppAssignResultVO.java | 66 +++++ .../cluster/ClusterAppFullAssignRequest.java | 58 +++++ .../ClusterAppSingleServerAssignRequest.java | 56 +++++ .../domain/cluster/ClusterClientInfoVO.java | 76 ++++++ .../domain/cluster/ClusterGroupEntity.java | 91 +++++++ .../domain/cluster/ClusterStateSingleVO.java | 63 +++++ .../cluster/config/ServerFlowConfig.java | 13 + .../cluster/config/ServerTransportConfig.java | 2 +- .../cluster/request/ClusterAppAssignMap.java | 112 +++++++++ .../ClusterClientModifyRequest.java | 2 +- .../{ => request}/ClusterModifyRequest.java | 2 +- .../ClusterServerModifyRequest.java | 2 +- .../state/AppClusterClientStateWrapVO.java | 79 ++++++ .../state/AppClusterServerStateWrapVO.java | 102 ++++++++ .../{ => state}/ClusterClientStateVO.java | 14 +- .../cluster/state/ClusterRequestLimitVO.java | 63 +++++ .../{ => state}/ClusterServerStateVO.java | 33 ++- .../{ => state}/ClusterStateSimpleEntity.java | 2 +- .../state/ClusterUniversalStatePairVO.java | 72 ++++++ .../{ => state}/ClusterUniversalStateVO.java | 2 +- .../dashboard/metric/MetricFetcher.java | 20 +- .../dashboard/rule/FlowRuleApiProvider.java | 4 +- .../dashboard/rule/FlowRuleApiPublisher.java | 4 +- .../service/ClusterAssignService.java | 58 +++++ .../service/ClusterAssignServiceImpl.java | 235 ++++++++++++++++++ .../service/ClusterConfigService.java | 80 +++++- .../sentinel/dashboard/util/AsyncUtils.java | 72 ++++++ .../dashboard/util/ClusterEntityUtils.java | 138 ++++++++++ .../sentinel/dashboard/util/MachineUtils.java | 72 ++++++ .../dashboard/view/FlowControllerV1.java | 4 +- .../dashboard/view/FlowControllerV2.java | 2 + .../view/cluster/ClusterAssignController.java | 104 ++++++++ .../ClusterConfigController.java | 97 ++++++-- .../dashboard/rule/nacos/NacosConfigUtil.java | 1 + .../demo/cluster/ClusterClientDemo.java | 62 +++++ 42 files changed, 1964 insertions(+), 183 deletions(-) rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/{util/MachineUtil.java => client/CommandFailedException.java} (58%) create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppAssignResultVO.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppFullAssignRequest.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppSingleServerAssignRequest.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientInfoVO.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterGroupEntity.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSingleVO.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/request/ClusterAppAssignMap.java rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/{ => request}/ClusterClientModifyRequest.java (96%) rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/{ => request}/ClusterModifyRequest.java (92%) rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/{ => request}/ClusterServerModifyRequest.java (97%) create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterClientStateWrapVO.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterServerStateWrapVO.java rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/{ => state}/ClusterClientStateVO.java (72%) create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterRequestLimitVO.java rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/{ => state}/ClusterServerStateVO.java (70%) rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/{ => state}/ClusterStateSimpleEntity.java (96%) create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterUniversalStatePairVO.java rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/{ => state}/ClusterUniversalStateVO.java (96%) create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignService.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignServiceImpl.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/AsyncUtils.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/ClusterEntityUtils.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtils.java create mode 100644 sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/cluster/ClusterAssignController.java rename sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/{ => cluster}/ClusterConfigController.java (64%) create mode 100644 sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java diff --git a/sentinel-dashboard/pom.xml b/sentinel-dashboard/pom.xml index e0c17a37..6f15d3e5 100755 --- a/sentinel-dashboard/pom.xml +++ b/sentinel-dashboard/pom.xml @@ -56,12 +56,6 @@ spring-boot-starter-logging ${spring.boot.version} - - org.springframework.boot - spring-boot-devtools - ${spring.boot.version} - true - org.springframework.boot spring-boot-starter-test @@ -105,11 +99,17 @@ com.alibaba fastjson + junit junit test + + org.mockito + mockito-core + test + 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 dc6d2697..a56c0ac0 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 @@ -15,6 +15,8 @@ */ package com.taobao.csp.sentinel.dashboard; +import com.alibaba.csp.sentinel.init.InitExecutor; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -27,6 +29,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; public class DashboardApplication { public static void main(String[] args) { + triggerSentinelInit(); SpringApplication.run(DashboardApplication.class, args); } + + private static void triggerSentinelInit() { + new Thread(() -> InitExecutor.doInit()).start(); + } } 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/client/CommandFailedException.java similarity index 58% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtil.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/CommandFailedException.java index ac94dcd6..337c0c95 100644 --- 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/client/CommandFailedException.java @@ -13,20 +13,21 @@ * 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; +package com.taobao.csp.sentinel.dashboard.client; /** * @author Eric Zhao */ -public final class MachineUtil { +public class CommandFailedException extends RuntimeException { + + public CommandFailedException() {} + + public CommandFailedException(String message) { + super(message); + } - public static boolean isMachineHealth(MachineInfo machine) { - if (machine == null) { - return false; - } - return System.currentTimeMillis() - machine.getTimestamp().getTime() < MachineDiscovery.MAX_CLIENT_LIVE_TIME_MS; + @Override + public synchronized Throwable fillInStackTrace() { + return this; } } diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/CommandNotFoundException.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/CommandNotFoundException.java index 6f9418c9..3914a9e1 100644 --- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/CommandNotFoundException.java +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/client/CommandNotFoundException.java @@ -20,9 +20,15 @@ package com.taobao.csp.sentinel.dashboard.client; * @since 0.2.1 */ public class CommandNotFoundException extends Exception { + public CommandNotFoundException() { } public CommandNotFoundException(String message) { super(message); } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } } 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 6e82e787..6a512099 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 @@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; +import com.alibaba.csp.sentinel.command.CommandConstants; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.command.vo.NodeVo; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; @@ -43,8 +44,9 @@ 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.ClusterClientInfoVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterServerStateVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.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; @@ -63,6 +65,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import static com.taobao.csp.sentinel.dashboard.util.AsyncUtils.*; + /** * Communicate with Sentinel client. * @@ -104,7 +108,7 @@ public class SentinelApiClient { private final boolean enableHttps = false; public SentinelApiClient() { - IOReactorConfig ioConfig = IOReactorConfig.custom().setConnectTimeout(3000).setSoTimeout(3000) + IOReactorConfig ioConfig = IOReactorConfig.custom().setConnectTimeout(3000).setSoTimeout(10000) .setIoThreadCount(Runtime.getRuntime().availableProcessors() * 2).build(); httpClient = HttpAsyncClients.custom().setRedirectStrategy(new DefaultRedirectStrategy() { @Override @@ -115,6 +119,109 @@ public class SentinelApiClient { httpClient.start(); } + private boolean isSuccess(int statusCode) { + return statusCode >= 200 && statusCode < 300; + } + + private boolean isCommandNotFound(int statusCode, String body) { + return statusCode == 400 && StringUtil.isNotEmpty(body) && body.contains(CommandConstants.MSG_UNKNOWN_COMMAND_PREFIX); + } + + private CompletableFuture executeCommand(String command, URI uri) { + CompletableFuture future = new CompletableFuture<>(); + if (StringUtil.isBlank(command) || uri == null) { + future.completeExceptionally(new IllegalArgumentException("Bad URL or command name")); + return future; + } + final HttpGet httpGet = new HttpGet(uri); + httpClient.execute(httpGet, new FutureCallback() { + @Override + public void completed(final HttpResponse response) { + int statusCode = response.getStatusLine().getStatusCode(); + try { + String value = getBody(response); + if (isSuccess(statusCode)) { + future.complete(value); + } else { + if (isCommandNotFound(statusCode, value)) { + future.completeExceptionally(new CommandNotFoundException(command)); + } else { + future.completeExceptionally(new CommandFailedException(value)); + } + } + + } catch (Exception ex) { + future.completeExceptionally(ex); + logger.error("HTTP request failed: " + uri.toString(), ex); + } + } + + @Override + public void failed(final Exception ex) { + future.completeExceptionally(ex); + logger.error("HTTP request failed: " + uri.toString(), ex); + } + + @Override + public void cancelled() { + future.complete(null); + } + }); + return future; + } + + private String httpGetContent(String url) { + final HttpGet httpGet = new HttpGet(url); + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference reference = new AtomicReference<>(); + httpClient.execute(httpGet, new FutureCallback() { + @Override + public void completed(final HttpResponse response) { + try { + reference.set(getBody(response)); + } catch (Exception e) { + logger.info("httpGetContent " + url + " error:", e); + } finally { + latch.countDown(); + } + } + + @Override + public void failed(final Exception ex) { + latch.countDown(); + logger.info("httpGetContent " + url + " failed:", ex); + } + + @Override + public void cancelled() { + latch.countDown(); + } + }); + try { + latch.await(5, TimeUnit.SECONDS); + } catch (Exception e) { + logger.info("wait http client error:", e); + } + return reference.get(); + } + + private String getBody(HttpResponse response) throws Exception { + Charset charset = null; + try { + String contentTypeStr = response.getFirstHeader("Content-type").getValue(); + if (StringUtil.isNotEmpty(contentTypeStr)) { + ContentType contentType = ContentType.parse(contentTypeStr); + charset = contentType.getCharset(); + } + } catch (Exception ignore) { + } + return EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET); + } + + public void close() throws Exception { + httpClient.close(); + } + public List fetchResourceOfMachine(String ip, int port, String type) { String url = "http://" + ip + ":" + port + "/" + RESOURCE_URL_PATH + "?type=" + type; String body = httpGetContent(url); @@ -388,7 +495,7 @@ public class SentinelApiClient { .setParameter("data", data); return executeCommand(SET_PARAM_RULE_PATH, uriBuilder.build()) .thenCompose(e -> { - if ("success".equals(e)) { + if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Push parameter flow rules to client failed: " + e); @@ -401,109 +508,6 @@ public class SentinelApiClient { } } - private boolean isSuccess(int statusCode) { - return statusCode >= 200 && statusCode < 300; - } - - private CompletableFuture executeCommand(String command, URI uri) { - CompletableFuture future = new CompletableFuture<>(); - if (StringUtil.isBlank(command) || uri == null) { - future.completeExceptionally(new IllegalArgumentException("Bad URL or command name")); - return future; - } - final HttpGet httpGet = new HttpGet(uri); - httpClient.execute(httpGet, new FutureCallback() { - @Override - public void completed(final HttpResponse response) { - int statusCode = response.getStatusLine().getStatusCode(); - try { - String value = getBody(response); - if (isSuccess(statusCode)) { - future.complete(value); - } else { - if (statusCode == 400) { - future.completeExceptionally(new CommandNotFoundException(command)); - } else { - future.completeExceptionally(new IllegalStateException(value)); - } - } - - } catch (Exception ex) { - future.completeExceptionally(ex); - logger.error("HTTP request failed: " + uri.toString(), ex); - } - } - - @Override - public void failed(final Exception ex) { - future.completeExceptionally(ex); - logger.error("HTTP request failed: " + uri.toString(), ex); - } - - @Override - public void cancelled() { - future.complete(null); - } - }); - return future; - } - - private String httpGetContent(String url) { - final HttpGet httpGet = new HttpGet(url); - final CountDownLatch latch = new CountDownLatch(1); - final AtomicReference reference = new AtomicReference<>(); - httpClient.execute(httpGet, new FutureCallback() { - @Override - public void completed(final HttpResponse response) { - try { - reference.set(getBody(response)); - } catch (Exception e) { - logger.info("httpGetContent " + url + " error:", e); - } finally { - latch.countDown(); - } - } - - @Override - public void failed(final Exception ex) { - latch.countDown(); - logger.info("httpGetContent " + url + " failed:", ex); - } - - @Override - public void cancelled() { - latch.countDown(); - } - }); - try { - latch.await(5, TimeUnit.SECONDS); - } catch (Exception e) { - logger.info("wait http client error:", e); - } - return reference.get(); - } - - private String getBody(HttpResponse response) throws Exception { - Charset charset = null; - try { - String contentTypeStr = response.getFirstHeader("Content-type").getValue(); - ContentType contentType = ContentType.parse(contentTypeStr); - charset = contentType.getCharset(); - } catch (Exception ignore) { - } - return EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET); - } - - public void close() throws Exception { - httpClient.close(); - } - - private CompletableFuture newFailedFuture(Throwable ex) { - CompletableFuture future = new CompletableFuture<>(); - future.completeExceptionally(ex); - return future; - } - // Cluster related public CompletableFuture fetchClusterMode(String app, String ip, int port) { @@ -533,7 +537,7 @@ public class SentinelApiClient { .setParameter("mode", String.valueOf(mode)); return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build()) .thenCompose(e -> { - if ("success".equals(e)) { + if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster mode: " + e); @@ -546,7 +550,7 @@ public class SentinelApiClient { } } - public CompletableFuture fetchClusterClientConfig(String app, String ip, int port) { + public CompletableFuture fetchClusterClientInfoAndConfig(String app, String ip, int port) { if (StringUtil.isBlank(ip) || port <= 0) { return newFailedFuture(new IllegalArgumentException("Invalid parameter")); } @@ -555,7 +559,7 @@ public class SentinelApiClient { 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)); + .thenApply(r -> JSON.parseObject(r, ClusterClientInfoVO.class)); } catch (Exception ex) { logger.warn("Error when fetching cluster client config", ex); return newFailedFuture(ex); @@ -573,7 +577,7 @@ public class SentinelApiClient { .setParameter("data", JSON.toJSONString(config)); return executeCommand(MODIFY_CLUSTER_MODE_PATH, uriBuilder.build()) .thenCompose(e -> { - if ("success".equals(e)) { + if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster client config: " + e); @@ -597,7 +601,7 @@ public class SentinelApiClient { .setParameter("data", JSON.toJSONString(config)); return executeCommand(MODIFY_CLUSTER_SERVER_FLOW_CONFIG_PATH, uriBuilder.build()) .thenCompose(e -> { - if ("success".equals(e)) { + if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster server flow config: " + e); @@ -622,7 +626,7 @@ public class SentinelApiClient { .setParameter("idleSeconds", config.getIdleSeconds().toString()); return executeCommand(MODIFY_CLUSTER_SERVER_TRANSPORT_CONFIG_PATH, uriBuilder.build()) .thenCompose(e -> { - if ("success".equals(e)) { + if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster server transport config: " + e); @@ -646,7 +650,7 @@ public class SentinelApiClient { .setParameter("data", JSON.toJSONString(set)); return executeCommand(MODIFY_CLUSTER_SERVER_NAMESPACE_SET_PATH, uriBuilder.build()) .thenCompose(e -> { - if ("success".equals(e)) { + if (CommandConstants.MSG_SUCCESS.equals(e)) { return CompletableFuture.completedFuture(null); } else { logger.warn("Error when modifying cluster server NamespaceSet: " + e); diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineDiscovery.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineDiscovery.java index 989128bd..792bf451 100755 --- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineDiscovery.java +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineDiscovery.java @@ -21,7 +21,7 @@ import java.util.Set; public interface MachineDiscovery { long MAX_CLIENT_LIVE_TIME_MS = 1000 * 60 * 5; - String UNKNOWN_APP_NAME = "UNKNOWN"; + String UNKNOWN_APP_NAME = "CLUSTER_NOT_STARTED"; List getAppNames(); diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineInfo.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineInfo.java index dba7fe6c..51d2ef95 100755 --- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineInfo.java +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/discovery/MachineInfo.java @@ -41,6 +41,10 @@ public class MachineInfo implements Comparable { return machineInfo; } + public String toHostPort() { + return ip + ":" + port; + } + public Integer getPort() { return port; } diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppAssignResultVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppAssignResultVO.java new file mode 100644 index 00000000..4d684628 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppAssignResultVO.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.Set; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class ClusterAppAssignResultVO { + + private Set failedServerSet; + private Set failedClientSet; + + private Integer totalCount; + + public Set getFailedServerSet() { + return failedServerSet; + } + + public ClusterAppAssignResultVO setFailedServerSet(Set failedServerSet) { + this.failedServerSet = failedServerSet; + return this; + } + + public Set getFailedClientSet() { + return failedClientSet; + } + + public ClusterAppAssignResultVO setFailedClientSet(Set failedClientSet) { + this.failedClientSet = failedClientSet; + return this; + } + + public Integer getTotalCount() { + return totalCount; + } + + public ClusterAppAssignResultVO setTotalCount(Integer totalCount) { + this.totalCount = totalCount; + return this; + } + + @Override + public String toString() { + return "ClusterAppAssignResultVO{" + + "failedServerSet=" + failedServerSet + + ", failedClientSet=" + failedClientSet + + ", totalCount=" + totalCount + + '}'; + } +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppFullAssignRequest.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppFullAssignRequest.java new file mode 100644 index 00000000..d4590ff5 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppFullAssignRequest.java @@ -0,0 +1,58 @@ +/* + * 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.request.ClusterAppAssignMap; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class ClusterAppFullAssignRequest { + + private List clusterMap; + private Set remainingList; + + public List getClusterMap() { + return clusterMap; + } + + public ClusterAppFullAssignRequest setClusterMap( + List clusterMap) { + this.clusterMap = clusterMap; + return this; + } + + public Set getRemainingList() { + return remainingList; + } + + public ClusterAppFullAssignRequest setRemainingList(Set remainingList) { + this.remainingList = remainingList; + return this; + } + + @Override + public String toString() { + return "ClusterAppFullAssignRequest{" + + "clusterMap=" + clusterMap + + ", remainingList=" + remainingList + + '}'; + } +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppSingleServerAssignRequest.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppSingleServerAssignRequest.java new file mode 100644 index 00000000..db935032 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterAppSingleServerAssignRequest.java @@ -0,0 +1,56 @@ +/* + * 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.request.ClusterAppAssignMap; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class ClusterAppSingleServerAssignRequest { + + private ClusterAppAssignMap clusterMap; + private Set remainingList; + + public ClusterAppAssignMap getClusterMap() { + return clusterMap; + } + + public ClusterAppSingleServerAssignRequest setClusterMap(ClusterAppAssignMap clusterMap) { + this.clusterMap = clusterMap; + return this; + } + + public Set getRemainingList() { + return remainingList; + } + + public ClusterAppSingleServerAssignRequest setRemainingList(Set remainingList) { + this.remainingList = remainingList; + return this; + } + + @Override + public String toString() { + return "ClusterAppSingleServerAssignRequest{" + + "clusterMap=" + clusterMap + + ", remainingList=" + remainingList + + '}'; + } +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientInfoVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientInfoVO.java new file mode 100644 index 00000000..1bff214c --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientInfoVO.java @@ -0,0 +1,76 @@ +/* + * 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.1 + */ +public class ClusterClientInfoVO { + + private String serverHost; + private Integer serverPort; + + private Integer clientState; + + private Integer requestTimeout; + + public String getServerHost() { + return serverHost; + } + + public ClusterClientInfoVO setServerHost(String serverHost) { + this.serverHost = serverHost; + return this; + } + + public Integer getServerPort() { + return serverPort; + } + + public ClusterClientInfoVO setServerPort(Integer serverPort) { + this.serverPort = serverPort; + return this; + } + + public Integer getClientState() { + return clientState; + } + + public ClusterClientInfoVO setClientState(Integer clientState) { + this.clientState = clientState; + return this; + } + + public Integer getRequestTimeout() { + return requestTimeout; + } + + public ClusterClientInfoVO setRequestTimeout(Integer requestTimeout) { + this.requestTimeout = requestTimeout; + return this; + } + + @Override + public String toString() { + return "ClusterClientInfoVO{" + + "serverHost='" + serverHost + '\'' + + ", serverPort=" + serverPort + + ", clientState=" + clientState + + ", requestTimeout=" + requestTimeout + + '}'; + } +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterGroupEntity.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterGroupEntity.java new file mode 100644 index 00000000..dcbf46f4 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterGroupEntity.java @@ -0,0 +1,91 @@ +/* + * 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.HashSet; +import java.util.Set; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class ClusterGroupEntity { + + private String machineId; + + private String ip; + private Integer port; + + private Set clientSet = new HashSet<>(); + + private Boolean belongToApp; + + public String getMachineId() { + return machineId; + } + + public ClusterGroupEntity setMachineId(String machineId) { + this.machineId = machineId; + return this; + } + + public String getIp() { + return ip; + } + + public ClusterGroupEntity setIp(String ip) { + this.ip = ip; + return this; + } + + public Integer getPort() { + return port; + } + + public ClusterGroupEntity setPort(Integer port) { + this.port = port; + return this; + } + + public Set getClientSet() { + return clientSet; + } + + public ClusterGroupEntity setClientSet(Set clientSet) { + this.clientSet = clientSet; + return this; + } + + public Boolean getBelongToApp() { + return belongToApp; + } + + public ClusterGroupEntity setBelongToApp(Boolean belongToApp) { + this.belongToApp = belongToApp; + return this; + } + + @Override + public String toString() { + return "ClusterGroupEntity{" + + "machineId='" + machineId + '\'' + + ", ip='" + ip + '\'' + + ", port=" + port + + ", clientSet=" + clientSet + + ", belongToApp=" + belongToApp + + '}'; + } +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSingleVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSingleVO.java new file mode 100644 index 00000000..0013fa56 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSingleVO.java @@ -0,0 +1,63 @@ +/* + * 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.1 + */ +public class ClusterStateSingleVO { + + private String address; + private Integer mode; + private String target; + + public String getAddress() { + return address; + } + + public ClusterStateSingleVO setAddress(String address) { + this.address = address; + return this; + } + + public Integer getMode() { + return mode; + } + + public ClusterStateSingleVO setMode(Integer mode) { + this.mode = mode; + return this; + } + + public String getTarget() { + return target; + } + + public ClusterStateSingleVO setTarget(String target) { + this.target = target; + return this; + } + + @Override + public String toString() { + return "ClusterStateSingleVO{" + + "address='" + address + '\'' + + ", mode=" + mode + + ", target='" + target + '\'' + + '}'; + } +} 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 index f0e2d888..cc62c5c2 100644 --- 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 @@ -26,6 +26,7 @@ public class ServerFlowConfig { public static final int DEFAULT_INTERVAL_MS = 1000; public static final int DEFAULT_SAMPLE_COUNT= 10; + public static final double DEFAULT_MAX_ALLOWED_QPS= 30000; private final String namespace; @@ -34,6 +35,8 @@ public class ServerFlowConfig { private Integer intervalMs = DEFAULT_INTERVAL_MS; private Integer sampleCount = DEFAULT_SAMPLE_COUNT; + private Double maxAllowedQps = DEFAULT_MAX_ALLOWED_QPS; + public ServerFlowConfig() { this("default"); } @@ -82,6 +85,15 @@ public class ServerFlowConfig { return this; } + public Double getMaxAllowedQps() { + return maxAllowedQps; + } + + public ServerFlowConfig setMaxAllowedQps(Double maxAllowedQps) { + this.maxAllowedQps = maxAllowedQps; + return this; + } + @Override public String toString() { return "ServerFlowConfig{" + @@ -90,6 +102,7 @@ public class ServerFlowConfig { ", maxOccupyRatio=" + maxOccupyRatio + ", intervalMs=" + intervalMs + ", sampleCount=" + sampleCount + + ", maxAllowedQps=" + maxAllowedQps + '}'; } } 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 index dbf03be3..698da2e6 100644 --- 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 @@ -21,7 +21,7 @@ package com.taobao.csp.sentinel.dashboard.domain.cluster.config; */ public class ServerTransportConfig { - public static final int DEFAULT_PORT = 8730; + public static final int DEFAULT_PORT = 18730; public static final int DEFAULT_IDLE_SECONDS = 600; private Integer port; diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/request/ClusterAppAssignMap.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/request/ClusterAppAssignMap.java new file mode 100644 index 00000000..047ef383 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/request/ClusterAppAssignMap.java @@ -0,0 +1,112 @@ +/* + * 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.request; + +import java.util.Set; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class ClusterAppAssignMap { + + private String machineId; + private String ip; + private Integer port; + + private Boolean belongToApp; + + private Set clientSet; + + private Set namespaceSet; + private Double maxAllowedQps; + + public String getMachineId() { + return machineId; + } + + public ClusterAppAssignMap setMachineId(String machineId) { + this.machineId = machineId; + return this; + } + + public String getIp() { + return ip; + } + + public ClusterAppAssignMap setIp(String ip) { + this.ip = ip; + return this; + } + + public Integer getPort() { + return port; + } + + public ClusterAppAssignMap setPort(Integer port) { + this.port = port; + return this; + } + + public Set getClientSet() { + return clientSet; + } + + public ClusterAppAssignMap setClientSet(Set clientSet) { + this.clientSet = clientSet; + return this; + } + + public Set getNamespaceSet() { + return namespaceSet; + } + + public ClusterAppAssignMap setNamespaceSet(Set namespaceSet) { + this.namespaceSet = namespaceSet; + return this; + } + + public Boolean getBelongToApp() { + return belongToApp; + } + + public ClusterAppAssignMap setBelongToApp(Boolean belongToApp) { + this.belongToApp = belongToApp; + return this; + } + + public Double getMaxAllowedQps() { + return maxAllowedQps; + } + + public ClusterAppAssignMap setMaxAllowedQps(Double maxAllowedQps) { + this.maxAllowedQps = maxAllowedQps; + return this; + } + + @Override + public String toString() { + return "ClusterAppAssignMap{" + + "machineId='" + machineId + '\'' + + ", ip='" + ip + '\'' + + ", port=" + port + + ", belongToApp=" + belongToApp + + ", clientSet=" + clientSet + + ", namespaceSet=" + namespaceSet + + ", maxAllowedQps=" + maxAllowedQps + + '}'; + } +} 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/request/ClusterClientModifyRequest.java similarity index 96% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientModifyRequest.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/request/ClusterClientModifyRequest.java index 924e2fd9..9c7a28b6 100644 --- 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/request/ClusterClientModifyRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.domain.cluster; +package com.taobao.csp.sentinel.dashboard.domain.cluster.request; import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; 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/request/ClusterModifyRequest.java similarity index 92% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterModifyRequest.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/request/ClusterModifyRequest.java index 60fd8325..59220749 100644 --- 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/request/ClusterModifyRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.domain.cluster; +package com.taobao.csp.sentinel.dashboard.domain.cluster.request; /** * @author Eric Zhao 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/request/ClusterServerModifyRequest.java similarity index 97% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerModifyRequest.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/request/ClusterServerModifyRequest.java index a621187a..0d647940 100644 --- 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/request/ClusterServerModifyRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.domain.cluster; +package com.taobao.csp.sentinel.dashboard.domain.cluster.request; import java.util.Set; diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterClientStateWrapVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterClientStateWrapVO.java new file mode 100644 index 00000000..ba642c2c --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterClientStateWrapVO.java @@ -0,0 +1,79 @@ +/* + * 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.state; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class AppClusterClientStateWrapVO { + + /** + * {ip}@{transport_command_port}. + */ + private String id; + + private Integer commandPort; + private String ip; + + private ClusterClientStateVO state; + + public String getId() { + return id; + } + + public AppClusterClientStateWrapVO setId(String id) { + this.id = id; + return this; + } + + public String getIp() { + return ip; + } + + public AppClusterClientStateWrapVO setIp(String ip) { + this.ip = ip; + return this; + } + + public ClusterClientStateVO getState() { + return state; + } + + public AppClusterClientStateWrapVO setState(ClusterClientStateVO state) { + this.state = state; + return this; + } + + public Integer getCommandPort() { + return commandPort; + } + + public AppClusterClientStateWrapVO setCommandPort(Integer commandPort) { + this.commandPort = commandPort; + return this; + } + + @Override + public String toString() { + return "AppClusterClientStateWrapVO{" + + "id='" + id + '\'' + + ", commandPort=" + commandPort + + ", ip='" + ip + '\'' + + ", state=" + state + + '}'; + } +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterServerStateWrapVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterServerStateWrapVO.java new file mode 100644 index 00000000..1d7b06c8 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/AppClusterServerStateWrapVO.java @@ -0,0 +1,102 @@ +/* + * 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.state; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class AppClusterServerStateWrapVO { + + /** + * {ip}@{transport_command_port}. + */ + private String id; + + private String ip; + private Integer port; + + private Integer connectedCount; + + private Boolean belongToApp; + + private ClusterServerStateVO state; + + public String getId() { + return id; + } + + public AppClusterServerStateWrapVO setId(String id) { + this.id = id; + return this; + } + + public String getIp() { + return ip; + } + + public AppClusterServerStateWrapVO setIp(String ip) { + this.ip = ip; + return this; + } + + public Integer getPort() { + return port; + } + + public AppClusterServerStateWrapVO setPort(Integer port) { + this.port = port; + return this; + } + + public Boolean getBelongToApp() { + return belongToApp; + } + + public AppClusterServerStateWrapVO setBelongToApp(Boolean belongToApp) { + this.belongToApp = belongToApp; + return this; + } + + public Integer getConnectedCount() { + return connectedCount; + } + + public AppClusterServerStateWrapVO setConnectedCount(Integer connectedCount) { + this.connectedCount = connectedCount; + return this; + } + + public ClusterServerStateVO getState() { + return state; + } + + public AppClusterServerStateWrapVO setState(ClusterServerStateVO state) { + this.state = state; + return this; + } + + @Override + public String toString() { + return "AppClusterServerStateWrapVO{" + + "id='" + id + '\'' + + ", ip='" + ip + '\'' + + ", port='" + port + '\'' + + ", belongToApp=" + belongToApp + + ", state=" + state + + '}'; + } +} 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/state/ClusterClientStateVO.java similarity index 72% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterClientStateVO.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterClientStateVO.java index 3e9e73f4..f0ca39b4 100644 --- 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/state/ClusterClientStateVO.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.domain.cluster; +package com.taobao.csp.sentinel.dashboard.domain.cluster.state; -import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterClientInfoVO; /** * @author Eric Zhao @@ -23,14 +23,16 @@ import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConf */ public class ClusterClientStateVO { - private ClusterClientConfig clientConfig; + /** + * Cluster token client state. + */ + private ClusterClientInfoVO clientConfig; - public ClusterClientConfig getClientConfig() { + public ClusterClientInfoVO getClientConfig() { return clientConfig; } - public ClusterClientStateVO setClientConfig( - ClusterClientConfig clientConfig) { + public ClusterClientStateVO setClientConfig(ClusterClientInfoVO clientConfig) { this.clientConfig = clientConfig; return this; } diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterRequestLimitVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterRequestLimitVO.java new file mode 100644 index 00000000..ba0d0995 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterRequestLimitVO.java @@ -0,0 +1,63 @@ +/* + * 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.state; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class ClusterRequestLimitVO { + + private String namespace; + private Double currentQps; + private Double maxAllowedQps; + + public String getNamespace() { + return namespace; + } + + public ClusterRequestLimitVO setNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + public Double getCurrentQps() { + return currentQps; + } + + public ClusterRequestLimitVO setCurrentQps(Double currentQps) { + this.currentQps = currentQps; + return this; + } + + public Double getMaxAllowedQps() { + return maxAllowedQps; + } + + public ClusterRequestLimitVO setMaxAllowedQps(Double maxAllowedQps) { + this.maxAllowedQps = maxAllowedQps; + return this; + } + + @Override + public String toString() { + return "ClusterRequestLimitVO{" + + "namespace='" + namespace + '\'' + + ", currentQps=" + currentQps + + ", maxAllowedQps=" + maxAllowedQps + + '}'; + } +} 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/state/ClusterServerStateVO.java similarity index 70% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterServerStateVO.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterServerStateVO.java index 5a687893..e751696b 100644 --- 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/state/ClusterServerStateVO.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.domain.cluster; +package com.taobao.csp.sentinel.dashboard.domain.cluster.state; import java.util.List; import java.util.Set; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ConnectionGroupVO; import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig; import com.taobao.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig; @@ -32,14 +33,17 @@ public class ClusterServerStateVO { private Set namespaceSet; private Integer port; + private List connection; + private List requestLimitData; + + private Boolean embedded; public ServerTransportConfig getTransport() { return transport; } - public ClusterServerStateVO setTransport( - ServerTransportConfig transport) { + public ClusterServerStateVO setTransport(ServerTransportConfig transport) { this.transport = transport; return this; } @@ -75,12 +79,29 @@ public class ClusterServerStateVO { return connection; } - public ClusterServerStateVO setConnection( - List connection) { + public ClusterServerStateVO setConnection(List connection) { this.connection = connection; return this; } + public List getRequestLimitData() { + return requestLimitData; + } + + public ClusterServerStateVO setRequestLimitData(List requestLimitData) { + this.requestLimitData = requestLimitData; + return this; + } + + public Boolean getEmbedded() { + return embedded; + } + + public ClusterServerStateVO setEmbedded(Boolean embedded) { + this.embedded = embedded; + return this; + } + @Override public String toString() { return "ClusterServerStateVO{" + @@ -89,6 +110,8 @@ public class ClusterServerStateVO { ", namespaceSet=" + namespaceSet + ", port=" + port + ", connection=" + connection + + ", requestLimitData=" + requestLimitData + + ", embedded=" + embedded + '}'; } } 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/state/ClusterStateSimpleEntity.java similarity index 96% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterStateSimpleEntity.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterStateSimpleEntity.java index 52577c60..0ccf5e63 100644 --- 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/state/ClusterStateSimpleEntity.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.domain.cluster; +package com.taobao.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterUniversalStatePairVO.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterUniversalStatePairVO.java new file mode 100644 index 00000000..428915ca --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterUniversalStatePairVO.java @@ -0,0 +1,72 @@ +/* + * 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.state; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public class ClusterUniversalStatePairVO { + + private String ip; + private Integer commandPort; + + private ClusterUniversalStateVO state; + + public ClusterUniversalStatePairVO() {} + + public ClusterUniversalStatePairVO(String ip, Integer commandPort, ClusterUniversalStateVO state) { + this.ip = ip; + this.commandPort = commandPort; + this.state = state; + } + + public String getIp() { + return ip; + } + + public ClusterUniversalStatePairVO setIp(String ip) { + this.ip = ip; + return this; + } + + public Integer getCommandPort() { + return commandPort; + } + + public ClusterUniversalStatePairVO setCommandPort(Integer commandPort) { + this.commandPort = commandPort; + return this; + } + + public ClusterUniversalStateVO getState() { + return state; + } + + public ClusterUniversalStatePairVO setState(ClusterUniversalStateVO state) { + this.state = state; + return this; + } + + @Override + public String toString() { + return "ClusterUniversalStatePairVO{" + + "ip='" + ip + '\'' + + ", commandPort=" + commandPort + + ", state=" + state + + '}'; + } +} 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/state/ClusterUniversalStateVO.java similarity index 96% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/ClusterUniversalStateVO.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/domain/cluster/state/ClusterUniversalStateVO.java index 8038bcc5..78fa7ba3 100644 --- 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/state/ClusterUniversalStateVO.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.domain.cluster; +package com.taobao.csp.sentinel.dashboard.domain.cluster.state; /** * @author Eric Zhao diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/metric/MetricFetcher.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/metric/MetricFetcher.java index 4f429289..369d0353 100755 --- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/metric/MetricFetcher.java +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/metric/MetricFetcher.java @@ -15,6 +15,8 @@ */ package com.taobao.csp.sentinel.dashboard.metric; +import java.net.ConnectException; +import java.net.SocketTimeoutException; import java.nio.charset.Charset; import java.util.Date; import java.util.List; @@ -41,6 +43,7 @@ import com.taobao.csp.sentinel.dashboard.datasource.entity.MetricEntity; import com.taobao.csp.sentinel.dashboard.discovery.AppManagement; import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; import com.taobao.csp.sentinel.dashboard.repository.metric.MetricsRepository; +import com.taobao.csp.sentinel.dashboard.util.MachineUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.concurrent.FutureCallback; @@ -64,7 +67,6 @@ import org.springframework.stereotype.Component; @Component public class MetricFetcher { - public static final long MAX_CLIENT_LIVE_TIME_MS = 1000 * 60 * 5; public static final String NO_METRICS = "No metrics"; private static final int HTTP_OK = 200; private static final long MAX_LAST_FETCH_INTERVAL_MS = 1000 * 15; @@ -183,7 +185,7 @@ public class MetricFetcher { final CountDownLatch latch = new CountDownLatch(machines.size()); for (final MachineInfo machine : machines) { // dead - if (System.currentTimeMillis() - machine.getTimestamp().getTime() > MAX_CLIENT_LIVE_TIME_MS) { + if (System.currentTimeMillis() - machine.getTimestamp().getTime() > MachineUtils.getMaxClientTimeout()) { latch.countDown(); dead.incrementAndGet(); continue; @@ -210,7 +212,13 @@ public class MetricFetcher { latch.countDown(); fail.incrementAndGet(); httpGet.abort(); - logger.error(msg + " metric " + url + " failed:", ex); + if (ex instanceof SocketTimeoutException) { + logger.error("Failed to fetch metric from <{}>: socket timeout", url); + } else if (ex instanceof ConnectException) { + logger.error("Failed to fetch metric from <{}> (ConnectionException: {})", url, ex.getMessage()); + } else { + logger.error(msg + " metric " + url + " error", ex); + } } @Override @@ -273,8 +281,10 @@ public class MetricFetcher { Charset charset = null; try { String contentTypeStr = response.getFirstHeader("Content-type").getValue(); - ContentType contentType = ContentType.parse(contentTypeStr); - charset = contentType.getCharset(); + if (StringUtil.isNotEmpty(contentTypeStr)) { + ContentType contentType = ContentType.parse(contentTypeStr); + charset = contentType.getCharset(); + } } catch (Exception ignore) { } String body = EntityUtils.toString(response.getEntity(), charset != null ? charset : DEFAULT_CHARSET); 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 index 6978d2e7..57c2dc04 100644 --- 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 @@ -25,7 +25,7 @@ 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 com.taobao.csp.sentinel.dashboard.util.MachineUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -47,7 +47,7 @@ public class FlowRuleApiProvider implements DynamicRuleProvider list = appManagement.getDetailApp(appName).getMachines() .stream() - .filter(MachineUtil::isMachineHealth) + .filter(MachineUtils::isMachineHealth) .sorted((e1, e2) -> { if (e1.getTimestamp().before(e2.getTimestamp())) { return 1; 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 index ba2a821b..e8656c88 100644 --- 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 @@ -24,7 +24,7 @@ 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 com.taobao.csp.sentinel.dashboard.util.MachineUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -51,7 +51,7 @@ public class FlowRuleApiPublisher implements DynamicRulePublisher set = appManagement.getDetailApp(app).getMachines(); for (MachineInfo machine : set) { - if (!MachineUtil.isMachineHealth(machine)) { + if (!MachineUtils.isMachineHealth(machine)) { continue; } // TODO: parse the results diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignService.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignService.java new file mode 100644 index 00000000..3b2a1edf --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignService.java @@ -0,0 +1,58 @@ +/* + * 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.List; +import java.util.Set; + +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.request.ClusterAppAssignMap; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public interface ClusterAssignService { + + /** + * Unbind a specific cluster server and its clients. + * + * @param app app name + * @param machineId valid machine ID ({@code host@commandPort}) + * @return assign result + */ + ClusterAppAssignResultVO unbindClusterServer(String app, String machineId); + + /** + * Unbind a set of cluster servers and its clients. + * + * @param app app name + * @param machineIdSet set of valid machine ID ({@code host@commandPort}) + * @return assign result + */ + ClusterAppAssignResultVO unbindClusterServers(String app, Set machineIdSet); + + /** + * Apply cluster server and client assignment for provided app. + * + * @param app app name + * @param clusterMap cluster assign map (server -> clients) + * @param remainingSet unassigned set of machine ID + * @return assign result + */ + ClusterAppAssignResultVO applyAssignToApp(String app, List clusterMap, + Set remainingSet); +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignServiceImpl.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignServiceImpl.java new file mode 100644 index 00000000..c7ebda82 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/service/ClusterAssignServiceImpl.java @@ -0,0 +1,235 @@ +/* + * 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.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.function.Tuple2; + +import com.taobao.csp.sentinel.dashboard.client.SentinelApiClient; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity; +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.domain.cluster.request.ClusterAppAssignMap; +import com.taobao.csp.sentinel.dashboard.util.MachineUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +@Service +public class ClusterAssignServiceImpl implements ClusterAssignService { + + private final Logger LOGGER = LoggerFactory.getLogger(ClusterAssignServiceImpl.class); + + @Autowired + private SentinelApiClient sentinelApiClient; + @Autowired + private ClusterConfigService clusterConfigService; + + @Override + public ClusterAppAssignResultVO unbindClusterServer(String app, String machineId) { + AssertUtil.assertNotBlank(app, "app cannot be blank"); + AssertUtil.assertNotBlank(machineId, "machineId cannot be blank"); + Set failedSet = new HashSet<>(); + + try { + ClusterGroupEntity entity = clusterConfigService.getClusterUniversalStateForAppMachine(app, machineId) + .get(10, TimeUnit.SECONDS); + Set toModifySet = new HashSet<>(); + toModifySet.add(machineId); + if (entity.getClientSet() != null) { + toModifySet.addAll(entity.getClientSet()); + } + // Modify mode to NOT-STARTED for all chosen token servers and associated token clients. + toModifySet.parallelStream() + .map(MachineUtils::parseCommandIpAndPort) + .filter(Optional::isPresent) + .map(Optional::get) + .map(e -> { + CompletableFuture f = modifyMode(app, e.r1, e.r2, ClusterStateManager.CLUSTER_NOT_STARTED); + return Tuple2.of(e.r1 + '@' + e.r2, f); + }) + .forEach(f -> handleFutureSync(f, failedSet)); + } catch (Exception ex) { + Throwable e = ex instanceof ExecutionException ? ex.getCause() : ex; + LOGGER.error("Failed to unbind machine <{}>", machineId, e); + failedSet.add(machineId); + } + return new ClusterAppAssignResultVO() + .setFailedClientSet(failedSet) + .setFailedServerSet(new HashSet<>()); + } + + @Override + public ClusterAppAssignResultVO unbindClusterServers(String app, Set machineIdSet) { + AssertUtil.assertNotBlank(app, "app cannot be blank"); + AssertUtil.isTrue(machineIdSet != null && !machineIdSet.isEmpty(), "machineIdSet cannot be empty"); + ClusterAppAssignResultVO result = new ClusterAppAssignResultVO() + .setFailedClientSet(new HashSet<>()) + .setFailedServerSet(new HashSet<>()); + for (String machineId : machineIdSet) { + ClusterAppAssignResultVO resultVO = unbindClusterServer(app, machineId); + result.getFailedClientSet().addAll(resultVO.getFailedClientSet()); + result.getFailedServerSet().addAll(resultVO.getFailedServerSet()); + } + return result; + } + + @Override + public ClusterAppAssignResultVO applyAssignToApp(String app, List clusterMap, + Set remainingSet) { + AssertUtil.assertNotBlank(app, "app cannot be blank"); + AssertUtil.notNull(clusterMap, "clusterMap cannot be null"); + Set failedServerSet = new HashSet<>(); + Set failedClientSet = new HashSet<>(); + + // Assign server and apply config. + clusterMap.stream() + .filter(Objects::nonNull) + .filter(ClusterAppAssignMap::getBelongToApp) + .map(e -> { + String ip = e.getIp(); + int commandPort = parsePort(e); + CompletableFuture f = modifyMode(app, ip, commandPort, ClusterStateManager.CLUSTER_SERVER) + .thenCompose(v -> applyServerConfigChange(app, ip, commandPort, e)); + return Tuple2.of(e.getMachineId(), f); + }) + .forEach(t -> handleFutureSync(t, failedServerSet)); + + // Assign client of servers and apply config. + clusterMap.parallelStream() + .filter(Objects::nonNull) + .forEach(e -> applyAllClientConfigChange(app, e, failedClientSet)); + + // Unbind remaining (unassigned) machines. + applyAllRemainingMachineSet(app, remainingSet, failedClientSet); + + return new ClusterAppAssignResultVO() + .setFailedClientSet(failedClientSet) + .setFailedServerSet(failedServerSet); + } + + private void applyAllRemainingMachineSet(String app, Set remainingSet, Set failedSet) { + if (remainingSet == null || remainingSet.isEmpty()) { + return; + } + remainingSet.parallelStream() + .filter(Objects::nonNull) + .map(MachineUtils::parseCommandIpAndPort) + .filter(Optional::isPresent) + .map(Optional::get) + .map(ipPort -> { + String ip = ipPort.r1; + int commandPort = ipPort.r2; + CompletableFuture f = modifyMode(app, ip, commandPort, ClusterStateManager.CLUSTER_NOT_STARTED); + return Tuple2.of(ip + '@' + commandPort, f); + }) + .forEach(t -> handleFutureSync(t, failedSet)); + } + + private void applyAllClientConfigChange(String app, ClusterAppAssignMap assignMap, + Set failedSet) { + Set clientSet = assignMap.getClientSet(); + if (clientSet == null || clientSet.isEmpty()) { + return; + } + final String serverIp = assignMap.getIp(); + final int serverPort = assignMap.getPort(); + clientSet.stream() + .map(MachineUtils::parseCommandIpAndPort) + .filter(Optional::isPresent) + .map(Optional::get) + .map(ipPort -> { + CompletableFuture f = sentinelApiClient + .modifyClusterMode(app, ipPort.r1, ipPort.r2, ClusterStateManager.CLUSTER_CLIENT) + .thenCompose(v -> sentinelApiClient.modifyClusterClientConfig(app, ipPort.r1, ipPort.r2, + new ClusterClientConfig().setRequestTimeout(20) + .setServerHost(serverIp) + .setServerPort(serverPort) + )); + return Tuple2.of(ipPort.r1 + '@' + ipPort.r2, f); + }) + .forEach(t -> handleFutureSync(t, failedSet)); + } + + private void handleFutureSync(Tuple2> t, Set failedSet) { + try { + t.r2.get(10, TimeUnit.SECONDS); + } catch (Exception ex) { + if (ex instanceof ExecutionException) { + LOGGER.error("Request for <{}> failed", t.r1, ex.getCause()); + } else { + LOGGER.error("Request for <{}> failed", t.r1, ex); + } + failedSet.add(t.r1); + } + } + + private CompletableFuture applyServerConfigChange(String app, String ip, int commandPort, + ClusterAppAssignMap assignMap) { + ServerTransportConfig transportConfig = new ServerTransportConfig() + .setPort(assignMap.getPort()) + .setIdleSeconds(600); + return sentinelApiClient.modifyClusterServerTransportConfig(app, ip, commandPort, transportConfig) + .thenCompose(v -> applyServerFlowConfigChange(app, ip, commandPort, assignMap)) + .thenCompose(v -> applyServerNamespaceSetConfig(app, ip, commandPort, assignMap)); + } + + private CompletableFuture applyServerFlowConfigChange(String app, String ip, int commandPort, + ClusterAppAssignMap assignMap) { + Double maxAllowedQps = assignMap.getMaxAllowedQps(); + if (maxAllowedQps == null || maxAllowedQps <= 0 || maxAllowedQps > 20_0000) { + return CompletableFuture.completedFuture(null); + } + return sentinelApiClient.modifyClusterServerFlowConfig(app, ip, commandPort, + new ServerFlowConfig().setMaxAllowedQps(maxAllowedQps)); + } + + private CompletableFuture applyServerNamespaceSetConfig(String app, String ip, int commandPort, + ClusterAppAssignMap assignMap) { + Set namespaceSet = assignMap.getNamespaceSet(); + if (namespaceSet == null || namespaceSet.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + return sentinelApiClient.modifyClusterServerNamespaceSet(app, ip, commandPort, namespaceSet); + } + + private CompletableFuture modifyMode(String app, String ip, int port, int mode) { + return sentinelApiClient.modifyClusterMode(app, ip, port, mode); + } + + private int parsePort(ClusterAppAssignMap assignMap) { + return MachineUtils.parseCommandPort(assignMap.getMachineId()) + .orElse(ServerTransportConfig.DEFAULT_PORT); + } +} 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 index ea334963..0e91c617 100644 --- 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 @@ -15,21 +15,30 @@ */ package com.taobao.csp.sentinel.dashboard.service; +import java.util.ArrayList; +import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; 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.discovery.AppInfo; +import com.taobao.csp.sentinel.dashboard.discovery.AppManagement; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity; +import com.taobao.csp.sentinel.dashboard.domain.cluster.request.ClusterClientModifyRequest; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterClientStateVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.request.ClusterServerModifyRequest; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.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 com.taobao.csp.sentinel.dashboard.util.AsyncUtils; +import com.taobao.csp.sentinel.dashboard.util.ClusterEntityUtils; +import com.taobao.csp.sentinel.dashboard.util.MachineUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -42,6 +51,8 @@ public class ClusterConfigService { @Autowired private SentinelApiClient sentinelApiClient; + @Autowired + private AppManagement appManagement; public CompletableFuture modifyClusterClientConfig(ClusterClientModifyRequest request) { if (notClientRequestValid(request)) { @@ -51,7 +62,7 @@ public class ClusterConfigService { 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)); + .thenCompose(v -> sentinelApiClient.modifyClusterMode(app, ip, port, ClusterStateManager.CLUSTER_CLIENT)); } private boolean notClientRequestValid(/*@NonNull */ ClusterClientModifyRequest request) { @@ -83,12 +94,64 @@ public class ClusterConfigService { .thenCompose(v -> sentinelApiClient.modifyClusterMode(app, ip, port, ClusterStateManager.CLUSTER_SERVER)); } + /** + * Get cluster state list of all available machines of provided application. + * + * @param app application name + * @return cluster state list of all available machines of the application + * @since 1.4.1 + */ + public CompletableFuture> getClusterUniversalState(String app) { + if (StringUtil.isBlank(app)) { + return AsyncUtils.newFailedFuture(new IllegalArgumentException("app cannot be empty")); + } + AppInfo appInfo = appManagement.getDetailApp(app); + if (appInfo == null || appInfo.getMachines() == null) { + return CompletableFuture.completedFuture(new ArrayList<>()); + } + + List> futures = appInfo.getMachines().stream() + .filter(MachineUtils::isMachineHealth) + .map(machine -> getClusterUniversalState(app, machine.getIp(), machine.getPort()) + .thenApply(e -> new ClusterUniversalStatePairVO(machine.getIp(), machine.getPort(), e))) + .collect(Collectors.toList()); + + return AsyncUtils.sequenceSuccessFuture(futures); + } + + public CompletableFuture getClusterUniversalStateForAppMachine(String app, String machineId) { + if (StringUtil.isBlank(app)) { + return AsyncUtils.newFailedFuture(new IllegalArgumentException("app cannot be empty")); + } + AppInfo appInfo = appManagement.getDetailApp(app); + if (appInfo == null || appInfo.getMachines() == null) { + return AsyncUtils.newFailedFuture(new IllegalArgumentException("app does not have machines")); + } + + boolean machineOk = appInfo.getMachines().stream() + .filter(MachineUtils::isMachineHealth) + .map(e -> e.getIp() + '@' + e.getPort()) + .anyMatch(e -> e.equals(machineId)); + if (!machineOk) { + return AsyncUtils.newFailedFuture(new IllegalStateException("machine does not exist or disconnected")); + } + + return getClusterUniversalState(app) + .thenApply(ClusterEntityUtils::wrapToClusterGroup) + .thenCompose(e -> e.stream() + .filter(e1 -> e1.getMachineId().equals(machineId)) + .findAny() + .map(CompletableFuture::completedFuture) + .orElse(AsyncUtils.newFailedFuture(new IllegalStateException("not a server: " + machineId))) + ); + } + 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) + return sentinelApiClient.fetchClusterClientInfoAndConfig(app, ip, port) .thenApply(cc -> vo.setClient(new ClusterClientStateVO().setClientConfig(cc))); } else { return CompletableFuture.completedFuture(vo); @@ -111,6 +174,7 @@ public class ClusterConfigService { 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; + || flowConfig.getIntervalMs() % flowConfig.getSampleCount() != 0 + || flowConfig.getMaxAllowedQps() == null || flowConfig.getMaxAllowedQps() < 0; } } diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/AsyncUtils.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/AsyncUtils.java new file mode 100644 index 00000000..20563b61 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/AsyncUtils.java @@ -0,0 +1,72 @@ +/* + * 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 java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public final class AsyncUtils { + + private static final Logger LOG = LoggerFactory.getLogger(AsyncUtils.class); + + public static CompletableFuture newFailedFuture(Throwable ex) { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(ex); + return future; + } + + public static CompletableFuture> sequenceFuture(List> futures) { + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream() + .map(AsyncUtils::getValue) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + ); + } + + public static CompletableFuture> sequenceSuccessFuture(List> futures) { + return CompletableFuture.supplyAsync(() -> futures.parallelStream() + .map(AsyncUtils::getValue) + .filter(Objects::nonNull) + .collect(Collectors.toList()) + ); + } + + public static T getValue(CompletableFuture future) { + try { + return future.get(10, TimeUnit.SECONDS); + } catch (Exception ex) { + LOG.error("getValue for async result failed", ex); + } + return null; + } + + public static boolean isSuccessFuture(CompletableFuture future) { + return future.isDone() && !future.isCompletedExceptionally() && !future.isCancelled(); + } + + private AsyncUtils() {} +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/ClusterEntityUtils.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/ClusterEntityUtils.java new file mode 100644 index 00000000..06f291ba --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/ClusterEntityUtils.java @@ -0,0 +1,138 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.util.StringUtil; + +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterGroupEntity; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ConnectionGroupVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.AppClusterClientStateWrapVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.AppClusterServerStateWrapVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterClientStateVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterServerStateVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +public final class ClusterEntityUtils { + + public static List wrapToAppClusterServerState( + List list) { + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + Map map = new HashMap<>(); + for (ClusterUniversalStatePairVO stateVO : list) { + int mode = stateVO.getState().getStateInfo().getMode(); + + if (mode == ClusterStateManager.CLUSTER_SERVER) { + String ip = stateVO.getIp(); + String serverId = ip + '@' + stateVO.getCommandPort(); + ClusterServerStateVO serverStateVO = stateVO.getState().getServer(); + map.computeIfAbsent(serverId, v -> new AppClusterServerStateWrapVO() + .setId(serverId) + .setIp(ip) + .setPort(serverStateVO.getPort()) + .setState(serverStateVO) + .setConnectedCount(serverStateVO.getConnection().stream() + .mapToInt(ConnectionGroupVO::getConnectedCount) + .sum() + ) + ); + } + } + return new ArrayList<>(map.values()); + } + + public static List wrapToAppClusterClientState( + List list) { + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + Map map = new HashMap<>(); + for (ClusterUniversalStatePairVO stateVO : list) { + int mode = stateVO.getState().getStateInfo().getMode(); + + if (mode == ClusterStateManager.CLUSTER_CLIENT) { + String ip = stateVO.getIp(); + String clientId = ip + '@' + stateVO.getCommandPort(); + ClusterClientStateVO clientStateVO = stateVO.getState().getClient(); + map.computeIfAbsent(clientId, v -> new AppClusterClientStateWrapVO() + .setId(clientId) + .setIp(ip) + .setState(clientStateVO) + .setCommandPort(stateVO.getCommandPort()) + ); + } + } + return new ArrayList<>(map.values()); + } + + public static List wrapToClusterGroup(List list) { + if (list == null || list.isEmpty()) { + return new ArrayList<>(); + } + Map map = new HashMap<>(); + for (ClusterUniversalStatePairVO stateVO : list) { + int mode = stateVO.getState().getStateInfo().getMode(); + String ip = stateVO.getIp(); + if (mode == ClusterStateManager.CLUSTER_SERVER) { + String serverAddress = getIp(ip); + int port = stateVO.getState().getServer().getPort(); + map.computeIfAbsent(serverAddress, v -> new ClusterGroupEntity() + .setBelongToApp(true).setMachineId(ip + '@' + stateVO.getCommandPort()) + .setIp(ip).setPort(port) + ); + } + } + for (ClusterUniversalStatePairVO stateVO : list) { + int mode = stateVO.getState().getStateInfo().getMode(); + String ip = stateVO.getIp(); + if (mode == ClusterStateManager.CLUSTER_CLIENT) { + String targetServer = stateVO.getState().getClient().getClientConfig().getServerHost(); + Integer targetPort = stateVO.getState().getClient().getClientConfig().getServerPort(); + if (StringUtil.isBlank(targetServer) || targetPort == null || targetPort <= 0) { + continue; + } + + ClusterGroupEntity group = map.computeIfAbsent(targetServer, + v -> new ClusterGroupEntity() + .setBelongToApp(true).setMachineId(targetServer) + .setIp(targetServer).setPort(targetPort) + ); + group.getClientSet().add(ip + '@' + stateVO.getCommandPort()); + } + } + return new ArrayList<>(map.values()); + } + + private static String getIp(String str) { + if (str.contains(":")) { + return str.split(":")[0]; + } + return str; + } + + private ClusterEntityUtils() {} +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtils.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtils.java new file mode 100644 index 00000000..b7836287 --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/util/MachineUtils.java @@ -0,0 +1,72 @@ +/* + * 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 java.util.Optional; + +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.csp.sentinel.util.function.Tuple2; + +import com.taobao.csp.sentinel.dashboard.discovery.MachineInfo; + +/** + * @author Eric Zhao + */ +public final class MachineUtils { + + public static final long DEFAULT_MAX_CLIENT_PING_TIMEOUT = 60 * 1000; + + public static long getMaxClientTimeout() { + return DEFAULT_MAX_CLIENT_PING_TIMEOUT; + } + + public static Optional parseCommandPort(String machineIp) { + try { + if (!machineIp.contains("@")) { + return Optional.empty(); + } + String[] str = machineIp.split("@"); + if (str.length <= 1) { + return Optional.empty(); + } + return Optional.of(Integer.parseInt(str[1])); + } catch (Exception ex) { + return Optional.empty(); + } + } + + public static Optional> parseCommandIpAndPort(String machineIp) { + try { + if (StringUtil.isEmpty(machineIp) || !machineIp.contains("@")) { + return Optional.empty(); + } + String[] str = machineIp.split("@"); + if (str.length <= 1) { + return Optional.empty(); + } + return Optional.of(Tuple2.of(str[0], Integer.parseInt(str[1]))); + } catch (Exception ex) { + return Optional.empty(); + } + } + + public static boolean isMachineHealth(MachineInfo machine) { + if (machine == null) { + return false; + } + return System.currentTimeMillis() - machine.getTimestamp().getTime() < getMaxClientTimeout(); + } +} diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java index 9813896f..dd435fe1 100755 --- a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/FlowControllerV1.java @@ -134,6 +134,8 @@ public class FlowControllerV1 { Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); + entity.setLimitApp(entity.getLimitApp().trim()); + entity.setResource(entity.getResource().trim()); try { entity = repository.save(entity); } catch (Throwable throwable) { @@ -224,7 +226,7 @@ public class FlowControllerV1 { } @DeleteMapping("/delete.json") - Result delete(Long id) { + public 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 index 52e3cf93..c3bf5bf9 100755 --- 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 @@ -137,6 +137,8 @@ public class FlowControllerV2 { Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); + entity.setLimitApp(entity.getLimitApp().trim()); + entity.setResource(entity.getResource().trim()); try { entity = repository.save(entity); publishRules(entity.getApp()); diff --git a/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/cluster/ClusterAssignController.java b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/cluster/ClusterAssignController.java new file mode 100644 index 00000000..0a91e56b --- /dev/null +++ b/sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/cluster/ClusterAssignController.java @@ -0,0 +1,104 @@ +/* + * 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.cluster; + +import java.util.Collections; +import java.util.Set; + +import com.alibaba.csp.sentinel.util.StringUtil; + +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterAppFullAssignRequest; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterAppAssignResultVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.ClusterAppSingleServerAssignRequest; +import com.taobao.csp.sentinel.dashboard.service.ClusterAssignService; +import com.taobao.csp.sentinel.dashboard.view.Result; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +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.RestController; + +/** + * @author Eric Zhao + * @since 1.4.1 + */ +@RestController +@RequestMapping("/cluster/assign") +public class ClusterAssignController { + + private final Logger logger = LoggerFactory.getLogger(ClusterAssignController.class); + + @Autowired + private ClusterAssignService clusterAssignService; + + @PostMapping("/all_server/{app}") + public Result apiAssignAllClusterServersOfApp(@PathVariable String app, + @RequestBody + ClusterAppFullAssignRequest assignRequest) { + if (StringUtil.isEmpty(app)) { + return Result.ofFail(-1, "app cannot be null or empty"); + } + if (assignRequest == null || assignRequest.getClusterMap() == null + || assignRequest.getRemainingList() == null) { + return Result.ofFail(-1, "bad request body"); + } + try { + return Result.ofSuccess(clusterAssignService.applyAssignToApp(app, assignRequest.getClusterMap(), + assignRequest.getRemainingList())); + } catch (Throwable throwable) { + logger.error("Error when assigning full cluster servers for app: " + app, throwable); + return Result.ofFail(-1, throwable.getMessage()); + } + } + + @PostMapping("/single_server/{app}") + public Result apiAssignSingleClusterServersOfApp(@PathVariable String app, + @RequestBody ClusterAppSingleServerAssignRequest assignRequest) { + if (StringUtil.isEmpty(app)) { + return Result.ofFail(-1, "app cannot be null or empty"); + } + if (assignRequest == null || assignRequest.getClusterMap() == null) { + return Result.ofFail(-1, "bad request body"); + } + try { + return Result.ofSuccess(clusterAssignService.applyAssignToApp(app, Collections.singletonList(assignRequest.getClusterMap()), + assignRequest.getRemainingList())); + } catch (Throwable throwable) { + logger.error("Error when assigning single cluster servers for app: " + app, throwable); + return Result.ofFail(-1, throwable.getMessage()); + } + } + + @PostMapping("/unbind_server/{app}") + public Result apiUnbindClusterServersOfApp(@PathVariable String app, + @RequestBody Set machineIds) { + if (StringUtil.isEmpty(app)) { + return Result.ofFail(-1, "app cannot be null or empty"); + } + if (machineIds == null || machineIds.isEmpty()) { + return Result.ofFail(-1, "bad request body"); + } + try { + return Result.ofSuccess(clusterAssignService.unbindClusterServers(app, machineIds)); + } catch (Throwable throwable) { + logger.error("Error when unbinding cluster server {} for app <{}>", machineIds, app, throwable); + return Result.ofFail(-1, throwable.getMessage()); + } + } +} 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/cluster/ClusterConfigController.java similarity index 64% rename from sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/ClusterConfigController.java rename to sentinel-dashboard/src/main/java/com/taobao/csp/sentinel/dashboard/view/cluster/ClusterConfigController.java index b4f1d86d..ee234e69 100644 --- 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/cluster/ClusterConfigController.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.taobao.csp.sentinel.dashboard.view; +package com.taobao.csp.sentinel.dashboard.view.cluster; +import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; @@ -26,16 +27,22 @@ 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.domain.cluster.request.ClusterClientModifyRequest; +import com.taobao.csp.sentinel.dashboard.domain.cluster.request.ClusterModifyRequest; +import com.taobao.csp.sentinel.dashboard.domain.cluster.request.ClusterServerModifyRequest; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.AppClusterClientStateWrapVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.AppClusterServerStateWrapVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStatePairVO; +import com.taobao.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalStateVO; import com.taobao.csp.sentinel.dashboard.service.ClusterConfigService; +import com.taobao.csp.sentinel.dashboard.util.ClusterEntityUtils; import com.taobao.csp.sentinel.dashboard.util.VersionUtils; +import com.taobao.csp.sentinel.dashboard.view.Result; 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.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -60,7 +67,7 @@ public class ClusterConfigController { @Autowired private ClusterConfigService clusterConfigService; - @PostMapping("/config/modify") + @PostMapping("/config/modify_single") public Result apiModifyClusterConfig(@RequestBody String payload) { if (StringUtil.isBlank(payload)) { return Result.ofFail(-1, "empty request body"); @@ -94,18 +101,22 @@ public class ClusterConfigController { 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()); - } + return errorResponse(ex); } catch (Throwable ex) { logger.error("Error when modifying cluster config", ex); return Result.ofFail(-1, ex.getMessage()); } } - @GetMapping("/state") + private Result errorResponse(ExecutionException ex) { + if (isNotSupported(ex.getCause())) { + return unsupportedVersion(); + } else { + return Result.ofThrowable(-1, ex.getCause()); + } + } + + @GetMapping("/state_single") public Result apiGetClusterState(@RequestParam String app, @RequestParam String ip, @RequestParam Integer port) { @@ -127,17 +138,69 @@ public class ClusterConfigController { .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()); - } + return errorResponse(ex); } catch (Throwable throwable) { logger.error("Error when fetching cluster state", throwable); return Result.ofFail(-1, throwable.getMessage()); } } + @GetMapping("/server_state/{app}") + public Result> apiGetClusterServerStateOfApp(@PathVariable String app) { + if (StringUtil.isEmpty(app)) { + return Result.ofFail(-1, "app cannot be null or empty"); + } + try { + return clusterConfigService.getClusterUniversalState(app) + .thenApply(ClusterEntityUtils::wrapToAppClusterServerState) + .thenApply(Result::ofSuccess) + .get(); + } catch (ExecutionException ex) { + logger.error("Error when fetching cluster server state of app: " + app, ex.getCause()); + return errorResponse(ex); + } catch (Throwable throwable) { + logger.error("Error when fetching cluster server state of app: " + app, throwable); + return Result.ofFail(-1, throwable.getMessage()); + } + } + + @GetMapping("/client_state/{app}") + public Result> apiGetClusterClientStateOfApp(@PathVariable String app) { + if (StringUtil.isEmpty(app)) { + return Result.ofFail(-1, "app cannot be null or empty"); + } + try { + return clusterConfigService.getClusterUniversalState(app) + .thenApply(ClusterEntityUtils::wrapToAppClusterClientState) + .thenApply(Result::ofSuccess) + .get(); + } catch (ExecutionException ex) { + logger.error("Error when fetching cluster token client state of app: " + app, ex.getCause()); + return errorResponse(ex); + } catch (Throwable throwable) { + logger.error("Error when fetching cluster token client state of app: " + app, throwable); + return Result.ofFail(-1, throwable.getMessage()); + } + } + + @GetMapping("/state/{app}") + public Result> apiGetClusterStateOfApp(@PathVariable String app) { + if (StringUtil.isEmpty(app)) { + return Result.ofFail(-1, "app cannot be null or empty"); + } + try { + return clusterConfigService.getClusterUniversalState(app) + .thenApply(Result::ofSuccess) + .get(); + } catch (ExecutionException ex) { + logger.error("Error when fetching cluster state of app: " + app, ex.getCause()); + return errorResponse(ex); + } catch (Throwable throwable) { + logger.error("Error when fetching cluster state of app: " + app, throwable); + return Result.ofFail(-1, throwable.getMessage()); + } + } + private boolean isNotSupported(Throwable ex) { return ex instanceof CommandNotFoundException; } 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 index d17d2475..312d63fd 100644 --- 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 @@ -25,6 +25,7 @@ public final class NacosConfigUtil { public static final String FLOW_DATA_ID_POSTFIX = "-flow-rules"; public static final String PARAM_FLOW_DATA_ID_POSTFIX = "-param-rules"; + public static final String CLUSTER_MAP_DATA_ID_POSTFIX = "-cluster-map"; /** * cc for `cluster-client` diff --git a/sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java b/sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java new file mode 100644 index 00000000..cf0af2e9 --- /dev/null +++ b/sentinel-demo/sentinel-demo-cluster/sentinel-demo-cluster-embedded/src/main/java/com/alibaba/csp/sentinel/demo/cluster/ClusterClientDemo.java @@ -0,0 +1,62 @@ +/* + * 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.alibaba.csp.sentinel.demo.cluster; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.alibaba.csp.sentinel.init.InitExecutor; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + *

Run this demo with the following args: -Dproject.name=appA

+ *

You need a token server running already.

+ * + * @author Eric Zhao + */ +public class ClusterClientDemo { + + public static void main(String[] args) { + InitExecutor.doInit(); + + // Manually schedule the cluster mode to client. + // In common, we need a scheduling system to modify the cluster mode automatically. + // Command HTTP API: http://:/setClusterMode?mode= + ClusterStateManager.setToClient(); + + String resourceName = "cluster-demo-entry"; + + // Assume we have a cluster flow rule for `demo-resource`: QPS = 5 in AVG_LOCAL mode. + for (int i = 0; i < 10; i++) { + tryEntry(resourceName); + } + } + + private static void tryEntry(String res) { + Entry entry = null; + try { + entry = SphU.entry(res, EntryType.IN, 1, "abc", "def"); + System.out.println("Passed"); + } catch (BlockException ex) { + ex.printStackTrace(); + } finally { + if (entry != null) { + entry.exit(); + } + } + } +}