feat: 添加UPF N3 Ping测试功能
This commit is contained in:
@@ -4,7 +4,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
gossh "golang.org/x/crypto/ssh"
|
gossh "golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
@@ -50,6 +52,57 @@ func (s *SSHClientSession) Read() []byte {
|
|||||||
return []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客户端会话消息
|
// singleWriter SSH客户端会话消息
|
||||||
type singleWriter struct {
|
type singleWriter struct {
|
||||||
b bytes.Buffer
|
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 设置默认值
|
// setDefaultValue 设置默认值
|
||||||
func (p *Ping) setDefaultValue() {
|
func (p *Ping) SetDefaultValue() {
|
||||||
if p.Interval < 1 || p.Interval > 10 {
|
if p.Interval < 1 || p.Interval > 10 {
|
||||||
p.Interval = 1
|
p.Interval = 1
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ func (p *Ping) setDefaultValue() {
|
|||||||
|
|
||||||
// NewPinger ping对象
|
// NewPinger ping对象
|
||||||
func (p *Ping) NewPinger() (*probing.Pinger, error) {
|
func (p *Ping) NewPinger() (*probing.Pinger, error) {
|
||||||
p.setDefaultValue()
|
p.SetDefaultValue()
|
||||||
|
|
||||||
pinger, err := probing.NewPinger(p.DesAddr)
|
pinger, err := probing.NewPinger(p.DesAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -259,3 +261,90 @@ func (s *Ping) parseOptions(reqData any) (string, error) {
|
|||||||
command = append(command, "\n")
|
command = append(command, "\n")
|
||||||
return strings.Join(command, " "), nil
|
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)),
|
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.ping", collectlogs.BUSINESS_TYPE_OTHER)),
|
||||||
controller.NewPing.Run,
|
controller.NewPing.Run,
|
||||||
)
|
)
|
||||||
|
pingGroup.POST("/rtt",
|
||||||
|
middleware.PreAuthorize(nil),
|
||||||
|
controller.NewPing.RTT,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user