feat: 添加UPF N3 Ping测试功能
This commit is contained in:
@@ -4,7 +4,9 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
)
|
||||
@@ -50,6 +52,57 @@ func (s *SSHClientSession) Read() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// RunCMD 执行单次命令
|
||||
func (s *SSHClientSession) RunCMD(cmd string) (string, error) {
|
||||
// 写入命令
|
||||
if cmd != "" {
|
||||
if _, err := s.Write(cmd + "\n"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// 超时退出 120s
|
||||
timeoutTicker := time.NewTicker(120 * time.Second)
|
||||
defer timeoutTicker.Stop()
|
||||
// 实时读取SSH消息直接输出
|
||||
msTicker := time.NewTicker(100 * time.Millisecond)
|
||||
defer msTicker.Stop()
|
||||
// 消息缓冲区
|
||||
var buf bytes.Buffer
|
||||
defer buf.Reset()
|
||||
for {
|
||||
select {
|
||||
case <-timeoutTicker.C:
|
||||
return "", fmt.Errorf("ssh session timeout")
|
||||
case <-msTicker.C:
|
||||
outputByte := s.Read()
|
||||
if len(outputByte) <= 0 {
|
||||
continue
|
||||
}
|
||||
outputStr := string(outputByte)
|
||||
buf.WriteString(outputStr)
|
||||
|
||||
// 命令终止符后继续执行命令
|
||||
// "~]# ":麒麟, "~]$ ":欧拉, "~# ":NXP, "~$ ":Ubuntu
|
||||
suffixStr := []string{"~]# ", "~]$ ", "~# ", "~$ "}
|
||||
suffix := false
|
||||
for _, v := range suffixStr {
|
||||
if strings.HasSuffix(outputStr, v) {
|
||||
suffix = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !suffix {
|
||||
suffix = strings.LastIndex(outputStr, "# ") != -1 // 特殊内容中的终端终止符
|
||||
}
|
||||
if suffix {
|
||||
return buf.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// singleWriter SSH客户端会话消息
|
||||
type singleWriter struct {
|
||||
b bytes.Buffer
|
||||
|
||||
@@ -223,3 +223,38 @@ func (s *PingController) Run(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RTT 时延抖动
|
||||
//
|
||||
// POST /rtt
|
||||
func (s PingController) RTT(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
var body struct {
|
||||
NeType string `json:"neType" binding:"required"` // 网元类型
|
||||
NeId string `json:"neId" binding:"required"` // 网元ID
|
||||
Ping model.Ping `json:"ping" binding:"required"` // 参数
|
||||
}
|
||||
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
|
||||
c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
|
||||
return
|
||||
}
|
||||
if body.Ping.DesAddr == "" {
|
||||
c.JSON(400, result.CodeMsg(400, "ping desAddr is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// 网元主机的SSH客户端
|
||||
sshClient, err := neService.NewNeInfo.NeRunSSHClient(body.NeType, body.NeId)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
defer sshClient.Close()
|
||||
|
||||
output, err := s.pingService.PingRTT(sshClient, body.Ping)
|
||||
if err != nil {
|
||||
c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error())))
|
||||
return
|
||||
}
|
||||
c.JSON(200, result.OkData(output))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type Ping struct {
|
||||
}
|
||||
|
||||
// setDefaultValue 设置默认值
|
||||
func (p *Ping) setDefaultValue() {
|
||||
func (p *Ping) SetDefaultValue() {
|
||||
if p.Interval < 1 || p.Interval > 10 {
|
||||
p.Interval = 1
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (p *Ping) setDefaultValue() {
|
||||
|
||||
// NewPinger ping对象
|
||||
func (p *Ping) NewPinger() (*probing.Pinger, error) {
|
||||
p.setDefaultValue()
|
||||
p.SetDefaultValue()
|
||||
|
||||
pinger, err := probing.NewPinger(p.DesAddr)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -259,3 +261,90 @@ func (s *Ping) parseOptions(reqData any) (string, error) {
|
||||
command = append(command, "\n")
|
||||
return strings.Join(command, " "), nil
|
||||
}
|
||||
|
||||
// PingRTT 时延抖动
|
||||
func (s *Ping) PingRTT(sshClient *ssh.ConnSSH, ping model.Ping) (map[string]any, error) {
|
||||
ping.SetDefaultValue()
|
||||
// ssh连接会话
|
||||
clientSession, err := sshClient.NewClientSession(128, 128)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer clientSession.Close()
|
||||
clientSession.RunCMD("") // 排空信息
|
||||
|
||||
// 命令字符串
|
||||
command := []string{"ping"}
|
||||
// Options
|
||||
if ping.Interval > 0 {
|
||||
command = append(command, fmt.Sprintf("-i %d", ping.Interval))
|
||||
}
|
||||
if ping.TTL > 0 {
|
||||
command = append(command, fmt.Sprintf("-t %d", ping.TTL))
|
||||
}
|
||||
if ping.Count > 0 {
|
||||
command = append(command, fmt.Sprintf("-c %d", ping.Count))
|
||||
}
|
||||
if ping.Size > 0 {
|
||||
command = append(command, fmt.Sprintf("-s %d", ping.Size))
|
||||
}
|
||||
if ping.Timeout > 0 {
|
||||
command = append(command, fmt.Sprintf("-w %d", ping.Timeout))
|
||||
}
|
||||
if ping.SrcAddr != "" {
|
||||
command = append(command, fmt.Sprintf("-I %s", ping.SrcAddr))
|
||||
}
|
||||
command = append(command, ping.DesAddr)
|
||||
|
||||
// 执行命令
|
||||
output, err := clientSession.RunCMD(strings.Join(command, " "))
|
||||
if err != nil {
|
||||
logger.Errorf("NeRunSSHCmd RunCMD %s err => %s", output, err.Error())
|
||||
return nil, fmt.Errorf("neinfo ssh run cmd err")
|
||||
}
|
||||
|
||||
packetsStr := ""
|
||||
rttStr := ""
|
||||
arr := strings.Split(output, "\r\n")
|
||||
for _, v := range arr {
|
||||
fmt.Println(v)
|
||||
if strings.Contains(v, "packets transmitted") {
|
||||
packetsStr = v
|
||||
}
|
||||
if strings.Contains(v, "rtt min/avg/max/mdev") {
|
||||
rttStr = v
|
||||
}
|
||||
}
|
||||
if len(arr) < 6 {
|
||||
return nil, fmt.Errorf("result error")
|
||||
}
|
||||
|
||||
// 定义一个 map 来存储结果
|
||||
stats := make(map[string]any)
|
||||
|
||||
if packetsStr != "" {
|
||||
// 正则表达式来提取相关字段 "5 packets transmitted, 5 received, 0% packet loss, time 4101ms"
|
||||
re := regexp.MustCompile(`(?P<transmitted>\d+) packets transmitted, (?P<received>\d+) received, (?P<loss>\d+)% packet loss, time (?P<time>\d+)ms`)
|
||||
matches := re.FindStringSubmatch(packetsStr)
|
||||
if len(matches) > 0 {
|
||||
stats["packets_transmitted"], _ = strconv.Atoi(matches[1])
|
||||
stats["packets_received"], _ = strconv.Atoi(matches[2])
|
||||
stats["packet_loss"], _ = strconv.Atoi(matches[3])
|
||||
stats["time_ms"], _ = strconv.Atoi(matches[4])
|
||||
}
|
||||
}
|
||||
|
||||
if rttStr != "" {
|
||||
// 正则表达式来提取 rtt 的数据 "rtt min/avg/max/mdev = 0.324/0.458/0.639/0.112 ms"
|
||||
re := regexp.MustCompile(`rtt min/avg/max/mdev = (?P<min>\d+\.\d+)/(?P<avg>\d+\.\d+)/(?P<max>\d+\.\d+)/(?P<mdev>\d+\.\d+)`)
|
||||
matches := re.FindStringSubmatch(rttStr)
|
||||
if len(matches) > 0 {
|
||||
stats["min_rtt"], _ = strconv.ParseFloat(matches[1], 64)
|
||||
stats["avg_rtt"], _ = strconv.ParseFloat(matches[2], 64)
|
||||
stats["max_rtt"], _ = strconv.ParseFloat(matches[3], 64)
|
||||
stats["mdev_rtt"], _ = strconv.ParseFloat(matches[4], 64)
|
||||
}
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
@@ -53,5 +53,9 @@ func Setup(router *gin.Engine) {
|
||||
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ping", collectlogs.BUSINESS_TYPE_OTHER)),
|
||||
controller.NewPing.Run,
|
||||
)
|
||||
pingGroup.POST("/rtt",
|
||||
middleware.PreAuthorize(nil),
|
||||
controller.NewPing.RTT,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user