- Add cluster token server management controller and service for app - Other enhancements and fixes Signed-off-by: Eric Zhao <sczyh16@gmail.com>master
@@ -56,12 +56,6 @@ | |||
<artifactId>spring-boot-starter-logging</artifactId> | |||
<version>${spring.boot.version}</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-devtools</artifactId> | |||
<version>${spring.boot.version}</version> | |||
<optional>true</optional> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-test</artifactId> | |||
@@ -105,11 +99,17 @@ | |||
<groupId>com.alibaba</groupId> | |||
<artifactId>fastjson</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>junit</groupId> | |||
<artifactId>junit</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.mockito</groupId> | |||
<artifactId>mockito-core</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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<String> executeCommand(String command, URI uri) { | |||
CompletableFuture<String> 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<HttpResponse>() { | |||
@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<String> reference = new AtomicReference<>(); | |||
httpClient.execute(httpGet, new FutureCallback<HttpResponse>() { | |||
@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<NodeVo> 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<String> executeCommand(String command, URI uri) { | |||
CompletableFuture<String> 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<HttpResponse>() { | |||
@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<String> reference = new AtomicReference<>(); | |||
httpClient.execute(httpGet, new FutureCallback<HttpResponse>() { | |||
@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 <R> CompletableFuture<R> newFailedFuture(Throwable ex) { | |||
CompletableFuture<R> future = new CompletableFuture<>(); | |||
future.completeExceptionally(ex); | |||
return future; | |||
} | |||
// Cluster related | |||
public CompletableFuture<ClusterStateSimpleEntity> 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<ClusterClientConfig> fetchClusterClientConfig(String app, String ip, int port) { | |||
public CompletableFuture<ClusterClientInfoVO> 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); | |||
@@ -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<String> getAppNames(); | |||
@@ -41,6 +41,10 @@ public class MachineInfo implements Comparable<MachineInfo> { | |||
return machineInfo; | |||
} | |||
public String toHostPort() { | |||
return ip + ":" + port; | |||
} | |||
public Integer getPort() { | |||
return port; | |||
} | |||
@@ -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<String> failedServerSet; | |||
private Set<String> failedClientSet; | |||
private Integer totalCount; | |||
public Set<String> getFailedServerSet() { | |||
return failedServerSet; | |||
} | |||
public ClusterAppAssignResultVO setFailedServerSet(Set<String> failedServerSet) { | |||
this.failedServerSet = failedServerSet; | |||
return this; | |||
} | |||
public Set<String> getFailedClientSet() { | |||
return failedClientSet; | |||
} | |||
public ClusterAppAssignResultVO setFailedClientSet(Set<String> 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 + | |||
'}'; | |||
} | |||
} |
@@ -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<ClusterAppAssignMap> clusterMap; | |||
private Set<String> remainingList; | |||
public List<ClusterAppAssignMap> getClusterMap() { | |||
return clusterMap; | |||
} | |||
public ClusterAppFullAssignRequest setClusterMap( | |||
List<ClusterAppAssignMap> clusterMap) { | |||
this.clusterMap = clusterMap; | |||
return this; | |||
} | |||
public Set<String> getRemainingList() { | |||
return remainingList; | |||
} | |||
public ClusterAppFullAssignRequest setRemainingList(Set<String> remainingList) { | |||
this.remainingList = remainingList; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterAppFullAssignRequest{" + | |||
"clusterMap=" + clusterMap + | |||
", remainingList=" + remainingList + | |||
'}'; | |||
} | |||
} |
@@ -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<String> remainingList; | |||
public ClusterAppAssignMap getClusterMap() { | |||
return clusterMap; | |||
} | |||
public ClusterAppSingleServerAssignRequest setClusterMap(ClusterAppAssignMap clusterMap) { | |||
this.clusterMap = clusterMap; | |||
return this; | |||
} | |||
public Set<String> getRemainingList() { | |||
return remainingList; | |||
} | |||
public ClusterAppSingleServerAssignRequest setRemainingList(Set<String> remainingList) { | |||
this.remainingList = remainingList; | |||
return this; | |||
} | |||
@Override | |||
public String toString() { | |||
return "ClusterAppSingleServerAssignRequest{" + | |||
"clusterMap=" + clusterMap + | |||
", remainingList=" + remainingList + | |||
'}'; | |||
} | |||
} |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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<String> 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<String> getClientSet() { | |||
return clientSet; | |||
} | |||
public ClusterGroupEntity setClientSet(Set<String> 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 + | |||
'}'; | |||
} | |||
} |
@@ -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 + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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; | |||
@@ -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<String> clientSet; | |||
private Set<String> 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<String> getClientSet() { | |||
return clientSet; | |||
} | |||
public ClusterAppAssignMap setClientSet(Set<String> clientSet) { | |||
this.clientSet = clientSet; | |||
return this; | |||
} | |||
public Set<String> getNamespaceSet() { | |||
return namespaceSet; | |||
} | |||
public ClusterAppAssignMap setNamespaceSet(Set<String> 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 + | |||
'}'; | |||
} | |||
} |
@@ -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; | |||
@@ -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 |
@@ -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; | |||
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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<String> namespaceSet; | |||
private Integer port; | |||
private List<ConnectionGroupVO> connection; | |||
private List<ClusterRequestLimitVO> 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<ConnectionGroupVO> connection) { | |||
public ClusterServerStateVO setConnection(List<ConnectionGroupVO> connection) { | |||
this.connection = connection; | |||
return this; | |||
} | |||
public List<ClusterRequestLimitVO> getRequestLimitData() { | |||
return requestLimitData; | |||
} | |||
public ClusterServerStateVO setRequestLimitData(List<ClusterRequestLimitVO> 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 + | |||
'}'; | |||
} | |||
} |
@@ -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 |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -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 |
@@ -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); | |||
@@ -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<FlowRuleEnt | |||
} | |||
List<MachineInfo> list = appManagement.getDetailApp(appName).getMachines() | |||
.stream() | |||
.filter(MachineUtil::isMachineHealth) | |||
.filter(MachineUtils::isMachineHealth) | |||
.sorted((e1, e2) -> { | |||
if (e1.getTimestamp().before(e2.getTimestamp())) { | |||
return 1; | |||
@@ -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<List<FlowRuleE | |||
Set<MachineInfo> set = appManagement.getDetailApp(app).getMachines(); | |||
for (MachineInfo machine : set) { | |||
if (!MachineUtil.isMachineHealth(machine)) { | |||
if (!MachineUtils.isMachineHealth(machine)) { | |||
continue; | |||
} | |||
// TODO: parse the results | |||
@@ -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<String> 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<ClusterAppAssignMap> clusterMap, | |||
Set<String> remainingSet); | |||
} |
@@ -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<String> failedSet = new HashSet<>(); | |||
try { | |||
ClusterGroupEntity entity = clusterConfigService.getClusterUniversalStateForAppMachine(app, machineId) | |||
.get(10, TimeUnit.SECONDS); | |||
Set<String> 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<Void> 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<String> 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<ClusterAppAssignMap> clusterMap, | |||
Set<String> remainingSet) { | |||
AssertUtil.assertNotBlank(app, "app cannot be blank"); | |||
AssertUtil.notNull(clusterMap, "clusterMap cannot be null"); | |||
Set<String> failedServerSet = new HashSet<>(); | |||
Set<String> 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<Void> 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<String> remainingSet, Set<String> 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<Void> 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<String> failedSet) { | |||
Set<String> 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<Void> 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<String, CompletableFuture<Void>> t, Set<String> 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<Void> 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<Void> 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<Void> applyServerNamespaceSetConfig(String app, String ip, int commandPort, | |||
ClusterAppAssignMap assignMap) { | |||
Set<String> namespaceSet = assignMap.getNamespaceSet(); | |||
if (namespaceSet == null || namespaceSet.isEmpty()) { | |||
return CompletableFuture.completedFuture(null); | |||
} | |||
return sentinelApiClient.modifyClusterServerNamespaceSet(app, ip, commandPort, namespaceSet); | |||
} | |||
private CompletableFuture<Void> 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); | |||
} | |||
} |
@@ -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<Void> 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<List<ClusterUniversalStatePairVO>> 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<CompletableFuture<ClusterUniversalStatePairVO>> 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<ClusterGroupEntity> 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<ClusterUniversalStateVO> getClusterUniversalState(String app, String ip, int port) { | |||
return sentinelApiClient.fetchClusterMode(app, ip, port) | |||
.thenApply(e -> new ClusterUniversalStateVO().setStateInfo(e)) | |||
.thenCompose(vo -> { | |||
if (vo.getStateInfo().getClientAvailable()) { | |||
return sentinelApiClient.fetchClusterClientConfig(app, ip, port) | |||
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; | |||
} | |||
} |
@@ -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 <R> CompletableFuture<R> newFailedFuture(Throwable ex) { | |||
CompletableFuture<R> future = new CompletableFuture<>(); | |||
future.completeExceptionally(ex); | |||
return future; | |||
} | |||
public static <R> CompletableFuture<List<R>> sequenceFuture(List<CompletableFuture<R>> futures) { | |||
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) | |||
.thenApply(v -> futures.stream() | |||
.map(AsyncUtils::getValue) | |||
.filter(Objects::nonNull) | |||
.collect(Collectors.toList()) | |||
); | |||
} | |||
public static <R> CompletableFuture<List<R>> sequenceSuccessFuture(List<CompletableFuture<R>> futures) { | |||
return CompletableFuture.supplyAsync(() -> futures.parallelStream() | |||
.map(AsyncUtils::getValue) | |||
.filter(Objects::nonNull) | |||
.collect(Collectors.toList()) | |||
); | |||
} | |||
public static <T> T getValue(CompletableFuture<T> 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() {} | |||
} |
@@ -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<AppClusterServerStateWrapVO> wrapToAppClusterServerState( | |||
List<ClusterUniversalStatePairVO> list) { | |||
if (list == null || list.isEmpty()) { | |||
return new ArrayList<>(); | |||
} | |||
Map<String, AppClusterServerStateWrapVO> 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<AppClusterClientStateWrapVO> wrapToAppClusterClientState( | |||
List<ClusterUniversalStatePairVO> list) { | |||
if (list == null || list.isEmpty()) { | |||
return new ArrayList<>(); | |||
} | |||
Map<String, AppClusterClientStateWrapVO> 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<ClusterGroupEntity> wrapToClusterGroup(List<ClusterUniversalStatePairVO> list) { | |||
if (list == null || list.isEmpty()) { | |||
return new ArrayList<>(); | |||
} | |||
Map<String, ClusterGroupEntity> 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() {} | |||
} |
@@ -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<Integer> 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<Tuple2<String, Integer>> 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(); | |||
} | |||
} |
@@ -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<Long> delete(Long id) { | |||
if (id == null) { | |||
return Result.ofFail(-1, "id can't be null"); | |||
} | |||
@@ -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()); | |||
@@ -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<ClusterAppAssignResultVO> 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<ClusterAppAssignResultVO> 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<ClusterAppAssignResultVO> apiUnbindClusterServersOfApp(@PathVariable String app, | |||
@RequestBody Set<String> 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()); | |||
} | |||
} | |||
} |
@@ -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<Boolean> 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 <T> Result<T> errorResponse(ExecutionException ex) { | |||
if (isNotSupported(ex.getCause())) { | |||
return unsupportedVersion(); | |||
} else { | |||
return Result.ofThrowable(-1, ex.getCause()); | |||
} | |||
} | |||
@GetMapping("/state_single") | |||
public Result<ClusterUniversalStateVO> 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<List<AppClusterServerStateWrapVO>> 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<List<AppClusterClientStateWrapVO>> 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<List<ClusterUniversalStatePairVO>> 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; | |||
} |
@@ -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` | |||
@@ -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; | |||
/** | |||
* <p>Run this demo with the following args: -Dproject.name=appA</p> | |||
* <p>You need a token server running already.</p> | |||
* | |||
* @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://<ip>:<port>/setClusterMode?mode=<xxx> | |||
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(); | |||
} | |||
} | |||
} | |||
} |