dashboard: support automatic/manual removal of unhealthy machines and disconnected applications (#168)
* Support removing unhealthy machine manually * Auto removal support for dead machine * Auto hidden for app if one have no healthy machine after some time(configurable) * Auto removal for app if hidden status lasts(configurable).
This commit is contained in:
parent
61fede3827
commit
737747a412
|
|
@ -47,7 +47,35 @@ Sentinel 提供了多种规则来保护系统的不同部分。流量控制规
|
||||||
本控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如**持久化的后端数据库、可靠的配置中心**等。
|
本控制台只是用于演示 Sentinel 的基本能力和工作流程,并没有依赖生产环境中所必需的组件,比如**持久化的后端数据库、可靠的配置中心**等。
|
||||||
目前 Sentinel 采用内存态的方式存储监控和规则数据,监控最长存储时间为 5 分钟,控制台重启后数据丢失。
|
目前 Sentinel 采用内存态的方式存储监控和规则数据,监控最长存储时间为 5 分钟,控制台重启后数据丢失。
|
||||||
|
|
||||||
|
## 3. 配置项
|
||||||
|
|
||||||
|
控制台的一些特性可以通过配置项来进行配置,配置项主要有两个来源:`System.getProperty()`和`System.getenv()`,同时存在时后者可以覆盖前者。
|
||||||
|
> 环境变量因为不支持`.`所以需要将其更换为`_`。
|
||||||
|
|
||||||
|
项 | 类型 | 默认值 | 最小值 | 描述
|
||||||
|
--- | --- | --- | --- | ---
|
||||||
|
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 | 是否隐藏无健康节点的应用,距离最近一次主机心跳时间的毫秒数,默认关闭
|
||||||
|
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 | 是否自动删除无健康节点的应用,距离最近一次其下节点的心跳时间毫秒数,默认关闭
|
||||||
|
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 | 主机失联判定,不可关闭
|
||||||
|
sentinel.dashboard.autoRemoveMachineMillis | Integer | 0 | 300000 | 距离最近心跳时间超过指定时间是否自动删除失联节点,默认关闭
|
||||||
|
|
||||||
|
配置示例:
|
||||||
|
|
||||||
|
命令行
|
||||||
|
```shell
|
||||||
|
java -Dsentinel.dashboard.app.hideAppNoMachineMillis=60000
|
||||||
|
```
|
||||||
|
java
|
||||||
|
```java
|
||||||
|
System.setProperty("sentinel.dashboard.app.hideAppNoMachineMillis", "60000");
|
||||||
|
```
|
||||||
|
环境变量
|
||||||
|
```shell
|
||||||
|
sentinel_dashboard_app_hideAppNoMachineMillis=60000
|
||||||
|
```
|
||||||
|
|
||||||
更多:
|
更多:
|
||||||
|
|
||||||
- [Sentinel 控制台启动和客户端接入](./README.md)
|
- [Sentinel 控制台启动和客户端接入](./README.md)
|
||||||
- [控制台 Wiki](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0)
|
- [控制台 Wiki](https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.csp</groupId>
|
<groupId>com.alibaba.csp</groupId>
|
||||||
<artifactId>sentinel-core</artifactId>
|
<artifactId>sentinel-core</artifactId>
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.csp</groupId>
|
<groupId>com.alibaba.csp</groupId>
|
||||||
|
|
@ -33,7 +32,6 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.csp</groupId>
|
<groupId>com.alibaba.csp</groupId>
|
||||||
<artifactId>sentinel-transport-simple-http</artifactId>
|
<artifactId>sentinel-transport-simple-http</artifactId>
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba.csp</groupId>
|
<groupId>com.alibaba.csp</groupId>
|
||||||
|
|
@ -118,7 +116,12 @@
|
||||||
<version>${apollo.openapi.version}</version>
|
<version>${apollo.openapi.version}</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.stefanbirkner</groupId>
|
||||||
|
<artifactId>system-rules</artifactId>
|
||||||
|
<version>1.16.1</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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.dashboard.config;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.lang.math.NumberUtils;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dashboard config support
|
||||||
|
* <p>
|
||||||
|
* Dashboard supports configuration loading by several ways by order:<br>
|
||||||
|
* 1. System.properties<br>
|
||||||
|
* 2. Env
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
* @since 1.5.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DashboardConfig {
|
||||||
|
/**
|
||||||
|
* hide app in sidebar when it had no healthy machine after specific period in millis
|
||||||
|
*/
|
||||||
|
public static final String CONFIG_HIDE_APP_NO_MACHINE_MILLIS = "sentinel.dashboard.app.hideAppNoMachineMillis";
|
||||||
|
/**
|
||||||
|
* remove app when it had no healthy machine after specific period in millis
|
||||||
|
*/
|
||||||
|
public static final String CONFIG_REMOVE_APP_NO_MACHINE_MILLIS = "sentinel.dashboard.removeAppNoMachineMillis";
|
||||||
|
/**
|
||||||
|
* unhealthy millis
|
||||||
|
*/
|
||||||
|
public static final String CONFIG_UNHEALTHY_MACHINE_MILLIS = "sentinel.dashboard.unhealthyMachineMillis";
|
||||||
|
/**
|
||||||
|
* auto remove unhealthy machine after specific period in millis
|
||||||
|
*/
|
||||||
|
public static final String CONFIG_AUTO_REMOVE_MACHINE_MILLIS = "sentinel.dashboard.autoRemoveMachineMillis";
|
||||||
|
private static final ConcurrentMap<String, Object> cacheMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String getConfig(String name) {
|
||||||
|
// env
|
||||||
|
String val = System.getenv(name);
|
||||||
|
if (StringUtils.isNotEmpty(val)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
// properties
|
||||||
|
val = System.getProperty(name);
|
||||||
|
if (StringUtils.isNotEmpty(val)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int getConfigInt(String name, int defaultVal, int minVal) {
|
||||||
|
if (cacheMap.containsKey(name)) {
|
||||||
|
return (int)cacheMap.get(name);
|
||||||
|
}
|
||||||
|
int val = NumberUtils.toInt(getConfig(name));
|
||||||
|
if (val == 0) {
|
||||||
|
val = defaultVal;
|
||||||
|
} else if (val < minVal) {
|
||||||
|
val = minVal;
|
||||||
|
}
|
||||||
|
cacheMap.put(name, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getHideAppNoMachineMillis() {
|
||||||
|
return getConfigInt(CONFIG_HIDE_APP_NO_MACHINE_MILLIS, 0, 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getRemoveAppNoMachineMillis() {
|
||||||
|
return getConfigInt(CONFIG_REMOVE_APP_NO_MACHINE_MILLIS, 0, 120000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getAutoRemoveMachineMillis() {
|
||||||
|
return getConfigInt(CONFIG_AUTO_REMOVE_MACHINE_MILLIS, 0, 300000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getUnhealthyMachineMillis() {
|
||||||
|
return getConfigInt(CONFIG_UNHEALTHY_MACHINE_MILLIS, 60000, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearCache() {
|
||||||
|
cacheMap.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,7 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,4 +80,21 @@ public class AppController {
|
||||||
});
|
});
|
||||||
return Result.ofSuccess(MachineInfoVo.fromMachineInfoList(list));
|
return Result.ofSuccess(MachineInfoVo.fromMachineInfoList(list));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResponseBody
|
||||||
|
@RequestMapping(value = "/{app}/machine/remove.json")
|
||||||
|
Result<String> removeMachineById(
|
||||||
|
@PathVariable("app") String app,
|
||||||
|
@RequestParam(name = "ip") String ip,
|
||||||
|
@RequestParam(name = "port") int port) {
|
||||||
|
AppInfo appInfo = appManagement.getDetailApp(app);
|
||||||
|
if (appInfo == null) {
|
||||||
|
return Result.ofSuccess(null);
|
||||||
|
}
|
||||||
|
if (appManagement.removeMachine(app, ip, port)) {
|
||||||
|
return Result.ofSuccessMsg("success");
|
||||||
|
} else {
|
||||||
|
return Result.ofFail(1, "remove failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.dashboard.controller;
|
package com.alibaba.csp.sentinel.dashboard.controller;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
|
|
||||||
|
|
@ -57,14 +55,15 @@ public class MachineRegistryController {
|
||||||
return Result.ofFail(-1, "your port not set yet");
|
return Result.ofFail(-1, "your port not set yet");
|
||||||
}
|
}
|
||||||
String sentinelVersion = StringUtil.isEmpty(v) ? "unknown" : v;
|
String sentinelVersion = StringUtil.isEmpty(v) ? "unknown" : v;
|
||||||
long timestamp = version == null ? System.currentTimeMillis() : version;
|
version = version == null ? System.currentTimeMillis() : version;
|
||||||
try {
|
try {
|
||||||
MachineInfo machineInfo = new MachineInfo();
|
MachineInfo machineInfo = new MachineInfo();
|
||||||
machineInfo.setApp(app);
|
machineInfo.setApp(app);
|
||||||
machineInfo.setHostname(hostname);
|
machineInfo.setHostname(hostname);
|
||||||
machineInfo.setIp(ip);
|
machineInfo.setIp(ip);
|
||||||
machineInfo.setPort(port);
|
machineInfo.setPort(port);
|
||||||
machineInfo.setTimestamp(new Date(timestamp));
|
machineInfo.setHeartbeatVersion(version);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis());
|
||||||
machineInfo.setVersion(sentinelVersion);
|
machineInfo.setVersion(sentinelVersion);
|
||||||
appManagement.addMachine(machineInfo);
|
appManagement.addMachine(machineInfo);
|
||||||
return Result.ofSuccessMsg("success");
|
return Result.ofSuccessMsg("success");
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,8 @@ public class MachineEntity {
|
||||||
machineInfo.setHostname(hostname);
|
machineInfo.setHostname(hostname);
|
||||||
machineInfo.setIp(ip);
|
machineInfo.setIp(ip);
|
||||||
machineInfo.setPort(port);
|
machineInfo.setPort(port);
|
||||||
machineInfo.setTimestamp(timestamp);
|
machineInfo.setLastHeatbeat(timestamp.getTime());
|
||||||
|
machineInfo.setHeartbeatVersion(timestamp.getTime());
|
||||||
|
|
||||||
return machineInfo;
|
return machineInfo;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,29 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.dashboard.discovery;
|
package com.alibaba.csp.sentinel.dashboard.discovery;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig;
|
||||||
|
|
||||||
public class AppInfo {
|
public class AppInfo {
|
||||||
|
private static final Comparator<MachineInfo> COMPARATOR_BY_MACHINE_HEARTBEAT_DESC = new Comparator<MachineInfo>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(MachineInfo o1, MachineInfo o2) {
|
||||||
|
if (o1.getLastHeatbeat() < o2.getLastHeatbeat()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (o1.getLastHeatbeat() > o2.getLastHeatbeat()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private String app = "";
|
private String app = "";
|
||||||
|
|
||||||
|
|
@ -60,9 +77,57 @@ public class AppInfo {
|
||||||
return machines.add(machineInfo);
|
return machines.add(machineInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized boolean removeMachine(String ip, int port) {
|
||||||
|
Iterator<MachineInfo> it = machines.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
MachineInfo machine = it.next();
|
||||||
|
if (machine.getIp().equals(ip) && machine.getPort() == port) {
|
||||||
|
it.remove();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<MachineInfo> getMachine(String ip, int port) {
|
public Optional<MachineInfo> getMachine(String ip, int port) {
|
||||||
return machines.stream()
|
return machines.stream()
|
||||||
.filter(e -> e.getIp().equals(ip) && e.getPort().equals(port))
|
.filter(e -> e.getIp().equals(ip) && e.getPort().equals(port))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean heartbeatJudge(int threshold) {
|
||||||
|
if (machines.size() == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (threshold > 0) {
|
||||||
|
long healthyCount = machines.stream()
|
||||||
|
.filter(m -> m.isHealthy())
|
||||||
|
.count();
|
||||||
|
if (healthyCount == 0) {
|
||||||
|
// no machine
|
||||||
|
long recentHeartBeat = machines.stream()
|
||||||
|
.max(COMPARATOR_BY_MACHINE_HEARTBEAT_DESC).get().getLastHeatbeat();
|
||||||
|
return System.currentTimeMillis() - recentHeartBeat < threshold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* having no healthy machine and should not be displayed
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isShown() {
|
||||||
|
return heartbeatJudge(DashboardConfig.getHideAppNoMachineMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* having no healthy machine and should be removed
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isDead() {
|
||||||
|
return !heartbeatJudge(DashboardConfig.getRemoveAppNoMachineMillis());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,11 @@ public class AppManagement implements MachineDiscovery {
|
||||||
return machineDiscovery.addMachine(machineInfo);
|
return machineDiscovery.addMachine(machineInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeMachine(String app, String ip, int port) {
|
||||||
|
return machineDiscovery.removeMachine(app, ip, port);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getAppNames() {
|
public List<String> getAppNames() {
|
||||||
return machineDiscovery.getAppNames();
|
return machineDiscovery.getAppNames();
|
||||||
|
|
@ -63,4 +68,9 @@ public class AppManagement implements MachineDiscovery {
|
||||||
return machineDiscovery.getDetailApp(app);
|
return machineDiscovery.getDetailApp(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeApp(String app) {
|
||||||
|
machineDiscovery.removeApp(app);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import java.util.Set;
|
||||||
|
|
||||||
public interface MachineDiscovery {
|
public interface MachineDiscovery {
|
||||||
|
|
||||||
long MAX_CLIENT_LIVE_TIME_MS = 1000 * 60 * 5;
|
|
||||||
String UNKNOWN_APP_NAME = "CLUSTER_NOT_STARTED";
|
String UNKNOWN_APP_NAME = "CLUSTER_NOT_STARTED";
|
||||||
|
|
||||||
List<String> getAppNames();
|
List<String> getAppNames();
|
||||||
|
|
@ -29,5 +28,9 @@ public interface MachineDiscovery {
|
||||||
|
|
||||||
AppInfo getDetailApp(String app);
|
AppInfo getDetailApp(String app);
|
||||||
|
|
||||||
|
void removeApp(String app);
|
||||||
|
|
||||||
long addMachine(MachineInfo machineInfo);
|
long addMachine(MachineInfo machineInfo);
|
||||||
|
|
||||||
|
boolean removeMachine(String app, String ip, int port);
|
||||||
}
|
}
|
||||||
|
|
@ -15,9 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package com.alibaba.csp.sentinel.dashboard.discovery;
|
package com.alibaba.csp.sentinel.dashboard.discovery;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig;
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
|
|
||||||
public class MachineInfo implements Comparable<MachineInfo> {
|
public class MachineInfo implements Comparable<MachineInfo> {
|
||||||
|
|
@ -26,7 +26,8 @@ public class MachineInfo implements Comparable<MachineInfo> {
|
||||||
private String hostname = "";
|
private String hostname = "";
|
||||||
private String ip = "";
|
private String ip = "";
|
||||||
private Integer port = -1;
|
private Integer port = -1;
|
||||||
private Date timestamp;
|
private long lastHeatbeat;
|
||||||
|
private long heartbeatVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the version of Sentinel client (since 0.2.0).
|
* Indicates the version of Sentinel client (since 0.2.0).
|
||||||
|
|
@ -77,12 +78,12 @@ public class MachineInfo implements Comparable<MachineInfo> {
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getTimestamp() {
|
public long getHeartbeatVersion() {
|
||||||
return timestamp;
|
return heartbeatVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTimestamp(Date timestamp) {
|
public void setHeartbeatVersion(long heartbeatVersion) {
|
||||||
this.timestamp = timestamp;
|
this.heartbeatVersion = heartbeatVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
|
|
@ -94,6 +95,32 @@ public class MachineInfo implements Comparable<MachineInfo> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isHealthy() {
|
||||||
|
long delta = System.currentTimeMillis() - lastHeatbeat;
|
||||||
|
return delta < DashboardConfig.getUnhealthyMachineMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* whether dead should be removed
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isDead() {
|
||||||
|
if (DashboardConfig.getAutoRemoveMachineMillis() > 0) {
|
||||||
|
long delta = System.currentTimeMillis() - lastHeatbeat;
|
||||||
|
return delta > DashboardConfig.getAutoRemoveMachineMillis();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastHeatbeat() {
|
||||||
|
return lastHeatbeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastHeatbeat(long lastHeatbeat) {
|
||||||
|
this.lastHeatbeat = lastHeatbeat;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(MachineInfo o) {
|
public int compareTo(MachineInfo o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
|
|
@ -110,14 +137,16 @@ public class MachineInfo implements Comparable<MachineInfo> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MachineInfo{" +
|
return new StringBuilder("MachineInfo {")
|
||||||
"app='" + app + '\'' +
|
.append("app='").append(app).append('\'')
|
||||||
", hostname='" + hostname + '\'' +
|
.append(", hostname='").append(hostname).append('\'')
|
||||||
", ip='" + ip + '\'' +
|
.append(", ip='").append(ip).append('\'')
|
||||||
", port=" + port +
|
.append(", port=").append(port)
|
||||||
", timestamp=" + timestamp +
|
.append(", heartbeatVersion=").append(heartbeatVersion)
|
||||||
", version='" + version + '\'' +
|
.append(", lastHeartbeat=").append(lastHeatbeat)
|
||||||
'}';
|
.append(", version='").append(version).append('\'')
|
||||||
|
.append(", healthy=").append(isHealthy())
|
||||||
|
.append('}').toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,15 @@ public class SimpleMachineDiscovery implements MachineDiscovery {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeMachine(String app, String ip, int port) {
|
||||||
|
AppInfo appInfo = apps.get(app);
|
||||||
|
if (appInfo != null) {
|
||||||
|
return appInfo.removeMachine(ip, port);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getAppNames() {
|
public List<String> getAppNames() {
|
||||||
return new ArrayList<>(apps.keySet());
|
return new ArrayList<>(apps.keySet());
|
||||||
|
|
@ -52,4 +61,9 @@ public class SimpleMachineDiscovery implements MachineDiscovery {
|
||||||
return new HashSet<>(apps.values());
|
return new HashSet<>(apps.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeApp(String app) {
|
||||||
|
apps.remove(app);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,8 @@
|
||||||
package com.alibaba.csp.sentinel.dashboard.domain.vo;
|
package com.alibaba.csp.sentinel.dashboard.domain.vo;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.MachineDiscovery;
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,9 +28,10 @@ public class MachineInfoVo {
|
||||||
private String app;
|
private String app;
|
||||||
private String hostname;
|
private String hostname;
|
||||||
private String ip;
|
private String ip;
|
||||||
private Integer port;
|
private int port;
|
||||||
private Date timestamp;
|
private long heartbeatVersion;
|
||||||
private boolean health;
|
private long lastHeartbeat;
|
||||||
|
private boolean healthy;
|
||||||
|
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
|
|
@ -50,11 +49,10 @@ public class MachineInfoVo {
|
||||||
vo.setHostname(machine.getHostname());
|
vo.setHostname(machine.getHostname());
|
||||||
vo.setIp(machine.getIp());
|
vo.setIp(machine.getIp());
|
||||||
vo.setPort(machine.getPort());
|
vo.setPort(machine.getPort());
|
||||||
vo.setTimestamp(machine.getTimestamp());
|
vo.setLastHeartbeat(machine.getLastHeatbeat());
|
||||||
|
vo.setHeartbeatVersion(machine.getHeartbeatVersion());
|
||||||
vo.setVersion(machine.getVersion());
|
vo.setVersion(machine.getVersion());
|
||||||
if (System.currentTimeMillis() - machine.getTimestamp().getTime() < MachineDiscovery.MAX_CLIENT_LIVE_TIME_MS) {
|
vo.setHealthy(machine.isHealthy());
|
||||||
vo.setHealth(true);
|
|
||||||
}
|
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,20 +80,28 @@ public class MachineInfoVo {
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getPort() {
|
public int getPort() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPort(Integer port) {
|
public void setPort(int port) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getTimestamp() {
|
public long getLastHeartbeat() {
|
||||||
return timestamp;
|
return lastHeartbeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTimestamp(Date timestamp) {
|
public void setLastHeartbeat(long lastHeartbeat) {
|
||||||
this.timestamp = timestamp;
|
this.lastHeartbeat = lastHeartbeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeartbeatVersion(long heartbeatVersion) {
|
||||||
|
this.heartbeatVersion = heartbeatVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHeartbeatVersion() {
|
||||||
|
return heartbeatVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
|
|
@ -107,11 +113,11 @@ public class MachineInfoVo {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isHealth() {
|
public boolean isHealthy() {
|
||||||
return health;
|
return healthy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHealth(boolean health) {
|
public void setHealthy(boolean healthy) {
|
||||||
this.health = health;
|
this.healthy = healthy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||||
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory;
|
||||||
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
import com.alibaba.csp.sentinel.config.SentinelConfig;
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
|
import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
|
||||||
|
import com.alibaba.csp.sentinel.dashboard.discovery.AppInfo;
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
||||||
import com.alibaba.csp.sentinel.node.metric.MetricNode;
|
import com.alibaba.csp.sentinel.node.metric.MetricNode;
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
|
import com.alibaba.csp.sentinel.dashboard.repository.metric.MetricsRepository;
|
||||||
import com.alibaba.csp.sentinel.dashboard.util.MachineUtils;
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.concurrent.FutureCallback;
|
import org.apache.http.concurrent.FutureCallback;
|
||||||
|
|
@ -168,14 +168,21 @@ public class MetricFetcher {
|
||||||
if (maxWaitSeconds <= 0) {
|
if (maxWaitSeconds <= 0) {
|
||||||
throw new IllegalArgumentException("maxWaitSeconds must > 0, but " + maxWaitSeconds);
|
throw new IllegalArgumentException("maxWaitSeconds must > 0, but " + maxWaitSeconds);
|
||||||
}
|
}
|
||||||
Set<MachineInfo> machines = appManagement.getDetailApp(app).getMachines();
|
AppInfo appInfo = appManagement.getDetailApp(app);
|
||||||
|
// auto remove for app
|
||||||
|
if (appInfo.isDead()) {
|
||||||
|
logger.info("Dead app removed: {}", app);
|
||||||
|
appManagement.removeApp(app);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<MachineInfo> machines = appInfo.getMachines();
|
||||||
logger.debug("enter fetchOnce(" + app + "), machines.size()=" + machines.size()
|
logger.debug("enter fetchOnce(" + app + "), machines.size()=" + machines.size()
|
||||||
+ ", time intervalMs [" + startTime + ", " + endTime + "]");
|
+ ", time intervalMs [" + startTime + ", " + endTime + "]");
|
||||||
if (machines.isEmpty()) {
|
if (machines.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final String msg = "fetch";
|
final String msg = "fetch";
|
||||||
AtomicLong dead = new AtomicLong();
|
AtomicLong unhealthy = new AtomicLong();
|
||||||
final AtomicLong success = new AtomicLong();
|
final AtomicLong success = new AtomicLong();
|
||||||
final AtomicLong fail = new AtomicLong();
|
final AtomicLong fail = new AtomicLong();
|
||||||
|
|
||||||
|
|
@ -184,10 +191,15 @@ public class MetricFetcher {
|
||||||
final Map<String, MetricEntity> metricMap = new ConcurrentHashMap<>(16);
|
final Map<String, MetricEntity> metricMap = new ConcurrentHashMap<>(16);
|
||||||
final CountDownLatch latch = new CountDownLatch(machines.size());
|
final CountDownLatch latch = new CountDownLatch(machines.size());
|
||||||
for (final MachineInfo machine : machines) {
|
for (final MachineInfo machine : machines) {
|
||||||
// dead
|
// auto remove
|
||||||
if (System.currentTimeMillis() - machine.getTimestamp().getTime() > MachineUtils.getMaxClientTimeout()) {
|
if (machine.isDead()) {
|
||||||
|
appManagement.getDetailApp(app).removeMachine(machine.getIp(), machine.getPort());
|
||||||
|
logger.info("Dead machine removed: {}:{} of {}", machine.getIp(), machine.getPort(), app);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!machine.isHealthy()) {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
dead.incrementAndGet();
|
unhealthy.incrementAndGet();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final String url = "http://" + machine.getIp() + ":" + machine.getPort() + "/" + METRIC_URL_PATH
|
final String url = "http://" + machine.getIp() + ":" + machine.getPort() + "/" + METRIC_URL_PATH
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import java.util.stream.Collectors;
|
||||||
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
|
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
import com.alibaba.csp.sentinel.dashboard.discovery.AppManagement;
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
||||||
import com.alibaba.csp.sentinel.dashboard.util.MachineUtils;
|
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
||||||
|
|
@ -48,11 +47,11 @@ public class FlowRuleApiProvider implements DynamicRuleProvider<List<FlowRuleEnt
|
||||||
}
|
}
|
||||||
List<MachineInfo> list = appManagement.getDetailApp(appName).getMachines()
|
List<MachineInfo> list = appManagement.getDetailApp(appName).getMachines()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(MachineUtils::isMachineHealth)
|
.filter(e -> e.isHealthy())
|
||||||
.sorted((e1, e2) -> {
|
.sorted((e1, e2) -> {
|
||||||
if (e1.getTimestamp().before(e2.getTimestamp())) {
|
if (e1.getLastHeatbeat() < e2.getLastHeatbeat()) {
|
||||||
return 1;
|
return 1;
|
||||||
} else if (e1.getTimestamp().after(e2.getTimestamp())) {
|
} else if (e1.getLastHeatbeat() > e2.getLastHeatbeat()) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
|
||||||
import com.alibaba.csp.sentinel.dashboard.util.MachineUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
@ -51,7 +50,7 @@ public class FlowRuleApiPublisher implements DynamicRulePublisher<List<FlowRuleE
|
||||||
Set<MachineInfo> set = appManagement.getDetailApp(app).getMachines();
|
Set<MachineInfo> set = appManagement.getDetailApp(app).getMachines();
|
||||||
|
|
||||||
for (MachineInfo machine : set) {
|
for (MachineInfo machine : set) {
|
||||||
if (!MachineUtils.isMachineHealth(machine)) {
|
if (!machine.isHealthy()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// TODO: parse the results
|
// TODO: parse the results
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ import com.alibaba.csp.sentinel.dashboard.domain.cluster.state.ClusterUniversalS
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig;
|
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ClusterClientConfig;
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
|
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerFlowConfig;
|
||||||
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;
|
import com.alibaba.csp.sentinel.dashboard.domain.cluster.config.ServerTransportConfig;
|
||||||
import com.alibaba.csp.sentinel.dashboard.util.MachineUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
|
@ -111,7 +110,7 @@ public class ClusterConfigService {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CompletableFuture<ClusterUniversalStatePairVO>> futures = appInfo.getMachines().stream()
|
List<CompletableFuture<ClusterUniversalStatePairVO>> futures = appInfo.getMachines().stream()
|
||||||
.filter(MachineUtils::isMachineHealth)
|
.filter(e -> e.isHealthy())
|
||||||
.map(machine -> getClusterUniversalState(app, machine.getIp(), machine.getPort())
|
.map(machine -> getClusterUniversalState(app, machine.getIp(), machine.getPort())
|
||||||
.thenApply(e -> new ClusterUniversalStatePairVO(machine.getIp(), machine.getPort(), e)))
|
.thenApply(e -> new ClusterUniversalStatePairVO(machine.getIp(), machine.getPort(), e)))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
@ -129,7 +128,7 @@ public class ClusterConfigService {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean machineOk = appInfo.getMachines().stream()
|
boolean machineOk = appInfo.getMachines().stream()
|
||||||
.filter(MachineUtils::isMachineHealth)
|
.filter(e -> e.isHealthy())
|
||||||
.map(e -> e.getIp() + '@' + e.getPort())
|
.map(e -> e.getIp() + '@' + e.getPort())
|
||||||
.anyMatch(e -> e.equals(machineId));
|
.anyMatch(e -> e.equals(machineId));
|
||||||
if (!machineOk) {
|
if (!machineOk) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ package com.alibaba.csp.sentinel.dashboard.util;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
|
|
||||||
import com.alibaba.csp.sentinel.util.StringUtil;
|
import com.alibaba.csp.sentinel.util.StringUtil;
|
||||||
import com.alibaba.csp.sentinel.util.function.Tuple2;
|
import com.alibaba.csp.sentinel.util.function.Tuple2;
|
||||||
|
|
||||||
|
|
@ -25,13 +24,6 @@ import com.alibaba.csp.sentinel.util.function.Tuple2;
|
||||||
* @author Eric Zhao
|
* @author Eric Zhao
|
||||||
*/
|
*/
|
||||||
public final class MachineUtils {
|
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) {
|
public static Optional<Integer> parseCommandPort(String machineIp) {
|
||||||
try {
|
try {
|
||||||
if (!machineIp.contains("@")) {
|
if (!machineIp.contains("@")) {
|
||||||
|
|
@ -61,11 +53,4 @@ public final class MachineUtils {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isMachineHealth(MachineInfo machine) {
|
|
||||||
if (machine == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return System.currentTimeMillis() - machine.getTimestamp().getTime() < getMaxClientTimeout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,26 +20,46 @@ app.controller('MachineCtl', ['$scope', '$stateParams', 'MachineService',
|
||||||
$scope.propertyName = propertyName;
|
$scope.propertyName = propertyName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.reloadMachines = function() {
|
||||||
MachineService.getAppMachines($scope.app).success(
|
MachineService.getAppMachines($scope.app).success(
|
||||||
function (data) {
|
function (data) {
|
||||||
// console.log('get machines: ' + data.data[0].hostname)
|
// console.log('get machines: ' + data.data[0].hostname)
|
||||||
if (data.code == 0 && data.data) {
|
if (data.code == 0 && data.data) {
|
||||||
$scope.machines = data.data;
|
$scope.machines = data.data;
|
||||||
var health = 0;
|
var healthy = 0;
|
||||||
$scope.machines.forEach(function (item) {
|
$scope.machines.forEach(function (item) {
|
||||||
if (item.health) {
|
if (item.healthy) {
|
||||||
health++;
|
healthy++;
|
||||||
}
|
}
|
||||||
if (!item.hostname) {
|
if (!item.hostname) {
|
||||||
item.hostname = '未知'
|
item.hostname = '未知'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
$scope.healthCount = health;
|
$scope.healthyCount = healthy;
|
||||||
$scope.machinesPageConfig.totalCount = $scope.machines.length;
|
$scope.machinesPageConfig.totalCount = $scope.machines.length;
|
||||||
} else {
|
} else {
|
||||||
$scope.machines = [];
|
$scope.machines = [];
|
||||||
$scope.healthCount = 0;
|
$scope.healthyCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.removeMachine = function(ip, port) {
|
||||||
|
if (!confirm("confirm to remove machine [" + ip + ":" + port + "]?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MachineService.removeAppMachine($scope.app, ip, port).success(
|
||||||
|
function(data) {
|
||||||
|
if (data.code == 0) {
|
||||||
|
$scope.reloadMachines();
|
||||||
|
} else {
|
||||||
|
alert("remove failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.reloadMachines();
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<a href="javascript:void(0);" ng-click="click($event)" collapse="{{collpaseall == 1}}" style="font-size: 16px;word-break: break-word;">
|
<a href="javascript:void(0);" ng-click="click($event)" collapse="{{collpaseall == 1}}" style="font-size: 16px;word-break: break-word;">
|
||||||
{{entry.app}}
|
{{entry.app}}
|
||||||
<span class="fa arrow"></span>
|
<span class="fa arrow"></span>
|
||||||
<span class="arrow">({{entry.heathCount}}/{{entry.machines.length}})</span>
|
<span class="arrow">({{entry.heathyCount}}/{{entry.machines.length}})</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!--<ul class="nav nav-second-level" collapse="{{entry.active}}" style="display: none;">-->
|
<!--<ul class="nav nav-second-level" collapse="{{entry.active}}" style="display: none;">-->
|
||||||
|
|
|
||||||
|
|
@ -21,20 +21,19 @@ angular.module('sentinelDashboardApp')
|
||||||
function (data) {
|
function (data) {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
let initHashApp = $location.path().split('/')[3];
|
let initHashApp = $location.path().split('/')[3];
|
||||||
let currTime = moment(new Date()).utc().add(-1000*60*5).format('YYYY-MM-DDTHH:mm:ss')
|
|
||||||
$scope.apps = data.data;
|
$scope.apps = data.data;
|
||||||
$scope.apps = $scope.apps.map(function (item) {
|
$scope.apps = $scope.apps.map(function (item) {
|
||||||
if (item.app === initHashApp) {
|
if (item.app === initHashApp) {
|
||||||
item.active = true;
|
item.active = true;
|
||||||
}
|
}
|
||||||
var heathCount = 0;
|
var heathyCount = 0;
|
||||||
for (var i in item.machines) {
|
for (var i in item.machines) {
|
||||||
if (item.machines[i].timestamp>currTime) {
|
if (item.machines[i].healthy) {
|
||||||
heathCount++;
|
heathyCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item.heathCount = heathCount;
|
item.heathyCount = heathyCount;
|
||||||
if (heathCount>0) {
|
if (item.shown) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
var app = angular.module('sentinelDashboardApp');
|
var app = angular.module('sentinelDashboardApp');
|
||||||
|
|
||||||
app.service('MachineService', ['$http', function ($http) {
|
app.service('MachineService', ['$http', '$httpParamSerializerJQLike', function ($http, $httpParamSerializerJQLike) {
|
||||||
this.getAppMachines = function (app) {
|
this.getAppMachines = function (app) {
|
||||||
return $http({
|
return $http({
|
||||||
url: 'app/' + app + '/machines.json',
|
url: 'app/' + app + '/machines.json',
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
this.removeAppMachine = function (app, ip, port) {
|
||||||
|
return $http({
|
||||||
|
url: 'app/' + app + '/machine/remove.json',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
"Content-type": 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
|
},
|
||||||
|
data: $httpParamSerializerJQLike({
|
||||||
|
ip: ip,
|
||||||
|
port: port
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
}]);
|
}]);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
<!--<h2 class="page-header">{{app}} </h2>-->
|
<!--<h2 class="page-header">{{app}} </h2>-->
|
||||||
<!--<div>-->
|
<!--<div>-->
|
||||||
<!--<span>实例总数 {{machines.length}}</span>,-->
|
<!--<span>实例总数 {{machines.length}}</span>,-->
|
||||||
<!--<span style="color: green"> 健康 {{healthCount}}</span>,-->
|
<!--<span style="color: green"> 健康 {{healthyCount}}</span>,-->
|
||||||
<!--<span style="color: red"> 失联 {{machines.length-healthCount}}</span>。-->
|
<!--<span style="color: red"> 失联 {{machines.length-healthyCount}}</span>。-->
|
||||||
|
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--<div>-->
|
<!--<div>-->
|
||||||
<!--<span>实例总数 {{machines.length}}, 健康 {{healthCount}}, 失联 {{machines.length-healthCount}}</span>-->
|
<!--<span>实例总数 {{machines.length}}, 健康 {{healthyCount}}, 失联 {{machines.length-healthyCount}}</span>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
|
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="inputs-header">
|
<div class="inputs-header">
|
||||||
<span class="brand" style="font-size: 13px;">机器列表</span>
|
<span class="brand" style="font-size: 13px;">机器列表</span>
|
||||||
<span>实例总数 {{machines.length}}, 健康 {{healthCount}}, 失联 {{machines.length-healthCount}}.</span>
|
<span>实例总数 {{machines.length}}, 健康 {{healthyCount}}, 失联 {{machines.length-healthyCount}}.</span>
|
||||||
<!--<button class="btn btn-danger" style="float: right;margin-right: 10px;height: 30px;font-size: 12px;" ng-click="addNewApp()">全部禁用</button>-->
|
<!--<button class="btn btn-danger" style="float: right;margin-right: 10px;height: 30px;font-size: 12px;" ng-click="addNewApp()">全部禁用</button>-->
|
||||||
<!--<button class="btn btn-danger" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="addNewApp()">全部启用</button>-->
|
<!--<button class="btn btn-danger" style="float: right; margin-right: 10px; height: 30px;font-size: 12px;margin-left: 10px;" ng-click="addNewApp()">全部启用</button>-->
|
||||||
<input class="form-control witdh-300" placeholder="关键字" ng-model="searchKey">
|
<input class="form-control witdh-300" placeholder="关键字" ng-model="searchKey">
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
<td>Sentinel 客户端版本</td>
|
<td>Sentinel 客户端版本</td>
|
||||||
<td>健康状态</td>
|
<td>健康状态</td>
|
||||||
<td>心跳时间</td>
|
<td>心跳时间</td>
|
||||||
|
<td>操作</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -52,10 +53,12 @@
|
||||||
<td style="word-wrap:break-word;word-break:break-all;">{{entry.ip}}</td>
|
<td style="word-wrap:break-word;word-break:break-all;">{{entry.ip}}</td>
|
||||||
<td> {{entry.port}} </td>
|
<td> {{entry.port}} </td>
|
||||||
<td> {{entry.version}} </td>
|
<td> {{entry.version}} </td>
|
||||||
<td ng-if="entry.health">健康</td>
|
<td ng-if="entry.healthy">健康</td>
|
||||||
<td ng-if="!entry.health" style="color: red">失联</td>
|
<td ng-if="!entry.healthy" style="color: red">失联</td>
|
||||||
<td>{{entry.timestamp | date: 'yyyy/MM/dd HH:mm:ss'}}</td>
|
<td>{{entry.lastHeartbeat | date: 'yyyy/MM/dd HH:mm:ss'}}</td>
|
||||||
<!--<td ng-if="!entry.health" style="color: grey">{{entry.timestamp | date: 'yyyy/MM/dd HH:mm:ss'}}</td>-->
|
<td>
|
||||||
|
<button ng-if="!entry.healthy" class="btn btn-xs btn-default" style="height: 25px; font-size: 12px;" ng-click="removeMachine(entry.ip, entry.port)">REMOVE</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* 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.dashboard.config;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.contrib.java.lang.system.EnvironmentVariables;
|
||||||
|
|
||||||
|
public class DashboardConfigTest {
|
||||||
|
@Rule
|
||||||
|
public final EnvironmentVariables environmentVariables = new EnvironmentVariables();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetConfigInt() {
|
||||||
|
// skip cache
|
||||||
|
|
||||||
|
// default value
|
||||||
|
assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10));
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(1, DashboardConfig.getConfigInt("t", 1, 10));
|
||||||
|
|
||||||
|
// property, wrong format
|
||||||
|
System.setProperty("t", "asdf");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10));
|
||||||
|
System.setProperty("t", "");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10));
|
||||||
|
|
||||||
|
// min value
|
||||||
|
System.setProperty("t", "2");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(2, DashboardConfig.getConfigInt("t", 0, 1));
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(10, DashboardConfig.getConfigInt("t", 0, 10));
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(2, DashboardConfig.getConfigInt("t", 0, -1));
|
||||||
|
|
||||||
|
// env
|
||||||
|
environmentVariables.set("t", "20");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(20, DashboardConfig.getConfigInt("t", 0, 10));
|
||||||
|
|
||||||
|
// wrong format env var, but it will override property
|
||||||
|
environmentVariables.set("t", "20dddd");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(0, DashboardConfig.getConfigInt("t", 0, 10));
|
||||||
|
|
||||||
|
// clear env, it will take property
|
||||||
|
environmentVariables.set("t", "");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(10, DashboardConfig.getConfigInt("t", 0, 10));
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(2, DashboardConfig.getConfigInt("t", 0, 1));
|
||||||
|
|
||||||
|
// enable cache
|
||||||
|
System.setProperty("t", "666");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
assertEquals(666, DashboardConfig.getConfigInt("t", 0, 1));
|
||||||
|
System.setProperty("t", "777");
|
||||||
|
assertEquals(666, DashboardConfig.getConfigInt("t", 0, 1));
|
||||||
|
System.setProperty("t", "555");
|
||||||
|
assertEquals(666, DashboardConfig.getConfigInt("t", 0, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,8 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class AppInfoTest {
|
public class AppInfoTest {
|
||||||
|
|
@ -65,4 +67,91 @@ public class AppInfoTest {
|
||||||
return machine;
|
return machine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addRemoveMachineTest() {
|
||||||
|
AppInfo appInfo = new AppInfo("default");
|
||||||
|
assertEquals("default", appInfo.getApp());
|
||||||
|
assertEquals(0, appInfo.getMachines().size());
|
||||||
|
//add one
|
||||||
|
{
|
||||||
|
MachineInfo machineInfo = new MachineInfo();
|
||||||
|
machineInfo.setApp("default");
|
||||||
|
machineInfo.setHostname("bogon");
|
||||||
|
machineInfo.setIp("127.0.0.1");
|
||||||
|
machineInfo.setPort(3389);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis());
|
||||||
|
machineInfo.setHeartbeatVersion(1);
|
||||||
|
machineInfo.setVersion("0.4.1");
|
||||||
|
appInfo.addMachine(machineInfo);
|
||||||
}
|
}
|
||||||
|
assertEquals(1, appInfo.getMachines().size());
|
||||||
|
//add duplicated one
|
||||||
|
{
|
||||||
|
MachineInfo machineInfo = new MachineInfo();
|
||||||
|
machineInfo.setApp("default");
|
||||||
|
machineInfo.setHostname("bogon");
|
||||||
|
machineInfo.setIp("127.0.0.1");
|
||||||
|
machineInfo.setPort(3389);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis());
|
||||||
|
machineInfo.setHeartbeatVersion(1);
|
||||||
|
machineInfo.setVersion("0.4.2");
|
||||||
|
appInfo.addMachine(machineInfo);
|
||||||
|
}
|
||||||
|
assertEquals(1, appInfo.getMachines().size());
|
||||||
|
//add different one
|
||||||
|
{
|
||||||
|
MachineInfo machineInfo = new MachineInfo();
|
||||||
|
machineInfo.setApp("default");
|
||||||
|
machineInfo.setHostname("bogon");
|
||||||
|
machineInfo.setIp("127.0.0.1");
|
||||||
|
machineInfo.setPort(3390);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis());
|
||||||
|
machineInfo.setHeartbeatVersion(1);
|
||||||
|
machineInfo.setVersion("0.4.3");
|
||||||
|
appInfo.addMachine(machineInfo);
|
||||||
|
}
|
||||||
|
assertEquals(2, appInfo.getMachines().size());
|
||||||
|
appInfo.removeMachine("127.0.0.1", 3389);
|
||||||
|
assertEquals(1, appInfo.getMachines().size());
|
||||||
|
appInfo.removeMachine("127.0.0.1", 3390);
|
||||||
|
assertEquals(0, appInfo.getMachines().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHealthyAndDead() {
|
||||||
|
System.setProperty(DashboardConfig.CONFIG_HIDE_APP_NO_MACHINE_MILLIS, "60000");
|
||||||
|
System.setProperty(DashboardConfig.CONFIG_REMOVE_APP_NO_MACHINE_MILLIS, "600000");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
String appName = "default";
|
||||||
|
AppInfo appInfo = new AppInfo();
|
||||||
|
appInfo.setApp(appName);
|
||||||
|
{
|
||||||
|
MachineInfo machineInfo = MachineInfo.of(appName, "127.0.0.1", 8801);
|
||||||
|
machineInfo.setHeartbeatVersion(1);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis());
|
||||||
|
appInfo.addMachine(machineInfo);
|
||||||
|
}
|
||||||
|
assertTrue(appInfo.isShown());
|
||||||
|
assertFalse(appInfo.isDead());
|
||||||
|
|
||||||
|
{
|
||||||
|
MachineInfo machineInfo = MachineInfo.of(appName, "127.0.0.1", 8801);
|
||||||
|
machineInfo.setHeartbeatVersion(1);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis() - 70000);
|
||||||
|
appInfo.addMachine(machineInfo);
|
||||||
|
}
|
||||||
|
assertFalse(appInfo.isShown());
|
||||||
|
assertFalse(appInfo.isDead());
|
||||||
|
|
||||||
|
{
|
||||||
|
MachineInfo machineInfo = MachineInfo.of(appName, "127.0.0.1", 8801);
|
||||||
|
machineInfo.setHeartbeatVersion(1);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis() - 700000);
|
||||||
|
appInfo.addMachine(machineInfo);
|
||||||
|
}
|
||||||
|
assertFalse(appInfo.isShown());
|
||||||
|
assertTrue(appInfo.isDead());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.alibaba.csp.sentinel.dashboard.discovery;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import com.alibaba.csp.sentinel.dashboard.config.DashboardConfig;
|
||||||
|
|
||||||
|
public class MachineInfoTest {
|
||||||
|
@Test
|
||||||
|
public void testHealthyAndDead() {
|
||||||
|
System.setProperty(DashboardConfig.CONFIG_UNHEALTHY_MACHINE_MILLIS, "60000");
|
||||||
|
System.setProperty(DashboardConfig.CONFIG_AUTO_REMOVE_MACHINE_MILLIS, "600000");
|
||||||
|
DashboardConfig.clearCache();
|
||||||
|
MachineInfo machineInfo = new MachineInfo();
|
||||||
|
machineInfo.setHeartbeatVersion(1);
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis() - 10000);
|
||||||
|
assertTrue(machineInfo.isHealthy());
|
||||||
|
assertFalse(machineInfo.isDead());
|
||||||
|
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis() - 100000);
|
||||||
|
assertFalse(machineInfo.isHealthy());
|
||||||
|
assertFalse(machineInfo.isDead());
|
||||||
|
|
||||||
|
machineInfo.setLastHeatbeat(System.currentTimeMillis() - 1000000);
|
||||||
|
assertFalse(machineInfo.isHealthy());
|
||||||
|
assertTrue(machineInfo.isDead());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue