Files
be.ems/src/modules/tool/service/iperf.go
2025-06-11 18:30:35 +08:00

290 lines
8.4 KiB
Go
Raw 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 service
import (
"encoding/json"
"fmt"
"io"
"strings"
"time"
"be.ems/src/framework/config"
"be.ems/src/framework/logger"
"be.ems/src/framework/ssh"
"be.ems/src/framework/vo/result"
neService "be.ems/src/modules/network_element/service"
wsModel "be.ems/src/modules/ws/model"
)
// 实例化服务层 IPerf 结构体
var NewIPerf = &IPerf{}
// IPerf 网络性能测试工具 服务层处理
type IPerf struct{}
// Version 查询版本信息
func (s *IPerf) Version(meType, neId, version string) (string, error) {
if version != "V2" && version != "V3" {
return "", fmt.Errorf("iperf version is required V2 or V3")
}
cmd := "iperf3 --version"
if version == "V2" {
cmd = "iperf -v"
}
// 网元主机的SSH客户端
sshClient, err := neService.NewNeInfo.NeRunSSHClient(meType, neId)
if err != nil {
return "", err
}
defer sshClient.Close()
// 检查是否安装iperf
output, err := sshClient.RunCMD(cmd)
if err != nil {
if version == "V2" && strings.HasSuffix(err.Error(), "status 1") { // V2 版本
return strings.TrimSpace(output), nil
}
return "", fmt.Errorf("iperf %s not installed", version)
}
return strings.TrimSpace(output), err
}
// Install 安装iperf3
func (s *IPerf) Install(meType, neId, version string) error {
if version != "V2" && version != "V3" {
return fmt.Errorf("iperf version is required V2 or V3")
}
// 网元主机的SSH客户端
sshClient, err := neService.NewNeInfo.NeRunSSHClient(meType, neId)
if err != nil {
return err
}
defer sshClient.Close()
// 网元主机的SSH客户端进行文件传输
sftpClient, err := sshClient.NewClientSFTP()
if err != nil {
return err
}
defer sftpClient.Close()
nePath := "/tmp"
depPkg := "sudo dpkg -i"
depDir := "assets/dependency/iperf3/deb"
// 检查平台类型
if _, err := sshClient.RunCMD("sudo dpkg --version"); err == nil {
depPkg = "sudo dpkg -i"
depDir = "assets/dependency/iperf3/deb"
// sudo apt remove iperf3 libiperf0 libsctp1 libsctp-dev lksctp-tools
} else if _, err := sshClient.RunCMD("sudo yum --version"); err == nil {
depPkg = "sudo rpm -Uvh --nosignature --reinstall --force"
depDir = "assets/dependency/iperf3/rpm"
// yum remove iperf3 iperf3-help.noarch
} else {
return fmt.Errorf("iperf %s not supported install", version)
}
// V2版本和V3版本的安装包路径不同
if version == "V2" {
depDir = strings.Replace(depDir, "iperf3", "iperf", 1)
}
// 从 embed.FS 中读取默认配置文件内容
assetsDir := config.GetAssetsDirFS()
fsDirEntrys, err := assetsDir.ReadDir(depDir)
if err != nil {
return err
}
neFilePaths := []string{}
for _, d := range fsDirEntrys {
// 打开本地文件
localFile, err := assetsDir.Open(fmt.Sprintf("%s/%s", depDir, d.Name()))
if err != nil {
return fmt.Errorf("iperf %s file local error", version)
}
defer localFile.Close()
// 创建远程文件
remotePath := fmt.Sprintf("%s/%s", nePath, d.Name())
remoteFile, err := sftpClient.Client.Create(remotePath)
if err != nil {
return fmt.Errorf("iperf %s file remote error", version)
}
defer remoteFile.Close()
// 使用 io.Copy 将嵌入的文件内容复制到目标文件
if _, err := io.Copy(remoteFile, localFile); err != nil {
return fmt.Errorf("iperf %s file copy error", version)
}
neFilePaths = append(neFilePaths, remotePath)
}
// 删除软件包
defer func() {
pkgRemove := fmt.Sprintf("sudo rm %s", strings.Join(neFilePaths, " "))
sshClient.RunCMD(pkgRemove)
}()
// 安装软件包
pkgInstall := fmt.Sprintf("%s %s", depPkg, strings.Join(neFilePaths, " "))
if _, err := sshClient.RunCMD(pkgInstall); err != nil {
return fmt.Errorf("iperf %s install error", version)
}
return err
}
// Run 接收IPerf3终端交互业务处理
func (s *IPerf) Run(client *wsModel.WSClient, reqMsg wsModel.WSRequest) {
// 必传requestId确认消息
if reqMsg.RequestID == "" {
msg := "message requestId is required"
logger.Infof("ws IPerf Run UID %s err: %s", client.BindUid, msg)
msgByte, _ := json.Marshal(result.ErrMsg(msg))
client.MsgChan <- msgByte
return
}
var resByte []byte
var err error
switch reqMsg.Type {
case "close":
// 主动关闭
resultByte, _ := json.Marshal(result.OkMsg("user initiated closure"))
client.MsgChan <- resultByte
// 等待1s后关闭连接
time.Sleep(1 * time.Second)
client.StopChan <- struct{}{}
return
case "iperf":
// SSH会话消息接收写入会话
var command string
command, err = s.parseOptions(reqMsg.Data)
if command != "" && err == nil {
sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
_, err = sshClientSession.Write(command)
}
case "ctrl-c":
// 模拟按下 Ctrl+C
sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
_, err = sshClientSession.Write("\u0003\n")
case "resize":
// 会话窗口重置
msgByte, _ := json.Marshal(reqMsg.Data)
var data struct {
Cols int `json:"cols"`
Rows int `json:"rows"`
}
err = json.Unmarshal(msgByte, &data)
if err == nil {
sshClientSession := client.ChildConn.(*ssh.SSHClientSession)
err = sshClientSession.Session.WindowChange(data.Rows, data.Cols)
}
default:
err = fmt.Errorf("message type %s not supported", reqMsg.Type)
}
if err != nil {
logger.Warnf("ws IPerf Run UID %s err: %s", client.BindUid, err.Error())
msgByte, _ := json.Marshal(result.ErrMsg(err.Error()))
client.MsgChan <- msgByte
if err == io.EOF {
// 等待1s后关闭连接
time.Sleep(1 * time.Second)
client.StopChan <- struct{}{}
}
return
}
if len(resByte) > 0 {
client.MsgChan <- resByte
}
}
// parseOptions 解析拼装iperf3命令 iperf [-s|-c host] [options]
func (s *IPerf) parseOptions(reqData any) (string, error) {
msgByte, _ := json.Marshal(reqData)
var data struct {
Command string `json:"command"` // 命令字符串
Version string `json:"version"` // 服务版本默认V3
Mode string `json:"mode"` // 服务端或客户端默认客户端client
Host string `json:"host"` // 客户端连接到的服务端IP地址
// Server or Client
Port int `json:"port"` // 服务端口
Interval int `json:"interval"` // 每次报告之间的时间间隔,单位为秒
// Server
OneOff bool `json:"oneOff"` // 只进行一次连接
// Client
UDP bool `json:"udp"` // use UDP rather than TCP
Time int `json:"time"` // 以秒为单位的传输时间(默认为 10 秒)
Reverse bool `json:"reverse"` // 以反向模式运行(服务器发送,客户端接收)
Window string `json:"window"` // 设置窗口大小/套接字缓冲区大小
Parallel int `json:"parallel"` // 运行的并行客户端流数量
Bitrate int `json:"bitrate"` // 以比特/秒为单位0 表示无限制)
}
if err := json.Unmarshal(msgByte, &data); err != nil {
logger.Warnf("ws processor parseClient err: %s", err.Error())
return "", fmt.Errorf("query data structure error")
}
if data.Version != "V3" && data.Version != "V2" {
return "", fmt.Errorf("query data version support V3 or V2")
}
command := []string{"iperf3"}
if data.Version == "V2" {
command = []string{"iperf"}
}
// 命令字符串高优先级
if data.Command != "" {
command = append(command, data.Command)
command = append(command, "\n")
return strings.Join(command, " "), nil
}
if data.Mode != "client" && data.Mode != "server" {
return "", fmt.Errorf("query data mode support client or server")
}
if data.Mode == "client" && data.Host == "" {
return "", fmt.Errorf("query data client host empty")
}
if data.Mode == "client" {
command = append(command, "-c")
command = append(command, data.Host)
// Client
if data.UDP {
command = append(command, "-u")
}
if data.Time > 0 {
command = append(command, fmt.Sprintf("-t %d", data.Time))
}
if data.Bitrate > 0 {
command = append(command, fmt.Sprintf("-b %d", data.Bitrate))
}
if data.Parallel > 0 {
command = append(command, fmt.Sprintf("-P %d", data.Parallel))
}
if data.Reverse {
command = append(command, "-R")
}
if data.Window != "" {
command = append(command, fmt.Sprintf("-w %s", data.Window))
}
}
if data.Mode == "server" {
command = append(command, "-s")
// Server
if data.OneOff {
command = append(command, "-1")
}
}
// Server or Client
if data.Port > 0 {
command = append(command, fmt.Sprintf("-p %d", data.Port))
}
if data.Interval > 0 {
command = append(command, fmt.Sprintf("-i %d", data.Interval))
}
command = append(command, "\n")
return strings.Join(command, " "), nil
}