From e87e5c3664350d3e1bb02d0c889e12faa2db63c7 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Fri, 12 Apr 2024 17:26:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BD=91=E5=85=83=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E5=8D=87=E7=BA=A7=E5=92=8C=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network_element/controller/ne_version.go | 32 ++++ .../network_element/network_element.go | 5 + .../repository/ne_version.impl.go | 23 ++- .../network_element/service/ne_version.go | 5 + .../service/ne_version.impl.go | 148 ++++++++++++++++++ 5 files changed, 212 insertions(+), 1 deletion(-) diff --git a/src/modules/network_element/controller/ne_version.go b/src/modules/network_element/controller/ne_version.go index 5fab991e..06e8cbc8 100644 --- a/src/modules/network_element/controller/ne_version.go +++ b/src/modules/network_element/controller/ne_version.go @@ -150,3 +150,35 @@ func (s *NeVersionController) Remove(c *gin.Context) { msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows}) c.JSON(200, result.OkMsg(msg)) } + +// 网元版本操作 +// +// POST /operate +func (s *NeVersionController) Operate(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body struct { + Action string `json:"action" binding:"required,oneof=upgrade rollback"` // 操作行为 + NeType string `json:"neType" gorm:"ne_type" binding:"required"` // 网元类型 + NeId string `json:"neId" gorm:"ne_id" binding:"required"` // 网元ID + Preinput map[string]string `json:"preinput" ` // 预先输入参数 + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + neVersion := s.neVersionService.SelectByNeTypeAndNeID(body.NeType, body.NeId) + if neVersion.NeId != body.NeId { + // 没有可访问网元版本数据! + c.JSON(200, result.ErrMsg(i18n.TKey(language, "neVersion.noData"))) + return + } + + // 进行相关命令操作 + output, err := s.neVersionService.Operate(body.Action, neVersion, body.Preinput) + if err != nil { + c.JSON(200, result.ErrMsg(i18n.TKey(language, err.Error()))) + return + } + c.JSON(200, result.OkData(output)) +} diff --git a/src/modules/network_element/network_element.go b/src/modules/network_element/network_element.go index cd5b8bcc..fc4ad1aa 100644 --- a/src/modules/network_element/network_element.go +++ b/src/modules/network_element/network_element.go @@ -188,6 +188,11 @@ func Setup(router *gin.Engine) { collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neVersion", collectlogs.BUSINESS_TYPE_DELETE)), controller.NewNeVersion.Remove, ) + neVersionGroup.POST("/operate", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.neVersion", collectlogs.BUSINESS_TYPE_OTHER)), + controller.NewNeVersion.Operate, + ) } // 网元软件包信息 diff --git a/src/modules/network_element/repository/ne_version.impl.go b/src/modules/network_element/repository/ne_version.impl.go index 5991abd8..0bcfe659 100644 --- a/src/modules/network_element/repository/ne_version.impl.go +++ b/src/modules/network_element/repository/ne_version.impl.go @@ -15,17 +15,20 @@ import ( // 实例化数据层 NewNeVersion 结构体 var NewNeVersionImpl = &NeVersionImpl{ selectSql: `select - id, ne_type, ne_id, version, path, pre_version, pre_path, new_version, new_path, status, create_by, create_time, update_by, update_time + id, ne_type, ne_id, name, version, path, pre_name, pre_version, pre_path, new_name, new_version, new_path, status, create_by, create_time, update_by, update_time from ne_version`, resultMap: map[string]string{ "id": "ID", "ne_type": "NeType", "ne_id": "NeId", + "name": "name", "version": "Version", "path": "Path", + "pre_name": "preName", "pre_version": "PreVersion", "pre_path": "PrePath", + "new_name": "NewName", "new_version": "NewVersion", "new_path": "NewPath", "status": "Status", @@ -223,18 +226,27 @@ func (r *NeVersionImpl) Insert(neVersion model.NeVersion) string { if neVersion.NeId != "" { params["ne_id"] = neVersion.NeId } + if neVersion.Name != "" { + params["name"] = neVersion.Name + } if neVersion.Version != "" { params["version"] = neVersion.Version } if neVersion.Path != "" { params["path"] = neVersion.Path } + if neVersion.PreName != "" { + params["pre_name"] = neVersion.PreName + } if neVersion.PreVersion != "" { params["pre_version"] = neVersion.PreVersion } if neVersion.PrePath != "" { params["pre_path"] = neVersion.PrePath } + if neVersion.NewName != "" { + params["new_name"] = neVersion.NewName + } if neVersion.NewVersion != "" { params["new_version"] = neVersion.NewVersion } @@ -286,18 +298,27 @@ func (r *NeVersionImpl) Update(neVersion model.NeVersion) int64 { if neVersion.NeId != "" { params["ne_id"] = neVersion.NeId } + if neVersion.Name != "" { + params["name"] = neVersion.Name + } if neVersion.Version != "" { params["version"] = neVersion.Version } if neVersion.Path != "" { params["path"] = neVersion.Path } + if neVersion.PreName != "" { + params["pre_name"] = neVersion.PreName + } if neVersion.PreVersion != "" { params["pre_version"] = neVersion.PreVersion } if neVersion.PrePath != "" { params["pre_path"] = neVersion.PrePath } + if neVersion.NewName != "" { + params["new_name"] = neVersion.NewName + } if neVersion.NewVersion != "" { params["new_version"] = neVersion.NewVersion } diff --git a/src/modules/network_element/service/ne_version.go b/src/modules/network_element/service/ne_version.go index 5e86b679..09090f9f 100644 --- a/src/modules/network_element/service/ne_version.go +++ b/src/modules/network_element/service/ne_version.go @@ -27,4 +27,9 @@ type INeVersion interface { // CheckUniqueTypeAndID 校验网元类型和网元ID是否唯一 CheckUniqueTypeAndID(neType, neId, id string) bool + + // Operate 操作版本上传到网元主机执行命令 + // + // action 安装行为:upgrade rollback + Operate(action string, neVersion model.NeVersion, preinput map[string]string) (string, error) } diff --git a/src/modules/network_element/service/ne_version.impl.go b/src/modules/network_element/service/ne_version.impl.go index 5e2df893..018d3d36 100644 --- a/src/modules/network_element/service/ne_version.impl.go +++ b/src/modules/network_element/service/ne_version.impl.go @@ -2,7 +2,12 @@ package service import ( "fmt" + "os" + "path/filepath" + "strings" + "time" + "be.ems/src/framework/utils/file" "be.ems/src/modules/network_element/model" "be.ems/src/modules/network_element/repository" ) @@ -89,3 +94,146 @@ func (r *NeVersionImpl) CheckUniqueTypeAndID(neType, neId, id string) bool { } return uniqueId == "" } + +// Operate 操作版本上传到网元主机执行命令 +// +// action 安装行为:upgrade rollback +func (r *NeVersionImpl) Operate(action string, neVersion model.NeVersion, preinput map[string]string) (string, error) { + softwarePath := neVersion.Path + if action == "upgrade" { + softwarePath = neVersion.NewPath + } + if action == "rollback" { + softwarePath = neVersion.PrePath + } + // 检查文件是否存在 + localFilePath := file.ParseUploadFilePath(softwarePath) + if _, err := os.Stat(localFilePath); err != nil { + return "", fmt.Errorf("file read failure") + } + fileName := filepath.Base(softwarePath) + if strings.Contains(fileName, "*") { + fileName = strings.ReplaceAll(fileName, "*", "_") + } + nePath := "/tmp" + neFilePath := fmt.Sprintf("%s/%s", nePath, fileName) + + // 网元主机的SSH客户端 + sshClient, err := NewNeInfoImpl.NeRunSSHclient(neVersion.NeType, neVersion.NeId) + if err != nil { + return "", err + } + defer sshClient.Close() + // 网元主机的SSH客户端进行文件传输 + sftpClient, err := sshClient.NewClientSFTP() + if err != nil { + return "", err + } + defer sftpClient.Close() + + // 上传软件包到 /tmp + if err = sftpClient.CopyFileLocalToRemote(localFilePath, neFilePath); err != nil { + return "", fmt.Errorf("error uploading package") + } + + // ========= 安装命令 start ========= + // 命令终止结束标记 + okFlagStr := fmt.Sprintf("%s version %s successful!", neVersion.NeType, action) + // 安装软件包 + pkgCmdStr := fmt.Sprintf("sudo dpkg -i %s", neFilePath) + fileExt := filepath.Ext(strings.ToLower(fileName)) + if strings.HasSuffix(fileExt, "rpm") { + pkgCmdStr = fmt.Sprintf("sudo rpm -Uvh %s", neFilePath) + } + + // 预先参数 + cmdStrArr := []string{} + if neVersion.NeType == "OMC" { + cmdStrArr = append(cmdStrArr, "sudo /usr/local/omc/bin/omcsvc.sh stop") + cmdStrArr = append(cmdStrArr, pkgCmdStr) + cmdStrArr = append(cmdStrArr, "sudo /usr/local/omc/bin/omcsvc.sh restart") + return sshClient.RunCMD(fmt.Sprintf("nohup sh -c \"sleep 15s && %s\" > /dev/null 2>&1 & \n", strings.Join(cmdStrArr, " && "))) + } else if neVersion.NeType == "IMS" { + if !strings.Contains(strings.ToLower(fileName), "ims") { + return "", fmt.Errorf("error file package not ims") + } + cmdStrArr = append(cmdStrArr, "sudo ims-stop \n") + cmdStrArr = append(cmdStrArr, pkgCmdStr+" \n") + // P/I/S-CSCF Config 配置覆盖 + if pisCSCF, ok := preinput["pisCSCF"]; ok && pisCSCF != "" { + cmdStrArr = append(cmdStrArr, fmt.Sprintf("%s \n", pisCSCF)) + } + cmdStrArr = append(cmdStrArr, "sudo ims-start \n") + } else { + neTypeLower := strings.ToLower(neVersion.NeType) + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo service %s stop \n", neTypeLower)) + cmdStrArr = append(cmdStrArr, pkgCmdStr+" \n") + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo service %s restart \n", neTypeLower)) + } + + // 删除软件包 + cmdStrArr = append(cmdStrArr, fmt.Sprintf("sudo rm %s \n", neFilePath)) + // 结束 + cmdStrArr = append(cmdStrArr, fmt.Sprintf("echo '%s' \n", okFlagStr)) + // ========= 安装命令 end ========= + + // ssh连接会话 + clientSession, err := sshClient.NewClientSession(80, 24) + if err != nil { + return "", fmt.Errorf("neinfo ssh client session new err") + } + defer clientSession.Close() + + firstRead := true // 首次命令进行记录日志信息 + logMsg := "" // 日志信息 + done := make(chan bool) // 完成信号 + // 超时退出 30s + timeoutTicker := time.NewTicker(30 * time.Second) + defer timeoutTicker.Stop() + // 实时读取SSH消息直接输出 + msTicker := time.NewTicker(100 * time.Millisecond) + defer msTicker.Stop() + go func() { + for { + select { + case <-timeoutTicker.C: + done <- true + return + case <-msTicker.C: + outputByte := clientSession.Read() + if len(outputByte) > 0 { + outputStr := string(outputByte) + // 非首次进行记录命令 + if !firstRead { + logMsg += outputStr + } + // IMS预输入 + if neVersion.NeType == "IMS" && strings.Contains(outputStr, "(P/I/S-CSCF Config)? ") { + shiftElement := cmdStrArr[0] // 获取第一个元素 + cmdStrArr = cmdStrArr[1:] // 将第一个元素从切片中移除 + clientSession.Write(shiftElement) + continue + } + // 命令终止符后继续执行命令 + if len(cmdStrArr) > 0 && strings.LastIndex(outputStr, "~$ ") > 2 { + if firstRead { + firstRead = false + } + shiftElement := cmdStrArr[0] // 获取第一个元素 + cmdStrArr = cmdStrArr[1:] // 将第一个元素从切片中移除 + clientSession.Write(shiftElement) + continue + } + // 最后输出的退出标记 + if strings.LastIndex(outputStr, okFlagStr) > 5 { + done <- true + break + } + } + } + } + }() + // 等待写入协程完成 + <-done + return logMsg, nil +}