0825
This commit is contained in:
@@ -31,6 +31,9 @@ type Runner struct {
|
||||
tools ToolSet
|
||||
metrics []Metric
|
||||
installFailed []string // 自动安装仍失败的工具
|
||||
useDocker bool // 是否使用 Docker 模式执行
|
||||
dockerImage string // Docker 镜像名(如 registry/stress-tools:latest)
|
||||
sudoPwd string // sudo 密码(用于 docker 命令)
|
||||
}
|
||||
|
||||
// NewRunner 创建执行器(检查/在线安装 → 构建指标 → 生成脚本 → 执行)
|
||||
@@ -41,6 +44,31 @@ func NewRunner(cfg Config, types []TestType, client *ssh.Client, autoInstall boo
|
||||
return &Runner{cfg: cfg, types: types, client: client, tools: tools, metrics: metrics, installFailed: failed}
|
||||
}
|
||||
|
||||
// NewRunnerDocker 创建 Docker 模式执行器(工具预装在镜像中,无需在线安装)
|
||||
// image 为 Docker 镜像名,sudoPwd 用于非 root 用户执行 docker 命令。
|
||||
func NewRunnerDocker(cfg Config, types []TestType, client *ssh.Client, image string, sudoPwd string) *Runner {
|
||||
// Docker 模式下,镜像内已包含所有工具,直接标记全部可用。
|
||||
// 这样 BuildMetrics 不会因缺少工具而禁用测试项。
|
||||
tools := ToolSet{Tools: make(map[string]ToolInfo)}
|
||||
for _, name := range requiredTools {
|
||||
tools.Tools[name] = ToolInfo{Name: name, Path: "/usr/bin/" + name, Version: "docker"}
|
||||
}
|
||||
// 额外标记 lm-sensors、stressapptest(Docker 镜像预装)
|
||||
tools.Tools["lm-sensors"] = ToolInfo{Name: "lm-sensors", Path: "/usr/bin/sensors", Version: "docker"}
|
||||
tools.Tools["stressapptest"] = ToolInfo{Name: "stressapptest", Path: "/usr/bin/stressapptest", Version: "docker"}
|
||||
metrics := BuildMetrics(types, tools)
|
||||
return &Runner{
|
||||
cfg: cfg,
|
||||
types: types,
|
||||
client: client,
|
||||
tools: tools,
|
||||
metrics: metrics,
|
||||
useDocker: true,
|
||||
dockerImage: image,
|
||||
sudoPwd: sudoPwd,
|
||||
}
|
||||
}
|
||||
|
||||
// Run 执行压力测试(生成脚本 → 远程执行 → 解析结果)
|
||||
func (r *Runner) Run(ip string) *Report {
|
||||
report := &Report{IP: ip, StartTime: time.Now()}
|
||||
@@ -74,8 +102,14 @@ func (r *Runner) Run(ip string) *Report {
|
||||
script := r.assembleScript()
|
||||
fmt.Printf(" [脚本] 拼装完成,%d 字节\n", len(script))
|
||||
|
||||
// 通过 heredoc 传到远程执行
|
||||
scriptCmd := fmt.Sprintf("cat <<'AUTOCHECKSCRIPT' > /tmp/auto-check.sh\n%s\nAUTOCHECKSCRIPT\nchmod +x /tmp/auto-check.sh && bash /tmp/auto-check.sh 2>&1", script)
|
||||
// 通过 heredoc 传到远程执行(Docker 模式或原生模式)
|
||||
var scriptCmd string
|
||||
if r.useDocker {
|
||||
scriptCmd = r.buildDockerCmd(script)
|
||||
fmt.Printf(" [模式] Docker 容器执行(镜像: %s)\n", r.dockerImage)
|
||||
} else {
|
||||
scriptCmd = fmt.Sprintf("cat <<'AUTOCHECKSCRIPT' > /tmp/auto-check.sh\n%s\nAUTOCHECKSCRIPT\nchmod +x /tmp/auto-check.sh && bash /tmp/auto-check.sh 2>&1", script)
|
||||
}
|
||||
output, err := sshclient.RunCommand(r.client, scriptCmd)
|
||||
|
||||
if err != nil {
|
||||
@@ -366,6 +400,39 @@ func (r *Runner) readScript(name string) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// buildDockerCmd 构造 Docker 执行命令。
|
||||
// 将脚本通过 heredoc 传入 docker run,在容器内执行。
|
||||
// 容器需要挂载 /sys(温度传感器)、/volume1(飞牛存储卷)等。
|
||||
func (r *Runner) buildDockerCmd(script string) string {
|
||||
// 构造 docker run 命令:
|
||||
// --rm 容器结束后自动清理
|
||||
// -i 交互式输入(通过 stdin 传入脚本)
|
||||
// --privileged 允许访问硬件(温度传感器、内核日志)
|
||||
// --pid=host 共享宿主机进程空间(top 可见)
|
||||
// -v /sys:/sys:ro 温度传感器(只读)
|
||||
// -v /volume1:/volume1 飞牛存储卷(磁盘测试)
|
||||
dockerCmd := fmt.Sprintf(
|
||||
"docker run --rm -i --privileged --pid=host "+
|
||||
"-v /sys:/sys:ro "+
|
||||
"-v /volume1:/volume1 "+
|
||||
"%s bash -s",
|
||||
r.dockerImage,
|
||||
)
|
||||
// 通过 heredoc 将脚本传入容器执行(使用 sudo 以获取 docker 权限)
|
||||
heredoc := fmt.Sprintf("cat <<'AUTOCHECKSCRIPT' | %s\n%s\nAUTOCHECKSCRIPT", dockerCmd, script)
|
||||
|
||||
// 如果需要 sudo(非 root 用户)
|
||||
runner := heredoc
|
||||
if out, err := sshclient.RunCommand(r.client, "id -u"); err == nil && strings.TrimSpace(out) != "0" {
|
||||
if r.sudoPwd != "" {
|
||||
runner = fmt.Sprintf("echo '%s' | sudo -S bash -c %s", r.sudoPwd, shellQuote(heredoc))
|
||||
} else {
|
||||
runner = "sudo " + heredoc
|
||||
}
|
||||
}
|
||||
return runner
|
||||
}
|
||||
|
||||
// testPriority 测试执行顺序:单项测试在前,综合测试(full)最后,监控项最后
|
||||
func testPriority(m Metric) int {
|
||||
if m.IsMonitor {
|
||||
@@ -432,6 +499,28 @@ func TestDevice(ip string, cfg config.Config) *Report {
|
||||
stressCfg.TempLimit = cfg.Stress.TempLimit
|
||||
stressCfg.UseMirror = cfg.Stress.UseMirror
|
||||
|
||||
// 检测是否使用 Docker 模式(配置启用且远程主机支持)
|
||||
if cfg.Docker.Enabled {
|
||||
dockerInfo := DetectDocker(conn, cfg.Docker.Image, sudoPwd)
|
||||
if dockerInfo.Available {
|
||||
if dockerInfo.HasImage {
|
||||
fmt.Printf(" [Docker] 镜像 %s 已存在,使用容器模式\n", cfg.Docker.Image)
|
||||
return NewRunnerDocker(stressCfg, ParseTypes(cfg.Stress.Types), conn, cfg.Docker.Image, sudoPwd).Run(ip)
|
||||
}
|
||||
// 镜像不存在,尝试拉取(如果失败则回退到原生模式)
|
||||
fmt.Printf(" [Docker] 镜像 %s 不存在,尝试拉取...\n", cfg.Docker.Image)
|
||||
if out, err := runPrivileged(conn, fmt.Sprintf("docker pull %s 2>&1", cfg.Docker.Image), sudoPwd); err == nil {
|
||||
fmt.Printf(" [Docker] 镜像拉取成功\n")
|
||||
return NewRunnerDocker(stressCfg, ParseTypes(cfg.Stress.Types), conn, cfg.Docker.Image, sudoPwd).Run(ip)
|
||||
} else {
|
||||
fmt.Printf(" [Docker] 镜像拉取失败,回退到原生模式: %v\n%s\n", err, truncate(out, 200))
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(" [Docker] 目标机未安装 Docker,回退到原生模式\n")
|
||||
}
|
||||
}
|
||||
|
||||
// 原生模式:在线安装工具(回退路径)
|
||||
return NewRunner(stressCfg, ParseTypes(cfg.Stress.Types), conn, cfg.Stress.AutoInstall, sudoPwd).Run(ip)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user