wip v1.0.0
This commit is contained in:
470
internal/cmd/test.go
Normal file
470
internal/cmd/test.go
Normal file
@@ -0,0 +1,470 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/loveuer/go-alived/pkg/logger"
|
||||
"github.com/loveuer/go-alived/pkg/netif"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type TestResult struct {
|
||||
Name string
|
||||
Pass bool
|
||||
Message string
|
||||
Fatal bool
|
||||
}
|
||||
|
||||
type EnvironmentTest struct {
|
||||
log *logger.Logger
|
||||
results []TestResult
|
||||
errors int
|
||||
warns int
|
||||
}
|
||||
|
||||
func NewEnvironmentTest(log *logger.Logger) *EnvironmentTest {
|
||||
return &EnvironmentTest{
|
||||
log: log,
|
||||
results: make([]TestResult, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) AddResult(name string, pass bool, message string, fatal bool) {
|
||||
t.results = append(t.results, TestResult{
|
||||
Name: name,
|
||||
Pass: pass,
|
||||
Message: message,
|
||||
Fatal: fatal,
|
||||
})
|
||||
|
||||
if !pass {
|
||||
if fatal {
|
||||
t.errors++
|
||||
} else {
|
||||
t.warns++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestRootPermission() {
|
||||
t.log.Info("检查运行权限...")
|
||||
|
||||
if os.Geteuid() != 0 {
|
||||
t.AddResult("Root权限", false, "需要root权限运行,请使用sudo", true)
|
||||
} else {
|
||||
t.AddResult("Root权限", true, "以root用户运行", false)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestNetworkInterface(ifaceName string) string {
|
||||
t.log.Info("检查网络接口...")
|
||||
|
||||
if ifaceName == "" {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
t.AddResult("网络接口", false, "无法获取网络接口列表", true)
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagUp != 0 && iface.Flags&net.FlagLoopback == 0 {
|
||||
addrs, err := iface.Addrs()
|
||||
if err == nil && len(addrs) > 0 {
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && ipnet.IP.To4() != nil {
|
||||
ifaceName = iface.Name
|
||||
t.log.Info("自动选择网卡: %s", ifaceName)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if ifaceName != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ifaceName == "" {
|
||||
t.AddResult("网络接口", false, "未找到可用的网络接口", true)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
iface, err := netif.GetInterface(ifaceName)
|
||||
if err != nil {
|
||||
t.AddResult("网络接口", false, fmt.Sprintf("网卡 %s 不存在", ifaceName), true)
|
||||
return ""
|
||||
}
|
||||
|
||||
if !iface.IsUp() {
|
||||
t.AddResult("网络接口状态", false, fmt.Sprintf("网卡 %s 未启动", ifaceName), true)
|
||||
return ""
|
||||
}
|
||||
|
||||
t.AddResult("网络接口", true, fmt.Sprintf("网卡 %s 存在且已启动", ifaceName), false)
|
||||
return ifaceName
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestVIPOperations(ifaceName, testVIP string) {
|
||||
t.log.Info("测试VIP添加/删除功能...")
|
||||
|
||||
if ifaceName == "" || testVIP == "" {
|
||||
t.AddResult("VIP操作", false, "网卡名或测试VIP为空", true)
|
||||
return
|
||||
}
|
||||
|
||||
iface, err := netif.GetInterface(ifaceName)
|
||||
if err != nil {
|
||||
t.AddResult("VIP操作", false, fmt.Sprintf("获取网卡失败: %v", err), true)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.Contains(testVIP, "/") {
|
||||
testVIP = testVIP + "/32"
|
||||
}
|
||||
|
||||
exists, _ := iface.HasIP(testVIP)
|
||||
if exists {
|
||||
t.AddResult("VIP操作", false, fmt.Sprintf("VIP %s 已存在,请使用其他IP测试", testVIP), true)
|
||||
return
|
||||
}
|
||||
|
||||
err = iface.AddIP(testVIP)
|
||||
if err != nil {
|
||||
t.AddResult("VIP添加", false, fmt.Sprintf("VIP添加失败: %v", err), true)
|
||||
return
|
||||
}
|
||||
|
||||
t.AddResult("VIP添加", true, fmt.Sprintf("成功添加VIP %s", testVIP), false)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
exists, _ = iface.HasIP(testVIP)
|
||||
if !exists {
|
||||
t.AddResult("VIP验证", false, "VIP添加后无法在网卡上找到", true)
|
||||
iface.DeleteIP(testVIP)
|
||||
return
|
||||
}
|
||||
|
||||
t.AddResult("VIP验证", true, "VIP已成功添加到网卡", false)
|
||||
|
||||
vipAddr := strings.Split(testVIP, "/")[0]
|
||||
cmd := exec.Command("ping", "-c", "1", "-W", "1", vipAddr)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
t.AddResult("VIP可达性", false, "VIP ping失败(可能需要路由配置)", false)
|
||||
} else {
|
||||
t.AddResult("VIP可达性", true, "VIP可以ping通", false)
|
||||
}
|
||||
|
||||
err = iface.DeleteIP(testVIP)
|
||||
if err != nil {
|
||||
t.AddResult("VIP删除", false, fmt.Sprintf("VIP删除失败: %v", err), false)
|
||||
} else {
|
||||
t.AddResult("VIP删除", true, "VIP删除成功", false)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestMulticast(ifaceName string) {
|
||||
t.log.Info("检查组播支持...")
|
||||
|
||||
if ifaceName == "" {
|
||||
t.AddResult("组播支持", false, "网卡名为空,跳过检查", false)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command("ip", "maddr", "show", ifaceName)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.AddResult("组播支持", false, "无法查询组播配置", false)
|
||||
return
|
||||
}
|
||||
|
||||
if len(output) > 0 {
|
||||
t.AddResult("组播支持", true, "网卡支持组播", false)
|
||||
} else {
|
||||
t.AddResult("组播支持", false, "网卡组播支持未知", false)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestFirewall() {
|
||||
t.log.Info("检查防火墙设置...")
|
||||
|
||||
cmd := exec.Command("iptables", "-L", "INPUT", "-n")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.AddResult("防火墙检查", false, "无法查询iptables规则(可能未安装)", false)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(string(output), "112") || strings.Contains(string(output), "vrrp") {
|
||||
t.AddResult("防火墙VRRP", true, "防火墙已配置VRRP规则", false)
|
||||
} else {
|
||||
t.AddResult("防火墙VRRP", false, "防火墙未配置VRRP规则,建议添加: iptables -A INPUT -p 112 -j ACCEPT", false)
|
||||
}
|
||||
|
||||
cmd = exec.Command("systemctl", "is-active", "firewalld")
|
||||
err = cmd.Run()
|
||||
if err == nil {
|
||||
cmd = exec.Command("firewall-cmd", "--list-protocols")
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
if strings.Contains(string(output), "vrrp") {
|
||||
t.AddResult("Firewalld VRRP", true, "firewalld已允许VRRP协议", false)
|
||||
} else {
|
||||
t.AddResult("Firewalld VRRP", false, "firewalld未配置VRRP,建议: firewall-cmd --permanent --add-protocol=vrrp", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestKernelParameters() {
|
||||
t.log.Info("检查内核参数...")
|
||||
|
||||
params := map[string]string{
|
||||
"/proc/sys/net/ipv4/ip_forward": "1",
|
||||
"/proc/sys/net/ipv4/conf/all/arp_ignore": "0",
|
||||
"/proc/sys/net/ipv4/conf/all/arp_announce": "0",
|
||||
}
|
||||
|
||||
for path, expected := range params {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(string(data))
|
||||
name := strings.TrimPrefix(path, "/proc/sys/net/ipv4/")
|
||||
|
||||
if value == expected {
|
||||
t.AddResult(name, true, fmt.Sprintf("%s = %s (正常)", name, value), false)
|
||||
} else {
|
||||
if name == "ip_forward" && value != "1" {
|
||||
t.AddResult(name, false, fmt.Sprintf("%s = %s (建议设置为1)", name, value), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestConflictingServices() {
|
||||
t.log.Info("检查冲突服务...")
|
||||
|
||||
services := []string{"keepalived"}
|
||||
hasConflict := false
|
||||
|
||||
for _, service := range services {
|
||||
cmd := exec.Command("systemctl", "is-active", service)
|
||||
err := cmd.Run()
|
||||
if err == nil {
|
||||
t.AddResult("服务冲突", false, fmt.Sprintf("发现运行中的%s服务,可能冲突", service), false)
|
||||
hasConflict = true
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("pgrep", "-x", "keepalived")
|
||||
err := cmd.Run()
|
||||
if err == nil {
|
||||
t.AddResult("进程冲突", false, "发现运行中的keepalived进程", false)
|
||||
hasConflict = true
|
||||
}
|
||||
|
||||
if !hasConflict {
|
||||
t.AddResult("服务冲突", true, "未发现冲突的服务", false)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestVirtualization() {
|
||||
t.log.Info("检查虚拟化环境...")
|
||||
|
||||
productFile := "/sys/class/dmi/id/product_name"
|
||||
data, err := os.ReadFile(productFile)
|
||||
if err != nil {
|
||||
cmd := exec.Command("systemd-detect-virt")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
virt := strings.TrimSpace(string(output))
|
||||
if virt != "none" {
|
||||
t.AddResult("虚拟化", true, fmt.Sprintf("检测到虚拟化环境: %s", virt), false)
|
||||
t.log.Warn("虚拟化环境可能需要特殊配置(如启用混杂模式)")
|
||||
} else {
|
||||
t.AddResult("虚拟化", true, "物理机环境", false)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
product := strings.TrimSpace(string(data))
|
||||
switch {
|
||||
case strings.Contains(product, "VMware"):
|
||||
t.AddResult("虚拟化", true, "VMware虚拟机(需要启用混杂模式)", false)
|
||||
t.log.Warn("VMware需要配置: 虚拟机设置 -> 网络适配器 -> 高级 -> 混杂模式: 允许全部")
|
||||
case strings.Contains(product, "VirtualBox"):
|
||||
t.AddResult("虚拟化", true, "VirtualBox虚拟机(需要桥接模式+混杂模式)", false)
|
||||
t.log.Warn("VirtualBox需要配置: 网络 -> 桥接网卡 -> 高级 -> 混杂模式: 全部允许")
|
||||
case strings.Contains(product, "KVM") || strings.Contains(product, "QEMU"):
|
||||
t.AddResult("虚拟化", true, "KVM/QEMU虚拟机(通常支持良好)", false)
|
||||
case strings.Contains(product, "Amazon") || strings.Contains(product, "EC2"):
|
||||
t.AddResult("虚拟化", false, "AWS EC2环境 - 不支持VRRP", true)
|
||||
t.log.Error("AWS不支持组播协议,无法运行VRRP,请使用Elastic IP或负载均衡")
|
||||
default:
|
||||
t.AddResult("虚拟化", true, fmt.Sprintf("环境: %s", product), false)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) TestCloudEnvironment() {
|
||||
t.log.Info("检查云环境...")
|
||||
|
||||
cloudTests := []struct {
|
||||
name string
|
||||
url string
|
||||
headers map[string]string
|
||||
isFatal bool
|
||||
solution string
|
||||
}{
|
||||
{
|
||||
name: "AWS",
|
||||
url: "http://169.254.169.254/latest/meta-data/instance-id",
|
||||
solution: "AWS不支持VRRP,请使用: Elastic IP、ALB或NLB",
|
||||
isFatal: true,
|
||||
},
|
||||
{
|
||||
name: "阿里云",
|
||||
url: "http://100.100.100.200/latest/meta-data/instance-id",
|
||||
solution: "阿里云ECS不支持VRRP,请使用: 负载均衡SLB或高可用虚拟IP(HaVip)",
|
||||
isFatal: true,
|
||||
},
|
||||
{
|
||||
name: "Azure",
|
||||
url: "http://169.254.169.254/metadata/instance?api-version=2021-02-01",
|
||||
headers: map[string]string{"Metadata": "true"},
|
||||
solution: "Azure建议使用: Azure Load Balancer或Traffic Manager",
|
||||
isFatal: false,
|
||||
},
|
||||
{
|
||||
name: "Google Cloud",
|
||||
url: "http://metadata.google.internal/computeMetadata/v1/instance/id",
|
||||
headers: map[string]string{"Metadata-Flavor": "Google"},
|
||||
solution: "GCP建议使用: Cloud Load Balancing",
|
||||
isFatal: false,
|
||||
},
|
||||
}
|
||||
|
||||
cloudDetected := false
|
||||
for _, test := range cloudTests {
|
||||
cmd := exec.Command("curl", "-s", "-m", "1", test.url)
|
||||
if len(test.headers) > 0 {
|
||||
for k, v := range test.headers {
|
||||
cmd.Args = append(cmd.Args, "-H", fmt.Sprintf("%s: %s", k, v))
|
||||
}
|
||||
}
|
||||
|
||||
err := cmd.Run()
|
||||
if err == nil {
|
||||
cloudDetected = true
|
||||
t.AddResult("云环境", !test.isFatal, fmt.Sprintf("检测到%s环境", test.name), test.isFatal)
|
||||
t.log.Warn(test.solution)
|
||||
}
|
||||
}
|
||||
|
||||
if !cloudDetected {
|
||||
t.AddResult("云环境", true, "未检测到公有云环境限制", false)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) PrintResults() {
|
||||
fmt.Println()
|
||||
fmt.Println("=== 测试结果 ===")
|
||||
fmt.Println()
|
||||
|
||||
for _, result := range t.results {
|
||||
status := "✓"
|
||||
if !result.Pass {
|
||||
if result.Fatal {
|
||||
status = "✗"
|
||||
} else {
|
||||
status = "⚠"
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s %-20s %s\n", status, result.Name, result.Message)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("=== 总结 ===")
|
||||
fmt.Println()
|
||||
|
||||
if t.errors == 0 && t.warns == 0 {
|
||||
fmt.Println("✓ 环境完全支持 go-alived")
|
||||
fmt.Println(" 可以正常使用所有功能")
|
||||
} else if t.errors == 0 {
|
||||
fmt.Printf("⚠ 环境基本支持,但有 %d 个警告\n", t.warns)
|
||||
fmt.Println(" 建议修复警告项以获得更好的稳定性")
|
||||
} else {
|
||||
fmt.Printf("✗ 发现 %d 个错误, %d 个警告\n", t.errors, t.warns)
|
||||
fmt.Println(" 请修复错误后再使用 go-alived")
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func (t *EnvironmentTest) HasErrors() bool {
|
||||
return t.errors > 0
|
||||
}
|
||||
|
||||
var (
|
||||
testIface string
|
||||
testVIP string
|
||||
)
|
||||
|
||||
var testCmd = &cobra.Command{
|
||||
Use: "test",
|
||||
Short: "Test environment for VRRP support",
|
||||
Long: `Test the current environment to verify if it supports VRRP functionality.
|
||||
This includes checking permissions, network interfaces, VIP operations, multicast support, and more.`,
|
||||
Run: runTest,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(testCmd)
|
||||
|
||||
testCmd.Flags().StringVarP(&testIface, "interface", "i", "", "network interface to test (auto-detect if not specified)")
|
||||
testCmd.Flags().StringVarP(&testVIP, "vip", "v", "", "test VIP address (e.g., 192.168.1.100/24)")
|
||||
}
|
||||
|
||||
func runTest(cmd *cobra.Command, args []string) {
|
||||
log := logger.New(false)
|
||||
|
||||
fmt.Println("=== go-alived 环境测试 ===")
|
||||
fmt.Println()
|
||||
|
||||
test := NewEnvironmentTest(log)
|
||||
|
||||
test.TestRootPermission()
|
||||
|
||||
selectedIface := test.TestNetworkInterface(testIface)
|
||||
|
||||
if selectedIface != "" && testVIP != "" {
|
||||
test.TestVIPOperations(selectedIface, testVIP)
|
||||
}
|
||||
|
||||
if selectedIface != "" {
|
||||
test.TestMulticast(selectedIface)
|
||||
}
|
||||
|
||||
test.TestFirewall()
|
||||
test.TestKernelParameters()
|
||||
test.TestConflictingServices()
|
||||
test.TestVirtualization()
|
||||
test.TestCloudEnvironment()
|
||||
|
||||
test.PrintResults()
|
||||
|
||||
if test.HasErrors() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user