refactor: 后端包结构重构、去除无用的文件

This commit is contained in:
meilin.huang
2022-06-02 17:41:11 +08:00
parent 51d06ab206
commit b2dc9dff0b
234 changed files with 749 additions and 816 deletions

View File

@@ -0,0 +1,186 @@
package machine
import (
"errors"
"fmt"
"mayfly-go/internal/devops/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/global"
"net"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// 客户端信息
type Cli struct {
machine *entity.Machine
// ssh客户端
client *ssh.Client
sftpClient *sftp.Client
}
// 机器客户端连接缓存45分钟内没有访问则会被关闭
var cliCache = cache.NewTimedCache(45*time.Minute, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key interface{}, value interface{}) {
value.(*Cli).Close()
})
// 是否存在指定id的客户端连接
func HasCli(machineId uint64) bool {
if _, ok := cliCache.Get(machineId); ok {
return true
}
return false
}
// 删除指定机器客户端,并关闭客户端连接
func DeleteCli(id uint64) {
cliCache.Delete(id)
}
// 从缓存中获取客户端信息,不存在则回调获取机器信息函数,并新建
func GetCli(machineId uint64, getMachine func(uint64) *entity.Machine) (*Cli, error) {
cli, err := cliCache.ComputeIfAbsent(machineId, func(key interface{}) (interface{}, error) {
c, err := newClient(getMachine(machineId))
if err != nil {
return nil, err
}
return c, nil
})
if cli != nil {
return cli.(*Cli), err
}
return nil, err
}
//根据机器信息创建客户端对象
func newClient(machine *entity.Machine) (*Cli, error) {
if machine == nil {
return nil, errors.New("机器不存在")
}
global.Log.Infof("[%s]机器连接:%s:%d", machine.Name, machine.Ip, machine.Port)
cli := new(Cli)
cli.machine = machine
err := cli.connect()
if err != nil {
return nil, err
}
return cli, nil
}
//连接
func (c *Cli) connect() error {
// 如果已经有client则直接返回
if c.client != nil {
return nil
}
m := c.machine
config := ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
sshClient, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return err
}
c.client = sshClient
return nil
}
// 测试连接
func TestConn(m *entity.Machine) error {
config := ssh.ClientConfig{
User: m.Username,
Auth: []ssh.AuthMethod{ssh.Password(m.Password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 5 * time.Second,
}
addr := fmt.Sprintf("%s:%d", m.Ip, m.Port)
sshClient, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return err
}
defer sshClient.Close()
return nil
}
// 关闭client和并从缓存中移除
func (c *Cli) Close() {
m := c.machine
global.Log.Info(fmt.Sprintf("关闭机器客户端连接-> id: %d, name: %s, ip: %s", m.Id, m.Name, m.Ip))
if c.client != nil {
c.client.Close()
c.client = nil
}
if c.sftpClient != nil {
c.sftpClient.Close()
c.sftpClient = nil
}
}
// 获取sftp client
func (c *Cli) GetSftpCli() *sftp.Client {
if c.client == nil {
if err := c.connect(); err != nil {
panic(biz.NewBizErr("连接ssh失败" + err.Error()))
}
}
sftpclient := c.sftpClient
// 如果sftpClient为nil则连接
if sftpclient == nil {
sc, serr := sftp.NewClient(c.client)
if serr != nil {
panic(biz.NewBizErr("获取sftp client失败" + serr.Error()))
}
sftpclient = sc
c.sftpClient = sftpclient
}
return sftpclient
}
// 获取session
func (c *Cli) GetSession() (*ssh.Session, error) {
if c.client == nil {
if err := c.connect(); err != nil {
return nil, err
}
}
return c.client.NewSession()
}
//执行shell
//@param shell shell脚本命令
func (c *Cli) Run(shell string) (*string, error) {
session, err := c.GetSession()
if err != nil {
c.Close()
return nil, err
}
defer session.Close()
buf, rerr := session.CombinedOutput(shell)
if rerr != nil {
return nil, rerr
}
res := string(buf)
return &res, nil
}
func (c *Cli) GetMachine() *entity.Machine {
return c.machine
}

View File

@@ -0,0 +1,143 @@
package machine
import (
"fmt"
"mayfly-go/pkg/utils"
"strings"
"testing"
)
func TestSSH(t *testing.T) {
//ssh.ListenAndServe("148.70.36.197")
//cli := New("148.70.36.197", "root", "g..91mn#", 22)
////output, err := cli.Run("free -h")
////fmt.Printf("%v\n%v", output, err)
//err := cli.RunTerminal("tail -f /usr/local/java/logs/eatlife-info.log", os.Stdout, os.Stdin)
//fmt.Println(err)
res := "top - 17:14:07 up 5 days, 6:30, 2 users, load average: 0.03, 0.04, 0.05\nTasks: 101 total, 1 running, 100 sleeping, 0 stopped, 0 zombie\n%Cpu(s): 6.2 us, 0.0 sy, 0.0 ni, 93.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st\nKiB Mem : 1882012 total, 73892 free, 770360 used, 1037760 buff/cache\nKiB Swap: 0 total, 0 free, 0 used. 933492 avail Mem"
split := strings.Split(res, "\n")
//var firstLine string
//for i := 0; i < len(split); i++ {
// if i == 0 {
// val := strings.Split(split[i], "top -")[1]
// vals := strings.Split(val, ",")
//
// }
//}
firstLine := strings.Split(strings.Split(split[0], "top -")[1], ",")
// 17:14:07 up 5 days
up := strings.Trim(strings.Split(firstLine[0], "up")[1], " ") + firstLine[1]
// 2 users
users := strings.Split(strings.Trim(firstLine[2], " "), " ")[0]
// load average: 0.03
oneMinLa := strings.Trim(strings.Split(strings.Trim(firstLine[3], " "), ":")[1], " ")
fiveMinLa := strings.Trim(firstLine[4], " ")
fietMinLa := strings.Trim(firstLine[5], " ")
fmt.Println(firstLine, up, users, oneMinLa, fiveMinLa, fietMinLa)
tasks := Parse(strings.Split(split[1], "Tasks:")[1])
cpu := Parse(strings.Split(split[2], "%Cpu(s):")[1])
mem := Parse(strings.Split(split[3], "KiB Mem :")[1])
fmt.Println(tasks, cpu, mem)
}
func Parse(val string) map[string]string {
res := make(map[string]string)
vals := strings.Split(val, ",")
for i := 0; i < len(vals); i++ {
trimData := strings.Trim(vals[i], " ")
keyValue := strings.Split(trimData, " ")
res[keyValue[1]] = keyValue[0]
}
return res
}
func TestTemplateRev(t *testing.T) {
temp := "hello my name is {name} hahahaha lihaiba {age} years old {public}"
str := "hello my name is hmlhmlhm 慌慌信息 hahahaha lihaiba 15 years old private protected"
//temp1 := " top - {up}, {users} users, load average: {loadavg}"
//str1 := " top - 17:14:07 up 5 days, 6:30, 2 users, load average: 0.03, 0.04, 0.05"
//taskTemp := "Tasks: {total} total, {running} running, {sleeping} sleeping, {stopped} stopped, {zombie} zombie"
//taskVal := "Tasks: 101 total, 1 running, 100 sleeping, 0 stopped, 0 zombie"
//nameRunne := []rune(str)
//index := strings.Index(temp, "{")
//ei := strings.Index(temp, "}") + 1
//next := temp[ei:]
//key := temp[index+1 : ei-1]
//value := SubString(str, index, UnicodeIndex(str, next))
res := make(map[string]interface{})
utils.ReverStrTemplate(temp, str, res)
fmt.Println(res)
}
//func ReverStrTemplate(temp, str string, res map[string]string) {
// index := UnicodeIndex(temp, "{")
// ei := UnicodeIndex(temp, "}") + 1
// next := temp[ei:]
// nextContain := UnicodeIndex(next, "{")
// nextIndexValue := next
// if nextContain != -1 {
// nextIndexValue = SubString(next, 0, nextContain)
// }
// key := temp[index+1 : ei-1]
// // 如果后面没有内容了,则取字符串的长度即可
// var valueLastIndex int
// if nextIndexValue == "" {
// valueLastIndex = StrLen(str)
// } else {
// valueLastIndex = UnicodeIndex(str, nextIndexValue)
// }
// value := SubString(str, index, valueLastIndex)
// res[key] = value
//
// if nextContain != -1 {
// ReverStrTemplate(next, SubString(str, UnicodeIndex(str, value)+StrLen(value), StrLen(str)), res)
// }
//}
//
//func StrLen(str string) int {
// return len([]rune(str))
//}
//
//func SubString(str string, begin, end int) (substr string) {
// // 将字符串的转换成[]rune
// rs := []rune(str)
// lth := len(rs)
//
// // 简单的越界判断
// if begin < 0 {
// begin = 0
// }
// if begin >= lth {
// begin = lth
// }
// if end > lth {
// end = lth
// }
//
// // 返回子串
// return string(rs[begin:end])
//}
//
//func UnicodeIndex(str, substr string) int {
// // 子串在字符串的字节位置
// result := strings.Index(str, substr)
// if result >= 0 {
// // 获得子串之前的字符串并转换成[]byte
// prefix := []byte(str)[0:result]
// // 将子串之前的字符串转换成[]rune
// rs := []rune(string(prefix))
// // 获得子串之前的字符串的长度,便是子串在字符串的字符位置
// result = len(rs)
// }
//
// return result
//}
func TestTerminal(t *testing.T) {
// ioutil.ReadAll(file)
}

View File

@@ -0,0 +1,19 @@
package machine
const StatsShell = `
cat /proc/uptime
echo '-----'
/bin/hostname -f
echo '-----'
cat /proc/loadavg
echo '-----'
cat /proc/meminfo
echo '-----'
df -B1
echo '-----'
/sbin/ip -o addr
echo '-----'
/bin/cat /proc/net/dev
echo '-----'
top -b -n 1 | grep Cpu
`

View File

@@ -0,0 +1,277 @@
package machine
import (
"bufio"
"fmt"
"strconv"
"strings"
"time"
)
type FSInfo struct {
MountPoint string
Used uint64
Free uint64
}
type NetIntfInfo struct {
IPv4 string
IPv6 string
Rx uint64
Tx uint64
}
type CPUInfo struct {
User float32
Nice float32
System float32
Idle float32
Iowait float32
Irq float32
SoftIrq float32
Steal float32
Guest float32
}
type Stats struct {
Uptime string
Hostname string
Load1 string
Load5 string
Load10 string
RunningProcs string
TotalProcs string
MemTotal uint64
MemFree uint64
MemBuffers uint64
MemAvailable uint64
MemCached uint64
SwapTotal uint64
SwapFree uint64
FSInfos []FSInfo
NetIntf map[string]NetIntfInfo
CPU CPUInfo // or []CPUInfo to get all the cpu-core's stats?
}
func (c *Cli) GetAllStats() *Stats {
res, _ := c.Run(StatsShell)
infos := strings.Split(*res, "-----")
stats := new(Stats)
getUptime(infos[0], stats)
getHostname(infos[1], stats)
getLoad(infos[2], stats)
getMemInfo(infos[3], stats)
getFSInfo(infos[4], stats)
getInterfaces(infos[5], stats)
getInterfaceInfo(infos[6], stats)
getCPU(infos[7], stats)
return stats
}
func getUptime(uptime string, stats *Stats) (err error) {
parts := strings.Fields(uptime)
if len(parts) == 2 {
var upsecs float64
upsecs, err = strconv.ParseFloat(parts[0], 64)
if err != nil {
return
}
stats.Uptime = fmtUptime(time.Duration(upsecs * 1e9))
}
return
}
func fmtUptime(dur time.Duration) string {
dur = dur - (dur % time.Second)
var days int
for dur.Hours() > 24.0 {
days++
dur -= 24 * time.Hour
}
s1 := dur.String()
s2 := ""
if days > 0 {
s2 = fmt.Sprintf("%dd ", days)
}
for _, ch := range s1 {
s2 += string(ch)
if ch == 'h' || ch == 'm' {
s2 += " "
}
}
return s2
}
func getHostname(hostname string, stats *Stats) (err error) {
stats.Hostname = strings.TrimSpace(hostname)
return
}
func getLoad(loadInfo string, stats *Stats) (err error) {
parts := strings.Fields(loadInfo)
if len(parts) == 5 {
stats.Load1 = parts[0]
stats.Load5 = parts[1]
stats.Load10 = parts[2]
if i := strings.Index(parts[3], "/"); i != -1 {
stats.RunningProcs = parts[3][0:i]
if i+1 < len(parts[3]) {
stats.TotalProcs = parts[3][i+1:]
}
}
}
return
}
func getMemInfo(memInfo string, stats *Stats) (err error) {
// "/bin/cat /proc/meminfo"
scanner := bufio.NewScanner(strings.NewReader(memInfo))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 3 {
val, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
continue
}
val *= 1024
switch parts[0] {
case "MemTotal:":
stats.MemTotal = val
case "MemFree:":
stats.MemFree = val
case "Buffers:":
stats.MemBuffers = val
case "Cached:":
stats.MemCached = val
case "SwapTotal:":
stats.SwapTotal = val
case "SwapFree:":
stats.SwapFree = val
case "MemAvailable:":
stats.MemAvailable = val
}
}
}
return
}
func getFSInfo(fsInfo string, stats *Stats) (err error) {
// "/bin/df -B1"
scanner := bufio.NewScanner(strings.NewReader(fsInfo))
flag := 0
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
n := len(parts)
dev := n > 0 && strings.Index(parts[0], "/dev/") == 0
if n == 1 && dev {
flag = 1
} else if (n == 5 && flag == 1) || (n == 6 && dev) {
i := flag
flag = 0
used, err := strconv.ParseUint(parts[2-i], 10, 64)
if err != nil {
continue
}
free, err := strconv.ParseUint(parts[3-i], 10, 64)
if err != nil {
continue
}
stats.FSInfos = append(stats.FSInfos, FSInfo{
parts[5-i], used, free,
})
}
}
return
}
func getInterfaces(iInfo string, stats *Stats) (err error) {
// "/sbin/ip -o addr"
if stats.NetIntf == nil {
stats.NetIntf = make(map[string]NetIntfInfo)
}
scanner := bufio.NewScanner(strings.NewReader(iInfo))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) >= 4 && (parts[2] == "inet" || parts[2] == "inet6") {
ipv4 := parts[2] == "inet"
intfname := parts[1]
if info, ok := stats.NetIntf[intfname]; ok {
if ipv4 {
info.IPv4 = parts[3]
} else {
info.IPv6 = parts[3]
}
stats.NetIntf[intfname] = info
} else {
info := NetIntfInfo{}
if ipv4 {
info.IPv4 = parts[3]
} else {
info.IPv6 = parts[3]
}
stats.NetIntf[intfname] = info
}
}
}
return
}
func getInterfaceInfo(iInfo string, stats *Stats) (err error) {
// /bin/cat /proc/net/dev
if stats.NetIntf == nil {
return
} // should have been here already
scanner := bufio.NewScanner(strings.NewReader(iInfo))
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) == 17 {
intf := strings.TrimSpace(parts[0])
intf = strings.TrimSuffix(intf, ":")
if info, ok := stats.NetIntf[intf]; ok {
rx, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
continue
}
tx, err := strconv.ParseUint(parts[9], 10, 64)
if err != nil {
continue
}
info.Rx = rx
info.Tx = tx
stats.NetIntf[intf] = info
}
}
}
return
}
func getCPU(cpuInfo string, stats *Stats) (err error) {
// %Cpu(s): 6.1 us, 3.0 sy, 0.0 ni, 90.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
value := strings.Split(cpuInfo, ":")[1]
values := strings.Split(value, ",")
us, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[0]), " ")[0], 32)
stats.CPU.User = float32(us)
sy, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[1]), " ")[0], 32)
stats.CPU.System = float32(sy)
id, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[3]), " ")[0], 32)
stats.CPU.Idle = float32(id)
wa, _ := strconv.ParseFloat(strings.Split(strings.TrimSpace(values[4]), " ")[0], 32)
stats.CPU.Iowait = float32(wa)
return nil
}

View File

@@ -0,0 +1,195 @@
package machine
import (
"bytes"
"encoding/json"
"io"
"mayfly-go/pkg/global"
"sync"
"time"
"github.com/gorilla/websocket"
"golang.org/x/crypto/ssh"
)
type safeBuffer struct {
buffer bytes.Buffer
mu sync.Mutex
}
func (w *safeBuffer) Write(p []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.Write(p)
}
func (w *safeBuffer) Bytes() []byte {
w.mu.Lock()
defer w.mu.Unlock()
return w.buffer.Bytes()
}
func (w *safeBuffer) Reset() {
w.mu.Lock()
defer w.mu.Unlock()
w.buffer.Reset()
}
const (
wsMsgCmd = "cmd"
wsMsgResize = "resize"
)
type WsMsg struct {
Type string `json:"type"`
Msg string `json:"msg"`
Cols int `json:"cols"`
Rows int `json:"rows"`
}
type LogicSshWsSession struct {
stdinPipe io.WriteCloser
comboOutput *safeBuffer //ssh 终端混合输出
inputFilterBuff *safeBuffer //用来过滤输入的命令和ssh_filter配置对比的
session *ssh.Session
wsConn *websocket.Conn
}
func NewLogicSshWsSession(cols, rows int, cli *Cli, wsConn *websocket.Conn) (*LogicSshWsSession, error) {
sshSession, err := cli.GetSession()
if err != nil {
return nil, err
}
stdinP, err := sshSession.StdinPipe()
if err != nil {
return nil, err
}
comboWriter := new(safeBuffer)
inputBuf := new(safeBuffer)
//ssh.stdout and stderr will write output into comboWriter
sshSession.Stdout = comboWriter
sshSession.Stderr = comboWriter
modes := ssh.TerminalModes{
ssh.ECHO: 1, // disable echo
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
// Request pseudo terminal
if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil {
return nil, err
}
// Start remote shell
if err := sshSession.Shell(); err != nil {
return nil, err
}
return &LogicSshWsSession{
stdinPipe: stdinP,
comboOutput: comboWriter,
inputFilterBuff: inputBuf,
session: sshSession,
wsConn: wsConn,
}, nil
}
//Close 关闭
func (sws *LogicSshWsSession) Close() {
if sws.session != nil {
sws.session.Close()
}
if sws.comboOutput != nil {
sws.comboOutput = nil
}
}
func (sws *LogicSshWsSession) Start(quitChan chan bool) {
go sws.receiveWsMsg(quitChan)
go sws.sendComboOutput(quitChan)
}
//receiveWsMsg receive websocket msg do some handling then write into ssh.session.stdin
func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) {
wsConn := sws.wsConn
//tells other go routine quit
defer setQuit(exitCh)
for {
select {
case <-exitCh:
return
default:
//read websocket msg
_, wsData, err := wsConn.ReadMessage()
if err != nil {
if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseNoStatusReceived) {
return
}
global.Log.Error("reading webSocket message failed: ", err)
return
}
//unmashal bytes into struct
msgObj := WsMsg{}
if err := json.Unmarshal(wsData, &msgObj); err != nil {
global.Log.Error("unmarshal websocket message failed", err)
}
switch msgObj.Type {
case wsMsgResize:
//handle xterm.js size change
if msgObj.Cols > 0 && msgObj.Rows > 0 {
if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil {
global.Log.Error("ssh pty change windows size failed")
}
}
case wsMsgCmd:
sws.sendWebsocketInputCommandToSshSessionStdinPipe([]byte(msgObj.Msg))
}
}
}
}
//sendWebsocketInputCommandToSshSessionStdinPipe
func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
if _, err := sws.stdinPipe.Write(cmdBytes); err != nil {
global.Log.Error("ws cmd bytes write to ssh.stdin pipe failed")
}
}
func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) {
wsConn := sws.wsConn
//todo 优化成一个方法
//tells other go routine quit
defer setQuit(exitCh)
//every 120ms write combine output bytes into websocket response
tick := time.NewTicker(time.Millisecond * time.Duration(60))
//for range time.Tick(120 * time.Millisecond){}
defer tick.Stop()
for {
select {
case <-tick.C:
if sws.comboOutput == nil {
return
}
bs := sws.comboOutput.Bytes()
if len(bs) > 0 {
err := wsConn.WriteMessage(websocket.TextMessage, bs)
if err != nil {
global.Log.Error("ssh sending combo output to webSocket failed")
}
sws.comboOutput.buffer.Reset()
}
case <-exitCh:
return
}
}
}
func (sws *LogicSshWsSession) Wait(quitChan chan bool) {
if err := sws.session.Wait(); err != nil {
setQuit(quitChan)
}
}
func setQuit(ch chan bool) {
ch <- true
}