Files
go-alived/internal/cmd/test.go
2025-12-08 22:23:45 +08:00

470 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}