Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"apply_test.go",
"grpc_snap_test.go",
"grpc_test.go",
"label_test.go",
"mail_test.go",
"order_admin_test.go",
"order_test.go",
"rank_test.go",
"report_test.go",
"scene_test.go",
"script_test.go",
"service_test.go",
"user_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/admin/ep/melloi/conf:go_default_library",
"//app/admin/ep/melloi/model:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"apply.go",
"bfs.go",
"clientmoni.go",
"cluster.go",
"comment.go",
"dapper.go",
"file.go",
"grpc.go",
"grpc_snap.go",
"job.go",
"label.go",
"mail.go",
"order.go",
"order_admin.go",
"order_report.go",
"ptest.go",
"rank.go",
"report.go",
"scene.go",
"script.go",
"script_snap.go",
"service.go",
"tools.go",
"tree.go",
"user.go",
"wechat.go",
],
importpath = "go-common/app/admin/ep/melloi/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/admin/ep/melloi/conf:go_default_library",
"//app/admin/ep/melloi/dao:go_default_library",
"//app/admin/ep/melloi/model:go_default_library",
"//app/admin/ep/melloi/service/proto:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/http/blademaster:go_default_library",
"//vendor/github.com/jinzhu/gorm:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/robfig/cron:go_default_library",
"//vendor/gopkg.in/gomail.v2:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/admin/ep/melloi/service/proto:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,175 @@
package service
import (
"context"
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/melloi/model"
"go-common/library/ecode"
"go-common/library/log"
)
// QueryApply query apply by apply object
func (s *Service) QueryApply(qar *model.QueryApplyRequest) (*model.QueryApplyResponse, error) {
return s.dao.QueryApply(&qar.Apply, qar.PageNum, qar.PageSize)
}
// QueryUserApplyList query user apply list
func (s *Service) QueryUserApplyList(userName string) ([]*model.Apply, error) {
return s.dao.QueryUserApplyList(userName)
}
// CheckRunPermission check user run permission
func (s *Service) CheckRunPermission(userName string) (ret bool) {
var (
startTime int64
err error
endTime int64
applyList []*model.Apply
)
currentTime := time.Now().Unix()
//白名单的人不需要做check直接返回true
if ExistsInSlice(userName, s.c.Melloi.Executor) {
ret = true
return
}
if applyList, err = s.dao.QueryUserApplyList(userName); err != nil {
log.Error("s.dao.QueryUserApplyList err :(%v)", err)
return
}
for _, apply := range applyList {
if startTime, err = strconv.ParseInt(apply.StartTime, 10, 64); err != nil {
return
}
if endTime, err = strconv.ParseInt(apply.EndTime, 10, 64); err != nil {
return
}
if currentTime >= startTime && currentTime <= endTime {
ret = true
break
}
}
return ret
}
// CheckRunTime check perf time
func (s *Service) CheckRunTime() (ret bool) {
currentTime := time.Now()
// 1:30 ~ 12:00
if currentTime.Hour() >= 1 && currentTime.Hour() < 12 {
ret = true
}
// 14:00 ~ 17:00
if currentTime.Hour() >= 14 && currentTime.Hour() < 17 {
ret = true
}
return
}
// UpdateApply update apply info
func (s *Service) UpdateApply(cookie string, apply *model.Apply) (err error) {
var user *model.User
if apply.ID == 0 {
return ecode.MelloiApplyRequestErr
}
if user, err = s.QueryUser(apply.From); err != nil {
log.Error("update apply query user error:%v)", err)
return
}
if user.ID != 0 {
user.Accept = apply.Status
if err = s.dao.UpdateUser(user); err != nil {
return err
}
// 判断是审批操作,发送微信通知
if apply.Status == 1 {
applyMsg, _ := s.dao.QueryApplyByID(apply.ID)
startTime, _ := strconv.Atoi(applyMsg.StartTime)
st := time.Unix(int64(startTime), 0)
endTime, _ := strconv.Atoi(applyMsg.EndTime)
et := time.Unix(int64(endTime), 0)
content := "[MELLOI]压测申请处理完成 通知 \n 压测服务: " + applyMsg.Path + "\n" + "压测时间:" + st.Format("2006-01-02 15:04:05") + "\n" + "压测结束时间:" + et.Format("2006-01-02 15:04:05") + "\n" + "申请人:" +
applyMsg.From + "\n" + "审批人:" + applyMsg.To + "\n" + "审批时间:" + time.Now().Format("2006-01-02 15:04:05")
log.Info("content:(%s)", content)
// 发送申请通过到群
go s.AddWechatSend(context.TODO(), cookie, content)
// 给申请人发送邮件
go s.SendMail(apply.From+"@bilibili.com", "[MELLOI]压测申请通知", content)
}
return s.dao.UpdateApply(apply)
}
return ecode.MelloiUpdateUserErr
}
// AddApply add new apply
func (s *Service) AddApply(c context.Context, cookie string, apply *model.Apply) (err error) {
apply.Status = -1
apply.Active = 1
// 添加apply到db
if err = s.dao.AddApply(apply); err != nil {
return ecode.MelloiApplyRequestErr
}
// 发送微信消息 & 发送邮件通知
//加密 apply.id|apply.from|apply.to
applyID := strconv.FormatInt(apply.ID, 10)
beStr := applyID + "|" + apply.From + "|" + apply.To
base64Str := base64.StdEncoding.EncodeToString([]byte(beStr))
// 将时间戳转成日期
startTime, _ := strconv.Atoi(apply.StartTime)
st := time.Unix(int64(startTime), 0)
endTime, _ := strconv.Atoi(apply.EndTime)
et := time.Unix(int64(endTime), 0)
// 增加依赖服务列表
var (
userService map[string][]string
serviceList = make(map[string][]string)
serviceDep string
serviceName string
)
serviceName = strings.Replace(apply.Path, "bilibili.", "", 1)
if userService, err = s.QueryDependServiceAdmins(c, serviceName, s.getSessionInCookie(cookie)); err != nil {
log.Error("query depend service admin error(%v)", err)
return
}
for _, v := range userService {
for _, service := range v {
serviceList[service] = nil
}
}
for k := range serviceList {
serviceDep += "\n" + k
}
// 拼接消息体amd=base64Str
content := fmt.Sprintf("[MELLOI]压测申请处理 通知 \n 压测服务:%s\n压测开始时间段:%s\n压测结束时间段:%s\n申请人:%s\n申请时间:%s\n依赖服务:%s\n审批地址:http://melloi.bilibili.co#/apply-m?platform=mb&amd=%s",
apply.Path, st.Format("2006-01-02 15:04:05"), et.Format("2006-01-02 15:04:05"),
apply.From, time.Now().Format("2006-01-02 15:04:05"), serviceDep, base64Str)
// 消息接收人
var touser []string
touser = append(touser, apply.To)
// 发送微信
go s.dao.PushWechatMsgToPerson(context.TODO(), cookie, touser, content)
// 发送邮件
subject := "Melloi压测申请"
go s.SendMail(apply.To+"@bilibili.com", subject, content)
return
}
// DeleteApply delete apply
func (s *Service) DeleteApply(id int64) error {
return s.dao.DeleteApply(id)
}

View File

@@ -0,0 +1,47 @@
package service
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/admin/ep/melloi/model"
)
var (
apply = model.Apply{ID: 1, Path: "bilibili.test.ep.melloi", From: "hujianping",
To: "hujianping", Status: 1, Active: 1, StartTime: "1541007000", EndTime: "1541235600"}
qar = model.QueryApplyRequest{
Apply: apply,
Pagination: model.Pagination{PageNum: 1, PageSize: 1, TotalSize: 1},
}
userName = "hujianping"
cookie = "_AJSESSIONID=e2df43ed324d20811e8d1be1a9fb36d5"
)
func Test_Apply(t *testing.T) {
Convey("query apply info", t, func() {
_, err := s.QueryApply(&qar)
So(err, ShouldBeNil)
})
Convey("query user applyList", t, func() {
_, err := s.QueryUserApplyList(userName)
So(err, ShouldBeNil)
})
Convey("add apply", t, func() {
err := s.AddApply(c, cookie, &apply)
So(err, ShouldBeNil)
})
Convey("update apply", t, func() {
err := s.UpdateApply(cookie, &apply)
So(err, ShouldBeNil)
})
Convey("delete apply", t, func() {
err := s.DeleteApply(apply.ID)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,8 @@
package service
import "context"
// UploadImg upload img
func (s *Service) UploadImg(c context.Context, imgContent []byte, imgName string) (location string, err error) {
return s.dao.UploadImg(c, imgContent, imgName)
}

View File

@@ -0,0 +1,8 @@
package service
import "go-common/app/admin/ep/melloi/model"
//AddClientMoni add ClientMoni
func (s *Service) AddClientMoni(clm *model.ClientMoni) (int, error) {
return s.dao.AddClientMoni(clm)
}

View File

@@ -0,0 +1,28 @@
package service
import (
"context"
"go-common/app/admin/ep/melloi/model"
"go-common/library/ecode"
)
//RmToken Get PaaS token
func (s *Service) RmToken(c context.Context) (token string, err error) {
return s.dao.RmToken(c)
}
//ClusterInfo get melloi server use
func (s *Service) ClusterInfo(c context.Context) (firstRetMap []*model.ClusterResponseItemsSon, err error) {
var token string
if token, err = s.RmToken(c); err != nil {
//err = ecode.MelloiGetTreeTokenErr
return
}
if firstRetMap, err = s.dao.NetInfo(c, token); err != nil {
err = ecode.MerlinGetUserTreeFailed
return
}
return
}

View File

@@ -0,0 +1,29 @@
package service
import (
"time"
"go-common/app/admin/ep/melloi/model"
)
//AddComment add comment for test job
func (s *Service) AddComment(comment *model.Comment) error {
comment.Status = 1
comment.SubmitDate = time.Now()
return s.dao.AddComment(comment)
}
//QueryComment query comment
func (s *Service) QueryComment(comment *model.Comment) (*model.QueryCommentResponse, error) {
return s.dao.QueryComment(comment)
}
//UpdateComment update comment
func (s *Service) UpdateComment(comment *model.Comment) error {
return s.dao.UpdateComment(comment)
}
//DeleteComment delete comment
func (s *Service) DeleteComment(id int64) error {
return s.dao.DeleteComment(id)
}

View File

@@ -0,0 +1,43 @@
package service
import (
"context"
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
// QueryDependServiceAdmins query depend Service admin
func (s *Service) QueryDependServiceAdmins(c context.Context, serviceName string, sessionValue string) (map[string][]string, error) {
var (
biliPrex = "bilibili."
err error
roles []*model.TreeRole
dependService []string
olesMap = make(map[string][]string)
)
if dependService, err = s.dao.QueryServiceDepend(c, serviceName); err != nil {
log.Error("query service depend error(%v)", err)
return nil, err
}
dependService = append(dependService, serviceName)
for _, service := range dependService {
var userService []string
if roles, err = s.QueryTreeAdmin(c, biliPrex+service, sessionValue); err != nil {
log.Warn("query tree admin of service (%s) depend error (%v)", biliPrex+service, err)
continue
}
for _, role := range roles {
// 增加多个service
userService = append(olesMap[role.UserName], biliPrex+service)
olesMap[role.UserName] = userService
}
}
return olesMap, err
}

View File

@@ -0,0 +1,297 @@
package service
import (
"bufio"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"go-common/app/admin/ep/melloi/conf"
"go-common/app/admin/ep/melloi/model"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"github.com/pkg/errors"
)
// Upload upload file
func (s *Service) Upload(c context.Context, uploadParam *model.UploadParam, formFile multipart.File, header *multipart.FileHeader) (id int, scriptPath string, err error) {
var destFile *os.File
//创建脚本保存路径
if uploadParam.ScriptPath == "" {
if scriptPath, err = s.uniqueFolderPath(uploadParam.Path); err != nil {
return
}
} else {
scriptPath = uploadParam.ScriptPath
}
//创建保存文件
if destFile, err = os.Create(scriptPath + header.Filename); err != nil {
log.Error("Create failed,error(%v)", err)
return
}
defer destFile.Close()
// 读取表单文件,写入保存文件
if _, err = io.Copy(destFile, formFile); err != nil {
log.Error("write file error (%v)", err)
return
}
// jmx文件
if strings.Contains(header.Filename, ".jmx") {
treePath := model.TreePath{Department: uploadParam.Department, Project: uploadParam.Project, App: uploadParam.APP}
script := model.Script{
ProjectName: uploadParam.TestName,
//TestName: uploadParam.TestName,
SavePath: destFile.Name(),
UpdateBy: uploadParam.UserName,
TreePath: treePath,
Upload: true,
Active: 1,
ResJtl: scriptPath + "jtl",
JmeterLog: scriptPath + "jmg",
ScriptPath: scriptPath,
ArgumentString: "[{\"\":\"\"}]",
TestType: 1,
Domain: uploadParam.Domains,
Fusing: uploadParam.Fusing,
UseBusinessStop: uploadParam.UseBusinessStop,
BusinessStopPercent: uploadParam.BusinessStopPercent,
}
if id, _, _, err = s.dao.AddScript(&script); err != nil {
log.Error("s.dao.AddScript err :(%v)", err)
return
}
}
return
}
// UploadAndGetProtoInfo upload and get proto info
func (s *Service) UploadAndGetProtoInfo(c context.Context, uploadParam *model.UploadParam, formFile multipart.File, header *multipart.FileHeader) (res map[string]interface{}, err error) {
// 获取上传路径, Path 为root_path ,ScriptPath 为 root_path + import路径
_, scriptPath, err := s.Upload(c, uploadParam, formFile, header)
if err != nil {
log.Error("Write file failed, error(%v)", err)
return
}
// 解析proto文件
path, fileName := filepath.Split(path.Join(scriptPath, header.Filename))
if res, err = s.ProtoParsing(path, fileName); err != nil {
log.Error("parser grpc error(%v)", err)
return
}
// 生成import文件路径
if err = s.CreateGRPCImportDir(res, uploadParam.Path); err != nil {
return
}
return
}
// CreateGRPCImportDir create grpc import path
func (s *Service) CreateGRPCImportDir(res map[string]interface{}, rootPath string) (err error) {
if ipts, ok := res["import"]; ok {
for _, ipt := range ipts.([]string) {
iptPath, _ := filepath.Split(ipt)
protoPath := &model.ProtoPathModel{RootPath: rootPath, ExtraPath: iptPath}
if err = s.CreateProtoImportDir(protoPath); err != nil {
log.Error("create proto import dir error(%v)", err)
return
}
}
}
return
}
// CompileProtoFile compile proto file get jar path
func (s *Service) CompileProtoFile(protoPath, filename string) (jarPath string, err error) {
return s.createGrpcJar(protoPath, filename)
}
//protocFile protoc file
func (s *Service) protocFile(scriptPath, protoPath string) (err error) {
var (
protoCMD string
protoJava string
protoFileList []string
line string
protoFiles = fmt.Sprintf("cd %s && find . -name '*.proto' | awk -F '\\.\\/' '{print $2}'", scriptPath)
stdout io.ReadCloser
)
// 获取scriptPath 目录下所有 proto文件
cmd := exec.Command("/bin/bash", "-c", protoFiles)
if stdout, err = cmd.StdoutPipe(); err != nil {
log.Error("run protoFiles (%s) error(%v)", protoFiles, err)
return
}
cmd.Start()
reader := bufio.NewReader(stdout)
for {
if line, err = reader.ReadString('\n'); err != nil || io.EOF == err {
break
}
if line != "" {
protoFileList = append(protoFileList, line)
}
}
// 编译所有proto文件
for _, line := range protoFileList {
protoCMD = fmt.Sprintf("protoc -I %s --java_out=%s %s", scriptPath, protoPath, path.Join(scriptPath, line))
protoJava = fmt.Sprintf("protoc --plugin=protoc-gen-grpc-java=%s --grpc-java_out=%s -I %s %s",
s.c.Grpc.ProtoJavaPluginPath, protoPath, scriptPath, path.Join(scriptPath, line))
if err = exec.Command("/bin/bash", "-c", protoCMD).Run(); err != nil {
log.Error("protoc --proto_path (%s) error (%v)", protoCMD, err)
return ecode.MelloiProtocError
}
// 编译所有proto文件
for _, line := range protoFileList {
protoCMD = fmt.Sprintf("protoc -I %s --java_out=%s %s", scriptPath, protoPath, path.Join(scriptPath, line))
protoJava = fmt.Sprintf("protoc --plugin=protoc-gen-grpc-java=%s --grpc-java_out=%s -I %s %s",
s.c.Grpc.ProtoJavaPluginPath, protoPath, scriptPath, path.Join(scriptPath, line))
if err = exec.Command("/bin/bash", "-c", protoCMD).Run(); err != nil {
log.Error("protoc --proto_path (%s) error (%v)", protoCMD, err)
return ecode.MelloiProtocError
}
if err = exec.Command("/bin/bash", "-c", protoJava).Run(); err != nil {
log.Error("protoc java (%s) error (%v)", protoCMD, err)
return ecode.MelloiProtocError
}
}
if err = exec.Command("/bin/bash", "-c", protoJava).Run(); err != nil {
log.Error("protoc java (%s) error (%v)", protoCMD, err)
return ecode.MelloiProtocError
}
}
return
}
//createGrpcJar crate grpc jar
func (s *Service) createGrpcJar(scriptPath, filename string) (jarPath string, err error) {
protoPath := path.Join(scriptPath, "proto")
if err = os.MkdirAll(protoPath, pathPerm); err != nil {
log.Error("create proto path err ... (%v)", err)
return
}
var (
cmdJava = fmt.Sprintf(" cd %s && find . -name '*.java' |grep -v '^\\./\\.' > sources.txt ", protoPath)
cmdJavac = fmt.Sprintf("cd %s && javac -Djava.ext.dirs=%s -encoding utf-8 @sources.txt ", protoPath, s.c.Jmeter.JmeterExtLibPath)
jarFilename = strings.Replace(filename, ".proto", "", -1) + ".jar"
cmdJar = fmt.Sprintf("cd %s && jar -cvf %s .", protoPath, jarFilename)
)
if err = s.protocFile(scriptPath, protoPath); err != nil {
log.Error("protoc error (%v)", err)
return
}
// 编译java文件
if err = exec.Command("/bin/bash", "-c", cmdJava).Run(); err != nil {
log.Error("find *.java (%s) error(%+v)", cmdJava, errors.WithStack(err))
return
}
if err = exec.Command("/bin/bash", "-c", cmdJavac).Run(); err != nil {
log.Error("javac protoc javac (%s) error(%v)", cmdJavac, err)
err = ecode.MelloiJavacCompileError
return
}
// 生成jar文件
if err = exec.Command("/bin/bash", "-c", cmdJar).Run(); err != nil {
log.Error("protoc jar (%s) error(%v)", cmdJar, err)
err = ecode.MelloiJarError
return
}
jarPath = path.Join(protoPath, jarFilename)
return
}
//DownloadFile Download File
func (s *Service) DownloadFile(c *bm.Context, filePath string, w http.ResponseWriter) (err error) {
var (
Fi *os.File
info os.FileInfo
)
if info, err = os.Stat(filePath); err != nil {
return
}
//如果文件太大,就禁止下载
if info.Size() > conf.Conf.Melloi.MaxDowloadSize {
err = ecode.MelloiBeyondFileSize
return
}
if Fi, err = os.Open(filePath); err != nil {
log.Error("open file err :(%v)", err)
return
}
defer Fi.Close()
w.Header().Set("Content-Disposition", "attachment; filename="+Fi.Name())
if _, err = io.Copy(w, io.Reader(Fi)); err != nil {
log.Error("copy file err :(%v)", err)
err = ecode.MelloiCopyFileErr
return
}
return
}
//ReadFile read file
func (s *Service) ReadFile(c *bm.Context, file, limit string) (data string, err error) {
var (
info os.FileInfo
buff []byte
cmd *exec.Cmd
)
log.Info("开始读取文件--------- : (%s)", file)
if info, err = os.Stat(file); err != nil {
return
}
if info.Size() > conf.Conf.Melloi.MaxFileSize {
cmd = exec.Command("/bin/bash", "-c", "tail -1500 "+file)
} else {
cmd = exec.Command("/bin/bash", "-c", "cat "+file)
}
buff, err = cmd.Output()
if err != nil {
return
}
data = string(buff)
return
}
//IsFileExists is file exists
func (s *Service) IsFileExists(c *bm.Context, fileName string) (bool, error) {
return exists(fileName)
}
// exists exists
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

View File

@@ -0,0 +1,322 @@
package service
import (
"context"
"fmt"
"html/template"
"io"
"os"
"os/exec"
"path"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/melloi/model"
"go-common/app/admin/ep/melloi/service/proto"
"go-common/library/ecode"
"go-common/library/log"
)
const (
_upCase = 1
_downCase = 0
)
// capitalize 首字母大小写处理
func (s *Service) capitalize(str string, tp int) string {
b := []rune(str)
if tp == _upCase {
if b[0] >= 97 && b[1] <= 122 {
b[0] -= 32
}
} else {
if b[0] >= 64 && b[0] <= 90 {
b[0] += 32
}
}
return string(b)
}
// ProtoParsing analyze proto file
func (s *Service) ProtoParsing(protoPath, protoName string) (res map[string]interface{}, err error) {
var pkgRetMap []string
res = make(map[string]interface{})
reader, err := os.Open(path.Join(protoPath, protoName))
if err != nil {
log.Error("open proto file error(%v)", err)
return nil, err
}
defer reader.Close()
parser := proto.NewParser(reader)
var proto *proto.Proto
proto, err = parser.Parse()
if err != nil {
log.Error("parse proto file error(%v)", err)
return nil, err
}
res["fileName"] = protoName
proto.Filename = strings.TrimSuffix(protoName, ".proto")
res["protoClass"] = s.capitalize(proto.Filename, _upCase)
if len(proto.Imports) > 0 {
var retImportMap []string
for _, impt := range proto.Imports {
retImportMap = append(retImportMap, impt.Filename)
}
res["import"] = retImportMap
}
if len(proto.Package) > 0 {
for _, pkg := range proto.Package {
pkgRetMap = append(pkgRetMap, pkg.Name)
}
res["package"] = pkgRetMap
}
if len(proto.Options) > 0 {
var retOptionsList []string
for _, opt := range proto.Options {
retOptionsList = append(retOptionsList, opt.Name)
// java_package proto
if opt.Name == "java_package" && len(proto.Package) <= 0 {
pkgRetMap = append(pkgRetMap, opt.Constant.Source)
}
}
res["package"] = pkgRetMap
res["options"] = retOptionsList
}
if len(proto.Services) > 0 {
var retImportMap []map[string]interface{}
for _, srv := range proto.Services {
firstTmp := make(map[string]interface{})
firstTmp["name"] = srv.Name
var secondMapTmp []map[string]interface{}
for _, rpc := range srv.RPCElements {
secondTmp := make(map[string]interface{})
method := s.capitalize(rpc.Name, _downCase)
// transfer get_by_uid to getByUid
if strings.ContainsAny(method, "_") {
var nmethod string
methodStrs := strings.Split(method, "_")
for i, item := range methodStrs {
if i != 0 {
item = s.capitalize(item, _upCase)
}
nmethod += item
}
method = nmethod
}
secondTmp["method"] = method
secondTmp["requestType"] = rpc.RequestType
secondTmp["returnType"] = rpc.ReturnsType
secondMapTmp = append(secondMapTmp, secondTmp)
}
firstTmp["rpc"] = secondMapTmp
retImportMap = append(retImportMap, firstTmp)
}
res["service"] = retImportMap
}
return
}
// CreateProtoImportDir create import dir
func (s *Service) CreateProtoImportDir(pathModel *model.ProtoPathModel) (err error) {
cMakeDir := fmt.Sprintf("mkdir -p %s ", path.Join(pathModel.RootPath, pathModel.ExtraPath))
if err = exec.Command("/bin/bash", "-c", cMakeDir).Run(); err != nil {
log.Error("create proto import dir error(%v)", err)
}
return
}
//GRPCQuickStart grpc quickstart
func (s *Service) GRPCQuickStart(c context.Context, request *model.GRPCQuickStartRequest, runUser string, cookies string) (ret map[string]string, err error) {
var (
g *model.GRPC
reportID int
)
addScriptReq := request.GRPCAddScriptRequest
ret = make(map[string]string)
if g, err = s.GRPCAddScript(c, &addScriptReq); err != nil {
log.Error("Save grpc script failed, (%v)", err)
return
}
if reportID, err = s.GRPCRunByModel(c, g, runUser, cookies); err != nil {
log.Error("performance test execution failed, (%v)", err)
return
}
ret["report_id"] = strconv.Itoa(reportID)
return
}
// SaveGRPCQuickStart save gRPC script of quick start
func (s *Service) SaveGRPCQuickStart(c context.Context, request *model.GRPCQuickStartRequest) (err error) {
addScriptReq := request.GRPCAddScriptRequest
_, err = s.GRPCAddScript(c, &addScriptReq)
return err
}
//GRPCAddScript create grpc script
func (s *Service) GRPCAddScript(c context.Context, request *model.GRPCAddScriptRequest) (g *model.GRPC, err error) {
// 若是debug ,则执行固定次数
if request.IsDebug == 1 {
request.Loops = 4
request.ThreadsSum = 1
request.TaskName += "_perf_debug" // debug tag
}
if g, err = s.CreateJmx(c, request); err != nil {
log.Error("create jmeter file error (%v)", err)
return
}
g.IsDebug = request.IsDebug
return s.dao.CreateGRPC(g)
}
// CreateJmx create jmx
func (s *Service) CreateJmx(c context.Context, request *model.GRPCAddScriptRequest) (g *model.GRPC, err error) {
if g, err = s.createJmeterFile(request); err != nil {
log.Error("create jmeter file error (%v)", err)
return
}
return
}
//GRPCRunByScriptID grpc execution by id
func (s *Service) GRPCRunByScriptID(c context.Context, request *model.GRPCExecuteScriptRequest, runUser string, cookie string) (reportId int, err error) {
var grpc *model.GRPC
if grpc, err = s.dao.QueryGRPCByID(request.ScriptID); err != nil {
return
}
return s.GRPCRunByModel(c, grpc, runUser, cookie)
}
//GRPCRunByModel execute grpc by model data
func (s *Service) GRPCRunByModel(c context.Context, grpc *model.GRPC, runUser string, cookie string) (reportId int, err error) {
var resp model.DoPtestResp
tim := strconv.FormatInt(time.Now().Unix(), 10)
testNameNick := grpc.TaskName + tim
log.Info("开始调用压测grpc job-------\n")
ptestParam := model.DoPtestParam{
UserName: runUser, // 用户名
LoadTime: grpc.LoadTime, //运行时间
TestNames: StringToSlice(grpc.TaskName), //接口名转数组
FileName: grpc.JmxPath, // jmx文件
ResJtl: grpc.JtlLog, // jtl时间戳
JmeterLog: grpc.JmxLog, // jmeterlog时间戳
Department: grpc.Department,
Project: grpc.Project,
IsDebug: false,
APP: grpc.APP,
ScriptID: grpc.ID,
Cookie: cookie, // 用不到
URL: grpc.ServiceName, // 用于微信通知
LabelIDs: nil,
Domain: grpc.HostName, // 微信通知Domain
FileSplit: false, // 文件切割
SplitNum: 0, // 切割数量
JarPath: grpc.JarPath,
Type: model.PROTOCOL_GRPC, //grpc
}
if grpc.IsDebug == 1 {
ptestParam.IsDebug = true
}
if resp, err = s.DoPtestByJmeter(c, ptestParam, StringToSlice(testNameNick)); err != nil {
log.Error("s.DoPtestByJmeter err :(%v)", err)
return
}
reportId = resp.ReportSuID
return
}
//QueryGrpc query grpc list
func (s *Service) QueryGrpc(c context.Context, sessionID string, qgr *model.QueryGRPCRequest) (res *model.QueryGRPCResponse, err error) {
// 获取服务树节点
var (
treeNodes []string
treeNodesd []string
)
if treeNodesd, err = s.QueryUserRoleNode(c, sessionID); err != nil {
log.Error("QueryUserRoleNode err (%v):", err)
return
}
treeNodes = append(treeNodesd, "")
if ExistsInSlice(qgr.Executor, s.c.Melloi.Executor) {
return s.dao.QueryGRPCByWhiteName(&qgr.GRPC, qgr.PageNum, qgr.PageSize)
}
return s.dao.QueryGRPC(&qgr.GRPC, qgr.PageNum, qgr.PageSize, treeNodes)
}
//QueryGrpcById query grpc by id
func (s *Service) QueryGrpcById(id int) (*model.GRPC, error) {
return s.dao.QueryGRPCByID(id)
}
// UpdateGrpc update grpc
func (s *Service) UpdateGrpc(grpc *model.GRPC) error {
return s.dao.UpdateGRPC(grpc)
}
// DeleteGrpc delete grpc by id
func (s *Service) DeleteGrpc(id int) error {
return s.dao.DeleteGRPC(id)
}
//createJmx create jmx file, jtl file, jmx_log file
func (s *Service) createJmeterFile(grpcReq *model.GRPCAddScriptRequest) (g *model.GRPC, err error) {
var (
buff *template.Template
file *os.File
)
if buff, err = template.ParseFiles(s.c.Jmeter.GRPCTemplatePath); err != nil {
log.Error("open grpc.jmx failed! (%v)", err)
return nil, ecode.MelloiJmeterGenerateErr
}
g = model.GRPCReqToGRPC(grpcReq)
// 脚本执行限制
if g.LoadTime > s.c.Jmeter.TestTimeLimit {
g.LoadTime = s.c.Jmeter.TestTimeLimit
}
if g.Loops <= 0 {
g.Loops = -1
}
if g.IsAsync {
g.AsyncInfo = unescaped(model.AsyncInfo)
}
// 生成jmx文件
if grpcReq.ScriptPath == "" {
log.Error("proto file is not uploaded.")
return nil, ecode.MelloiProtoFileNotUploaded
}
g.JmxPath = path.Join(grpcReq.ScriptPath, grpcReq.TaskName+".jmx")
g.JmxLog = path.Join(grpcReq.ScriptPath, grpcReq.TaskName+".log") // 定义好即可,运行时生成
g.JtlLog = path.Join(grpcReq.ScriptPath, grpcReq.TaskName+".jtl") // 定义好即可,运行时生成
if g.ParamFilePath != "" {
g.ParamEnable = "true"
}
if file, err = os.Create(g.JmxPath); err != nil {
log.Error("create jmx file error :(%v)", err)
return nil, ecode.MelloiJmeterGenerateErr
}
defer file.Close()
// 写入模板数据
buff = buff.Funcs(template.FuncMap{"unescaped": unescaped})
if err = buff.Execute(io.Writer(file), g); err != nil {
log.Error("write jmeter file failed, (%v)", err)
return nil, ecode.MelloiJmeterGenerateErr
}
return
}

View File

@@ -0,0 +1,23 @@
package service
import "go-common/app/admin/ep/melloi/model"
// QueryGRPCSnapByID query grpcsnap by id
func (s *Service) QueryGRPCSnapByID(id int) (*model.GRPCSnap, error) {
return s.dao.QueryGRPCSnapByID(id)
}
// UpdateGRPCSnap update grpc snap
func (s *Service) UpdateGRPCSnap(grpcSnap *model.GRPCSnap) error {
return s.dao.UpdateGRPCSnap(grpcSnap)
}
// CreateGRPCSnap Create GRPC Snap
func (s *Service) CreateGRPCSnap(grpcSnap *model.GRPCSnap) error {
return s.dao.CreateGRPCSnap(grpcSnap)
}
// DeleteGRPCSnap Delete GRPC Snap
func (s *Service) DeleteGRPCSnap(id int) error {
return s.dao.DeleteGRPCSnap(id)
}

View File

@@ -0,0 +1,59 @@
package service
import (
. "github.com/smartystreets/goconvey/convey"
"go-common/app/admin/ep/melloi/model"
"testing"
)
var (
gs = model.GRPCSnap{
ID: 1,
GRPCID: 1,
TaskName: "test",
Department: "test",
Project: "ep",
APP: "melloi",
Active: 1,
HostName: "172.22.33.22",
Port: 9000,
ServiceName: "get",
ProtoClassName: "code",
PkgPath: "book",
RequestType: "reqmsg",
RequestMethod: "reqmethod",
RequestContent: "reqcontent",
ResponseType: "returnmsg",
ScriptPath: "sp",
JarPath: "jp",
JmxPath: "jmxpat",
JmxLog: "hnxkig",
JtlLog: "ht.lgo",
ThreadsSum: 1,
RampUp: 1,
Loops: -1,
LoadTime: 1,
UpdateBy: "hujianping",
}
)
func Test_GrpcSnap(t *testing.T) {
Convey("query grpc snap", t, func() {
_, err := s.QueryGRPCSnapByID(gs.ID)
So(err, ShouldBeNil)
})
Convey("update grpc snap", t, func() {
err := s.UpdateGRPCSnap(&gs)
So(err, ShouldBeNil)
})
Convey("create grpc snap", t, func() {
err := s.CreateGRPCSnap(&gs)
So(err, ShouldBeNil)
})
Convey("delete grpc snap", t, func() {
err := s.DeleteGRPCSnap(gs.ID)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,119 @@
package service
import (
. "github.com/smartystreets/goconvey/convey"
"go-common/app/admin/ep/melloi/model"
"testing"
)
var (
protoPath = "/data/jmeter-log/test/ep/melloi/test/381016516/"
protoFile = "StreamEvent.proto"
protoModel = model.ProtoPathModel{
RootPath: "/data/jmeter-log/test/ep/melloi/test/445057856/proto", ExtraPath: "account/service/member",
}
scriptID = model.GRPCExecuteScriptRequest{ScriptID: 1}
grpc = model.GRPC{
TaskName: "testGrpc",
Department: "test",
Project: "ep",
APP: "melloi",
Active: 1,
HostName: "172.22.33.22",
Port: 9000,
ServiceName: "Identify",
ProtoClassName: "Api",
PkgPath: "V1",
RequestType: "GetCookieInfo",
RequestMethod: "getCookieInfo",
RequestContent: "{\"Cookie\":\"sid:1ers12;SETDATA:a18jds9234js9sfa24jsdf\"}",
ResponseType: "Reponse",
ScriptPath: "/data/jmeter/log/test/ep/melloi/",
JarPath: "/data/jmeter/log/test/ep/melloi/text.jar",
ThreadsSum: 1,
RampUp: 1,
Loops: -1,
LoadTime: 100,
UpdateBy: "hujianping",
IsDebug: 0,
}
qgr = model.QueryGRPCRequest{
Executor: "hujianping",
GRPC: grpc,
}
gasr = model.GRPCAddScriptRequest{
TaskName: "testGrpc",
Department: "test",
Project: "ep",
APP: "melloi",
Active: 1,
HostName: "172.22.33.22",
Port: 9000,
ServiceName: "Identify",
ProtoClassName: "Api",
PkgPath: "V1",
RequestType: "GetCookieInfo",
RequestMethod: "getCookieInfo",
RequestContent: "{\"Cookie\":\"sid:1ers12;SETDATA:a18jds9234js9sfa24jsdf\"}",
ResponseType: "Reponse",
ScriptPath: "/data/jmeter/log/test/ep/melloi/",
JarPath: "/data/jmeter/log/test/ep/melloi/text.jar",
ThreadsSum: 1,
RampUp: 1,
Loops: -1,
LoadTime: 100,
UpdateBy: "hujianping",
IsDebug: 0,
}
)
func Test_Grpc(t *testing.T) {
Convey("proto parse", t, func() {
_, err := s.ProtoParsing(protoPath, protoFile)
So(err, ShouldBeNil)
})
Convey("create proto dependency dir", t, func() {
err := s.CreateProtoImportDir(&protoModel)
So(err, ShouldBeNil)
})
Convey(" add grpc script", t, func() {
_, err := s.GRPCAddScript(c, &gasr)
So(err, ShouldBeNil)
})
Convey("create jmx file", t, func() {
_, err := s.CreateJmx(c, &gasr)
So(err, ShouldBeNil)
})
Convey("run by script", t, func() {
cookie = "baf4dd3244116f492b71af3532cac03e"
_, err := s.GRPCRunByScriptID(c, &scriptID, userName, cookie)
So(err, ShouldBeNil)
})
Convey("query grpc", t, func() {
_, err := s.QueryGrpc(c, "e2df43ed324d20811e8d1be1a9fb36d5", &qgr)
So(err, ShouldBeNil)
})
Convey("run grpc by model", t, func() {
cookie = "baf4dd3244116f492b71af3532cac03e"
_, err := s.GRPCRunByModel(c, &grpc, userName, cookie)
So(err, ShouldBeNil)
})
Convey("query grpc by id", t, func() {
_, err := s.QueryGrpcById(grpc.ID)
So(err, ShouldBeNil)
})
Convey("update grpc", t, func() {
err := s.UpdateGrpc(&grpc)
So(err, ShouldBeNil)
})
Convey("delete grpc", t, func() {
err := s.DeleteGrpc(grpc.ID)
So(err, ShouldBeNil)
})
Convey("create jmx file ", t, func() {
_, err := s.createJmeterFile(&gasr)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,141 @@
package service
import (
"context"
"encoding/json"
"net/http"
"go-common/app/admin/ep/melloi/model"
"go-common/library/ecode"
"go-common/library/log"
)
//AddJob add perf job
func (s *Service) AddJob(c context.Context, job model.Job) (firstRetMap *model.PaasJobResponse, err error) {
var (
token string
clusterMap *model.PaasJobResponse
JSON []byte
)
if JSON, err = json.Marshal(job); err != nil {
log.Error("json.Marshal err: (%v)", err)
return
}
log.Info("JOBINFO: (%s)", string(JSON))
if token, err = s.RmToken(c); err != nil {
log.Error("token err :(%v)", err)
return
}
// 调度逻辑先从ptest_job 表里面查看
log.Info("创建job的 token:(%s) ", token)
if clusterMap, err = s.dao.AddJob(c, token, &job); err != nil {
log.Error("s.dao.AddJob err :(%v)", err)
return
}
if clusterMap == nil {
return
}
firstRetMap = clusterMap
return
}
//DeleteJob force stop perf job
func (s *Service) DeleteJob(c context.Context, name string) (firstRetMap *model.PaasJobResponse, err error) {
var token string
if token, err = s.RmToken(c); err != nil {
return
}
log.Info("开始执行删除容器的 dao 层******* ")
if firstRetMap, err = s.dao.DeleteJob(c, token, name); err != nil {
err = ecode.MelloiPaasRequestErr
return
}
return
}
// CleanJob clean None running job
func (s *Service) CleanJob(c context.Context) (err error) {
var dockers = []*model.CleanableDocker{}
if dockers, err = s.dao.QueryCleanableDocker(c); err != nil {
log.Error("s.job query docker error(%v)", err)
return
}
for _, docker := range dockers {
if _, err := s.DeleteJob(c, docker.Name); err != nil {
continue
}
}
return
}
// QueryCleanableDocker query clearable Docker
func (s *Service) QueryCleanableDocker(c context.Context) (dockers []*model.CleanableDocker, err error) {
return s.dao.QueryCleanableDocker(c)
}
//DeleteJobBatch force stop perf job batch
func (s *Service) DeleteJobBatch(c context.Context, JobBatch model.JobBatch) (status string) {
for _, reportSuID := range JobBatch.ReportSuIDs {
go s.QueryOrStopAllPtestByJobName(context.TODO(), reportSuID, true, 3)
}
status = "success"
return
}
//QueryJobCPU query job cpu
func (s *Service) QueryJobCPU(c context.Context, jobNamed string) (responseBody *model.PaasQueryJobCPUResult, err error) {
var token string
if token, err = s.RmToken(c); err != nil {
return
}
log.Info("执行查询容器实时cpu 接口 ")
if responseBody, err = s.dao.QueryJobCPU(c, token, jobNamed); err != nil {
log.Error("query job cpu err (%v)", err)
err = ecode.MelloiPaasRequestErr
return
}
return
}
//QueryJobCPUByEP query job cpu
func (s *Service) QueryJobCPUByEP(c context.Context, id, hostIP string) (dockerStats *model.DockerStats, err error) {
if dockerStats, err = s.dao.QueryJobCPUByEP(c, id, hostIP); err != nil {
log.Error("query QueryJobCpuByEP err (%v)", err)
return
}
return
}
//Job get job info
func (s *Service) Job(c context.Context, name string) (firstRetMap *model.PaasJobQueryStatus, err error) {
var token string
if token, err = s.RmToken(c); err != nil {
err = ecode.MelloiPaasRequestErr
return
}
if firstRetMap, err = s.dao.Job(c, token, name); err != nil {
err = ecode.MelloiPaasRequestErr
return
}
return
}
//ForceDeleteJob Force Delete Job
func (s *Service) ForceDeleteJob(c context.Context, reportSuId int) (jobList *model.JobInfoList, err error) {
log.Info("开始执行强制删除容器******* ")
if jobList, err = s.dao.ForceDeleteJob(reportSuId); err != nil {
return
}
for _, job := range jobList.JobList {
url := "http://" + job.HostIp + ":8999" + "/ep/docker/remove?job_name=" + job.JobName
if _, delErr := http.Get(url); delErr != nil {
//log.Error("强制删除失败,请重试!")
s.QueryOrStopAllPtestByJobName(c, reportSuId, true, 3)
return
}
}
if upErr := s.dao.UpdateJobStatus(reportSuId); upErr != nil {
return
}
return
}

View File

@@ -0,0 +1,92 @@
package service
import (
"context"
"go-common/app/admin/ep/melloi/model"
"go-common/library/ecode"
"go-common/library/log"
)
// AddLabel create new label
func (s *Service) AddLabel(label *model.Label) (err error) {
if _, err = s.dao.QueryLabel(label); err == nil {
err = ecode.MelloiLabelExistErr
return
}
label.Active = 1
return s.dao.AddLabel(label)
}
// QueryLabel query all labels
func (s *Service) QueryLabel(c context.Context) ([]*model.Label, error) {
return s.dao.QueryLabels(c)
}
// DeleteLabel delete label by id
func (s *Service) DeleteLabel(id int64) error {
if id <= 0 {
return ecode.RequestErr
}
return s.dao.DeleteLabel(id)
}
// AddLabelRelation create new label relation
func (s *Service) AddLabelRelation(lr *model.LabelRelation) (err error) {
label := &model.Label{ID: lr.LabelID}
if label, err = s.dao.QueryLabel(label); err != nil {
return ecode.RequestErr
}
// 存在相同的记录
if _, err = s.dao.QueryLabelExist(lr); err != nil {
return ecode.MelloiLabelExistErr
}
// 每个脚本|任务最多有2个label
var lre []*model.LabelRelation
relation := model.LabelRelation{Type: lr.Type, TargetID: lr.TargetID}
if lre, err = s.dao.QueryLabelRelation(&relation); err != nil {
return err
}
if len(lre) >= 2 {
return ecode.MelloiLabelCountErr
}
lr.Description = label.Description
lr.Color = label.Color
lr.LabelName = label.Name
lr.Active = 1
if err = s.dao.AddLabelRelation(lr); err != nil {
log.Error("s.dao.AddLabelRelation err :(%v)", err)
return
}
return
}
// DeleteLabelRelation delete label relation by id
func (s *Service) DeleteLabelRelation(id int64) (err error) {
if id <= 0 {
return ecode.RequestErr
}
// 标签不存在
var exist bool
if exist, err = s.dao.CheckLabelRelationExist(id); err != nil {
return
}
if !exist {
return ecode.MelloiLabelRelationNotExist
}
return s.dao.DeleteLabelRelation(id)
}
// QueryLabelRelation query label relation by id , type, targetid
func (s *Service) QueryLabelRelation(lre *model.LabelRelation) (lr []*model.LabelRelation, err error) {
return s.dao.QueryLabelRelation(lre)
}
// QueryLabelRelationByIDs query label relation by ids
func (s *Service) QueryLabelRelationByIDs(ids []int64) (lr []*model.LabelRelation, err error) {
return s.dao.QueryLabelRelationByIDs(ids)
}

View File

@@ -0,0 +1,66 @@
package service
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/admin/ep/melloi/model"
)
var (
label = model.Label{
ID: 1,
Name: "name",
Description: "description",
Color: "#602230",
Active: 1,
}
lr = model.LabelRelation{
ID: 1,
LabelID: 1,
LabelName: "name",
Color: "#602230",
Description: "description",
TargetID: 1,
Type: 1,
Active: 1,
}
ids = []int64{1, 2, 3, 4, 5}
)
func Test_Label(t *testing.T) {
Convey("add label", t, func() {
err := s.AddLabel(&label)
So(err, ShouldBeNil)
})
Convey("query label", t, func() {
_, err := s.QueryLabel(c)
So(err, ShouldBeNil)
})
Convey("delete label", t, func() {
err := s.DeleteLabel(label.ID)
So(err, ShouldBeNil)
})
Convey("delete label relation", t, func() {
err := s.AddLabelRelation(&lr)
So(err, ShouldBeNil)
})
Convey("delete label relation", t, func() {
err := s.DeleteLabelRelation(lr.ID)
So(err, ShouldBeNil)
})
Convey("query label relation", t, func() {
_, err := s.QueryLabelRelation(&lr)
So(err, ShouldBeNil)
})
Convey("query label relation by ids", t, func() {
_, err := s.QueryLabelRelationByIDs(ids)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,18 @@
package service
import (
"gopkg.in/gomail.v2"
)
// SendMail send email
func (s *Service) SendMail(receiver string, subject string, content string) error {
var (
m = gomail.NewMessage()
)
m.SetHeader("To", receiver)
m.SetHeader("Subject", subject)
m.SetBody("text/html", content)
return s.dao.SendMail(m)
}

View File

@@ -0,0 +1,20 @@
package service
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
var (
receiver = "hujianping"
subject = "test of melloi"
content = "this is test content"
)
func Test_Mail(t *testing.T) {
Convey("test QueryOrder", t, func() {
err := s.SendMail(receiver, subject, content)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,29 @@
package service
import (
"time"
"go-common/app/admin/ep/melloi/model"
)
// QueryOrder query order by order object
func (s *Service) QueryOrder(qor *model.QueryOrderRequest) (*model.QueryOrderResponse, error) {
return s.dao.QueryOrder(&qor.Order, qor.PageNum, qor.PageSize)
}
// UpdateOrder update perf order information
func (s *Service) UpdateOrder(order *model.Order) error {
return s.dao.UpdateOrder(order)
}
// AddOrder create new order
func (s *Service) AddOrder(order *model.Order) error {
order.ApplyDate = time.Now()
order.Active = 1
return s.dao.AddOrder(order)
}
// DelOrder delete order info by orderID
func (s *Service) DelOrder(id int64) error {
return s.dao.DelOrder(id)
}

View File

@@ -0,0 +1,21 @@
package service
import (
"go-common/app/admin/ep/melloi/model"
"go-common/library/ecode"
)
// QueryOrderAdmin get administrator for order by current username
func (s *Service) QueryOrderAdmin(userName string) (*model.OrderAdmin, error) {
return s.dao.QueryOrderAdmin(userName)
}
// AddOrderAdmin add administrator for order
func (s *Service) AddOrderAdmin(admin *model.OrderAdmin) (err error) {
var oa *model.OrderAdmin
oa, _ = s.dao.QueryOrderAdmin(admin.UserName)
if oa.UserName == admin.UserName {
return ecode.MelloiAdminExist
}
return s.dao.AddOrderAdmin(admin)
}

View File

@@ -0,0 +1,37 @@
package service
import (
"testing"
"go-common/app/admin/ep/melloi/model"
. "github.com/smartystreets/goconvey/convey"
)
var (
oa = model.OrderAdmin{
UserName: "hujianping",
}
)
func Test_OrderAdmin(t *testing.T) {
Convey("test AddOrderAdmin 60003", t, func() {
err := s.AddOrderAdmin(&oa)
So(err, ShouldNotBeNil)
})
Convey("test QueryOrderAdmin 60003", t, func() {
var admin *model.OrderAdmin
admin, _ = s.QueryOrderAdmin(oa.UserName)
So(admin, ShouldNotBeNil)
})
Convey("test AddOrderAdmin", t, func() {
var oak = model.OrderAdmin{
UserName: "hukai",
}
err := s.AddOrderAdmin(&oak)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,38 @@
package service
import (
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
// AddReport add report
func (s *Service) AddReport(userName string, report *model.OrderReport) (err error) {
var qor *model.QueryOrderResponse
report.UpdateBy = userName
report.Active = 1
// 更新order report
if report.ID != 0 {
return s.UpdateReportByID(report)
}
// 新增order report
order := model.Order{ID: report.OrderID}
if qor, err = s.dao.QueryOrder(&order, 1, 1); err != nil {
log.Error("order_report.service get order error (%v)", err)
return err
}
report.Name = qor.Orders[0].Name
return s.dao.AddReport(report)
}
//QueryReportByOrderID query report
func (s *Service) QueryReportByOrderID(orderID int64) (*model.OrderReport, error) {
return s.dao.QueryReportByOrderID(orderID)
}
//UpdateReportByID update report by order_id
func (s *Service) UpdateReportByID(report *model.OrderReport) (err error) {
err = s.dao.UpdateReportByID(report)
return
}

View File

@@ -0,0 +1,38 @@
package service
import (
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/admin/ep/melloi/model"
)
var (
testOrder = model.QueryOrderRequest{
Order: model.Order{ID: 1, Name: "test", ApplyDate: time.Now()},
Pagination: model.Pagination{PageNum: 1, PageSize: 1, TotalSize: 1},
}
)
func Test_Order(t *testing.T) {
Convey("test QueryOrder", t, func() {
var workOrders *model.QueryOrderResponse
workOrders, _ = s.QueryOrder(&testOrder)
So(workOrders, ShouldBeEmpty)
})
Convey("test DeleteOrder", t, func() {
order := model.Order{ID: testOrder.ID}
err := s.DelOrder(order.ID)
So(err, ShouldBeNil)
})
Convey("test UpdateOrder", t, func() {
order := model.Order{ID: testOrder.ID, ApplyDate: testOrder.ApplyDate}
err := s.UpdateOrder(&order)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,44 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"comment.go",
"enum.go",
"extensions.go",
"field.go",
"group.go",
"import.go",
"message.go",
"oneof.go",
"option.go",
"package.go",
"parser.go",
"proto.go",
"range.go",
"reserved.go",
"service.go",
"syntax.go",
"token.go",
"visitor.go",
"walk.go",
],
importmap = "go-common/vendor/github.com/emicklei/proto",
importpath = "go-common/app/admin/ep/melloi/service/proto",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,22 @@
Copyright (c) 2017 Ernest Micklei
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,81 @@
SRCS := $(shell find . -name '*.go')
BIN_DEPS := \
github.com/golang/lint/golint \
github.com/kisielk/errcheck \
honnef.co/go/tools/cmd/staticcheck \
honnef.co/go/tools/cmd/unused
.PHONY: all
all: test
.PHONY: deps
deps:
go get -d -v ./...
.PHONY: updatedeps
updatedeps:
go get -d -v -u -f ./...
.PHONY: bindeps
bindeps:
go get -v $(BIN_DEPS)
.PHONY: updatebindeps
updatebindeps:
go get -u -v $(BIN_DEPS)
.PHONY: testdeps
testdeps: bindeps
go get -d -v -t ./...
.PHONY: updatetestdeps
updatetestdeps: updatebindeps
go get -d -v -t -u -f ./...
.PHONY: install
install: deps
go install ./...
.PHONY: golint
golint: testdeps
@# TODO: readd cmd/proto2gql when fixed
@#for file in $(SRCS); do
for file in $(shell echo $(SRCS) | grep -v cmd/proto2gql); do \
golint $${file}; \
if [ -n "$$(golint $${file})" ]; then \
exit 1; \
fi; \
done
.PHONY: vet
vet: testdeps
go vet ./...
.PHONY: testdeps
errcheck: testdeps
errcheck ./...
.PHONY: staticcheck
staticcheck: testdeps
staticcheck ./...
.PHONY: unused
unused: testdeps
unused ./...
.PHONY: lint
# TODO: readd errcheck and unused when fixed
#lint: golint vet errcheck staticcheck unused
lint: golint vet staticcheck
.PHONY: test
test: testdeps lint
go test -race ./...
.PHONY: clean
clean:
go clean -i ./...
integration:
PB=y go test -cover

View File

@@ -0,0 +1,50 @@
# proto
[![Build Status](https://travis-ci.org/emicklei/proto.png)](https://travis-ci.org/emicklei/proto)
[![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/proto)](https://goreportcard.com/report/github.com/emicklei/proto)
[![GoDoc](https://godoc.org/github.com/emicklei/proto?status.svg)](https://godoc.org/github.com/emicklei/proto)
Package in Go for parsing Google Protocol Buffers [.proto files version 2 + 3] (https://developers.google.com/protocol-buffers/docs/reference/proto3-spec)
### install
go get -u -v github.com/emicklei/proto
### usage
package main
import (
"fmt"
"os"
"github.com/emicklei/proto"
)
func main() {
reader, _ := os.Open("test.proto")
defer reader.Close()
parser := proto.NewParser(reader)
definition, _ := parser.Parse()
proto.Walk(definition,
proto.WithService(handleService),
proto.WithMessage(handleMessage))
}
func handleService(s *proto.Service) {
fmt.Println(s.Name)
}
func handleMessage(m *proto.Message) {
fmt.Println(m.Name)
}
### contributions
See (https://github.com/emicklei/proto-contrib) for other contributions on top of this package such as protofmt, proto2xsd and proto2gql.
© 2017, [ernestmicklei.com](http://ernestmicklei.com). MIT License. Contributions welcome.

View File

@@ -0,0 +1,146 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"strings"
"text/scanner"
)
// Comment one or more comment text lines, either in c- or c++ style.
type Comment struct {
Position scanner.Position
// Lines are comment text lines without prefixes //, ///, /* or suffix */
Lines []string
Cstyle bool // refers to /* ... */, C++ style is using //
ExtraSlash bool // is true if the comment starts with 3 slashes
}
// newComment returns a comment.
func newComment(pos scanner.Position, lit string) *Comment {
extraSlash := strings.HasPrefix(lit, "///")
isCstyle := strings.HasPrefix(lit, "/*") && strings.HasSuffix(lit, "*/")
var lines []string
if isCstyle {
withoutMarkers := strings.TrimRight(strings.TrimLeft(lit, "/*"), "*/")
lines = strings.Split(withoutMarkers, "\n")
} else {
lines = strings.Split(strings.TrimLeft(lit, "/"), "\n")
}
return &Comment{Position: pos, Lines: lines, Cstyle: isCstyle, ExtraSlash: extraSlash}
}
//type inlineComment struct {
// line string
// extraSlash bool
//}
// Accept dispatches the call to the visitor.
func (c *Comment) Accept(v Visitor) {
v.VisitComment(c)
}
// Merge appends all lines from the argument comment.
func (c *Comment) Merge(other *Comment) {
c.Lines = append(c.Lines, other.Lines...)
c.Cstyle = c.Cstyle || other.Cstyle
}
func (c Comment) hasTextOnLine(line int) bool {
if len(c.Lines) == 0 {
return false
}
return c.Position.Line <= line && line <= c.Position.Line+len(c.Lines)-1
}
// Message returns the first line or empty if no lines.
func (c Comment) Message() string {
if len(c.Lines) == 0 {
return ""
}
return c.Lines[0]
}
// commentInliner is for types that can have an inline comment.
type commentInliner interface {
inlineComment(c *Comment)
}
// maybeScanInlineComment tries to scan comment on the current line ; if present then set it for the last element added.
func maybeScanInlineComment(p *Parser, c elementContainer) {
currentPos := p.scanner.Position
// see if there is an inline Comment
pos, tok, lit := p.next()
esize := len(c.elements())
// seen comment and on same line and elements have been added
if tCOMMENT == tok && pos.Line == currentPos.Line && esize > 0 {
// if the last added element can have an inline comment then set it
last := c.elements()[esize-1]
if inliner, ok := last.(commentInliner); ok {
// TODO skip multiline?
inliner.inlineComment(newComment(pos, lit))
}
} else {
p.nextPut(pos, tok, lit)
}
}
// takeLastCommentIfEndsOnLine removes and returns the last element of the list if it is a Comment
func takeLastCommentIfEndsOnLine(list []Visitee, line int) (*Comment, []Visitee) {
if len(list) == 0 {
return nil, list
}
if last, ok := list[len(list)-1].(*Comment); ok && last.hasTextOnLine(line) {
return last, list[:len(list)-1]
}
return nil, list
}
// mergeOrReturnComment creates a new comment and tries to merge it with the last element (if is a comment and is on the next line).
func mergeOrReturnComment(elements []Visitee, lit string, pos scanner.Position) *Comment {
com := newComment(pos, lit)
esize := len(elements)
if esize == 0 {
return com
}
// last element must be a comment to merge
last, ok := elements[esize-1].(*Comment)
if !ok {
return com
}
// do not merge c-style comments
if last.Cstyle {
return com
}
// last comment has text on previous line
// TODO handle last line of file could be inline comment
if !last.hasTextOnLine(pos.Line - 1) {
return com
}
last.Merge(com)
return nil
}
// parent is part of elementContainer
func (c *Comment) parent(Visitee) {}

View File

@@ -0,0 +1,210 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Enum definition consists of a name and an enum body.
type Enum struct {
Position scanner.Position
Comment *Comment
Name string
Elements []Visitee
Parent Visitee
}
// Accept dispatches the call to the visitor.
func (e *Enum) Accept(v Visitor) {
v.VisitEnum(e)
}
// Doc is part of Documented
func (e *Enum) Doc() *Comment {
return e.Comment
}
// addElement is part of elementContainer
func (e *Enum) addElement(v Visitee) {
v.parent(e)
e.Elements = append(e.Elements, v)
}
// elements is part of elementContainer
func (e *Enum) elements() []Visitee {
return e.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (e *Enum) takeLastComment(expectedOnLine int) (last *Comment) {
last, e.Elements = takeLastCommentIfEndsOnLine(e.Elements, expectedOnLine)
return
}
func (e *Enum) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "enum identifier", e)
}
}
e.Name = lit
_, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "enum opening {", e)
}
for {
pos, tok, lit = p.next()
switch tok {
case tCOMMENT:
if com := mergeOrReturnComment(e.elements(), lit, pos); com != nil { // not merged?
e.addElement(com)
}
case tOPTION:
v := new(Option)
v.Position = pos
v.Comment = e.takeLastComment(pos.Line)
err := v.parse(p)
if err != nil {
return err
}
e.addElement(v)
case tRIGHTCURLY, tEOF:
goto done
case tSEMICOLON:
maybeScanInlineComment(p, e)
case tRESERVED:
r := new(Reserved)
r.Position = pos
r.Comment = e.takeLastComment(pos.Line - 1)
if err := r.parse(p); err != nil {
return err
}
e.addElement(r)
default:
p.nextPut(pos, tok, lit)
f := new(EnumField)
f.Position = pos
f.Comment = e.takeLastComment(pos.Line - 1)
if err := f.parse(p); err != nil {
return err
}
e.addElement(f)
}
}
done:
if tok != tRIGHTCURLY {
return p.unexpected(lit, "enum closing }", e)
}
return nil
}
// parent is part of elementContainer
func (e *Enum) parent(p Visitee) { e.Parent = p }
// EnumField is part of the body of an Enum.
type EnumField struct {
Position scanner.Position
Comment *Comment
Name string
Integer int
// ValueOption is deprecated, use Elements instead
ValueOption *Option
Elements []Visitee // such as Option and Comment
InlineComment *Comment
Parent Visitee
}
// Accept dispatches the call to the visitor.
func (f *EnumField) Accept(v Visitor) {
v.VisitEnumField(f)
}
// inlineComment is part of commentInliner.
func (f *EnumField) inlineComment(c *Comment) {
f.InlineComment = c
}
// Doc is part of Documented
func (f *EnumField) Doc() *Comment {
return f.Comment
}
func (f *EnumField) parse(p *Parser) (err error) {
_, tok, lit := p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "enum field identifier", f)
}
}
f.Name = lit
pos, tok, lit := p.next()
if tok != tEQUALS {
return p.unexpected(lit, "enum field =", f)
}
var i int
if i, err = p.nextInteger(); err != nil {
return p.unexpected(err.Error(), "enum field integer", f)
}
f.Integer = i
pos, tok, lit = p.next()
if tok == tLEFTSQUARE {
for {
o := new(Option)
o.Position = pos
o.IsEmbedded = true
err := o.parse(p)
if err != nil {
return err
}
// update deprecated field with the last option found
f.ValueOption = o
f.addElement(o)
pos, tok, lit = p.next()
if tok == tCOMMA {
continue
}
if tok == tRIGHTSQUARE {
break
}
}
}
if tSEMICOLON == tok {
p.nextPut(pos, tok, lit) // put back this token for scanning inline comment
}
return
}
// addElement is part of elementContainer
func (f *EnumField) addElement(v Visitee) {
v.parent(f)
f.Elements = append(f.Elements, v)
}
func (f *EnumField) parent(v Visitee) { f.Parent = v }

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Extensions declare that a range of field numbers in a message are available for third-party extensions.
// proto2 only
type Extensions struct {
Position scanner.Position
Comment *Comment
Ranges []Range
InlineComment *Comment
Parent Visitee
}
// inlineComment is part of commentInliner.
func (e *Extensions) inlineComment(c *Comment) {
e.InlineComment = c
}
// Accept dispatches the call to the visitor.
func (e *Extensions) Accept(v Visitor) {
v.VisitExtensions(e)
}
// parse expects ranges
func (e *Extensions) parse(p *Parser) error {
list, err := parseRanges(p, e)
if err != nil {
return err
}
e.Ranges = list
return nil
}
// parent is part of elementContainer
func (e *Extensions) parent(p Visitee) { e.Parent = p }

View File

@@ -0,0 +1,180 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Field is an abstract message field.
type Field struct {
Position scanner.Position
Comment *Comment
Name string
Type string
Sequence int
Options []*Option
InlineComment *Comment
Parent Visitee
}
// inlineComment is part of commentInliner.
func (f *Field) inlineComment(c *Comment) {
f.InlineComment = c
}
// NormalField represents a field in a Message.
type NormalField struct {
*Field
Repeated bool
Optional bool // proto2
Required bool // proto2
}
func newNormalField() *NormalField { return &NormalField{Field: new(Field)} }
// Accept dispatches the call to the visitor.
func (f *NormalField) Accept(v Visitor) {
v.VisitNormalField(f)
}
// Doc is part of Documented
func (f *NormalField) Doc() *Comment {
return f.Comment
}
// parse expects:
// [ "repeated" | "optional" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
func (f *NormalField) parse(p *Parser) error {
for {
_, tok, lit := p.nextTypeName()
switch tok {
case tREPEATED:
f.Repeated = true
return f.parse(p)
case tOPTIONAL: // proto2
f.Optional = true
return f.parse(p)
case tIDENT:
f.Type = lit
return parseFieldAfterType(f.Field, p)
default:
goto done
}
}
done:
return nil
}
// parseFieldAfterType expects:
// fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";
func parseFieldAfterType(f *Field, p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "field identifier", f)
}
}
f.Name = lit
pos, tok, lit = p.next()
if tok != tEQUALS {
return p.unexpected(lit, "field =", f)
}
i, err := p.nextInteger()
if err != nil {
return p.unexpected(lit, "field sequence number", f)
}
f.Sequence = i
// see if there are options
pos, tok, _ = p.next()
if tLEFTSQUARE != tok {
p.nextPut(pos, tok, lit)
return nil
}
// consume options
for {
o := new(Option)
o.Position = pos
o.IsEmbedded = true
err := o.parse(p)
if err != nil {
return err
}
f.Options = append(f.Options, o)
pos, tok, lit = p.next()
if tRIGHTSQUARE == tok {
break
}
if tCOMMA != tok {
return p.unexpected(lit, "option ,", o)
}
}
return nil
}
// MapField represents a map entry in a message.
type MapField struct {
*Field
KeyType string
}
func newMapField() *MapField { return &MapField{Field: new(Field)} }
// Accept dispatches the call to the visitor.
func (f *MapField) Accept(v Visitor) {
v.VisitMapField(f)
}
// parse expects:
// mapField = "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
// keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" |
// "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string"
func (f *MapField) parse(p *Parser) error {
_, tok, lit := p.next()
if tLESS != tok {
return p.unexpected(lit, "map keyType <", f)
}
_, tok, lit = p.nextTypeName()
if tIDENT != tok {
return p.unexpected(lit, "map identifier", f)
}
f.KeyType = lit
_, tok, lit = p.next()
if tCOMMA != tok {
return p.unexpected(lit, "map type separator ,", f)
}
_, tok, lit = p.nextTypeName()
if tIDENT != tok {
return p.unexpected(lit, "map valueType identifier", f)
}
f.Type = lit
_, tok, lit = p.next()
if tGREATER != tok {
return p.unexpected(lit, "map valueType >", f)
}
return parseFieldAfterType(f.Field, p)
}
func (f *Field) parent(v Visitee) { f.Parent = v }

View File

@@ -0,0 +1 @@
module github.com/emicklei/proto

View File

@@ -0,0 +1,99 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Group represents a (proto2 only) group.
// https://developers.google.com/protocol-buffers/docs/reference/proto2-spec#group_field
type Group struct {
Position scanner.Position
Comment *Comment
Name string
Optional bool
Repeated bool
Required bool
Sequence int
Elements []Visitee
Parent Visitee
}
// Accept dispatches the call to the visitor.
func (g *Group) Accept(v Visitor) {
v.VisitGroup(g)
}
// addElement is part of elementContainer
func (g *Group) addElement(v Visitee) {
v.parent(g)
g.Elements = append(g.Elements, v)
}
// elements is part of elementContainer
func (g *Group) elements() []Visitee {
return g.Elements
}
// Doc is part of Documented
func (g *Group) Doc() *Comment {
return g.Comment
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (g *Group) takeLastComment(expectedOnLine int) (last *Comment) {
last, g.Elements = takeLastCommentIfEndsOnLine(g.Elements, expectedOnLine)
return
}
// parse expects:
// groupName "=" fieldNumber { messageBody }
func (g *Group) parse(p *Parser) (err error) {
_, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "group name", g)
}
}
g.Name = lit
_, tok, lit = p.next()
if tok != tEQUALS {
return p.unexpected(lit, "group =", g)
}
var i int
if i, err = p.nextInteger(); err != nil {
return p.unexpected(lit, "group sequence number", g)
}
g.Sequence = i
_, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "group opening {", g)
}
return parseMessageBody(p, g)
}
func (g *Group) parent(v Visitee) { g.Parent = v }

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Import holds a filename to another .proto definition.
type Import struct {
Position scanner.Position
Comment *Comment
Filename string
Kind string // weak, public, <empty>
InlineComment *Comment
Parent Visitee
}
func (i *Import) parse(p *Parser) error {
_, tok, lit := p.next()
switch tok {
case tWEAK:
i.Kind = lit
return i.parse(p)
case tPUBLIC:
i.Kind = lit
return i.parse(p)
case tIDENT:
i.Filename = unQuote(lit)
default:
return p.unexpected(lit, "import classifier weak|public|quoted", i)
}
return nil
}
// Accept dispatches the call to the visitor.
func (i *Import) Accept(v Visitor) {
v.VisitImport(i)
}
// inlineComment is part of commentInliner.
func (i *Import) inlineComment(c *Comment) {
i.InlineComment = c
}
// Doc is part of Documented
func (i *Import) Doc() *Comment {
return i.Comment
}
func (i *Import) parent(v Visitee) { i.Parent = v }

View File

@@ -0,0 +1,232 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Message consists of a message name and a message body.
type Message struct {
Position scanner.Position
Comment *Comment
Name string
IsExtend bool
Elements []Visitee
Parent Visitee
}
func (m *Message) groupName() string {
if m.IsExtend {
return "extend"
}
return "message"
}
// parse expects ident { messageBody
func (m *Message) parse(p *Parser) error {
_, tok, lit := p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, m.groupName()+" identifier", m)
}
}
m.Name = lit
_, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, m.groupName()+" opening {", m)
}
return parseMessageBody(p, m)
}
// parseMessageBody parses elements after {. It consumes the closing }
func parseMessageBody(p *Parser, c elementContainer) error {
var (
pos scanner.Position
tok token
lit string
)
for {
pos, tok, lit = p.next()
switch {
case isComment(lit):
if com := mergeOrReturnComment(c.elements(), lit, pos); com != nil { // not merged?
c.addElement(com)
}
case tENUM == tok:
e := new(Enum)
e.Position = pos
e.Comment = c.takeLastComment(pos.Line - 1)
if err := e.parse(p); err != nil {
return err
}
c.addElement(e)
case tMESSAGE == tok:
msg := new(Message)
msg.Position = pos
msg.Comment = c.takeLastComment(pos.Line - 1)
if err := msg.parse(p); err != nil {
return err
}
c.addElement(msg)
case tOPTION == tok:
o := new(Option)
o.Position = pos
o.Comment = c.takeLastComment(pos.Line - 1)
if err := o.parse(p); err != nil {
return err
}
c.addElement(o)
case tONEOF == tok:
o := new(Oneof)
o.Position = pos
o.Comment = c.takeLastComment(pos.Line - 1)
if err := o.parse(p); err != nil {
return err
}
c.addElement(o)
case tMAP == tok:
f := newMapField()
f.Position = pos
f.Comment = c.takeLastComment(pos.Line - 1)
if err := f.parse(p); err != nil {
return err
}
c.addElement(f)
case tRESERVED == tok:
r := new(Reserved)
r.Position = pos
r.Comment = c.takeLastComment(pos.Line - 1)
if err := r.parse(p); err != nil {
return err
}
c.addElement(r)
// BEGIN proto2
case tOPTIONAL == tok || tREPEATED == tok || tREQUIRED == tok:
// look ahead
prevTok := tok
pos, tok, lit = p.next()
if tGROUP == tok {
g := new(Group)
g.Position = pos
g.Comment = c.takeLastComment(pos.Line - 1)
g.Optional = prevTok == tOPTIONAL
g.Repeated = prevTok == tREPEATED
g.Required = prevTok == tREQUIRED
if err := g.parse(p); err != nil {
return err
}
c.addElement(g)
} else {
// not a group, will be tFIELD
p.nextPut(pos, tok, lit)
f := newNormalField()
f.Type = lit
f.Position = pos
f.Comment = c.takeLastComment(pos.Line - 1)
f.Optional = prevTok == tOPTIONAL
f.Repeated = prevTok == tREPEATED
f.Required = prevTok == tREQUIRED
if err := f.parse(p); err != nil {
return err
}
c.addElement(f)
}
case tGROUP == tok:
g := new(Group)
g.Position = pos
g.Comment = c.takeLastComment(pos.Line - 1)
if err := g.parse(p); err != nil {
return err
}
c.addElement(g)
case tEXTENSIONS == tok:
e := new(Extensions)
e.Position = pos
e.Comment = c.takeLastComment(pos.Line - 1)
if err := e.parse(p); err != nil {
return err
}
c.addElement(e)
case tEXTEND == tok:
e := new(Message)
e.Position = pos
e.Comment = c.takeLastComment(pos.Line - 1)
e.IsExtend = true
if err := e.parse(p); err != nil {
return err
}
c.addElement(e)
// END proto2 only
case tRIGHTCURLY == tok || tEOF == tok:
goto done
case tSEMICOLON == tok:
maybeScanInlineComment(p, c)
// continue
default:
// tFIELD
p.nextPut(pos, tok, lit)
f := newNormalField()
f.Position = pos
f.Comment = c.takeLastComment(pos.Line - 1)
if err := f.parse(p); err != nil {
return err
}
c.addElement(f)
}
}
done:
if tok != tRIGHTCURLY {
return p.unexpected(lit, "extend|message|group closing }", c)
}
return nil
}
// Accept dispatches the call to the visitor.
func (m *Message) Accept(v Visitor) {
v.VisitMessage(m)
}
// addElement is part of elementContainer
func (m *Message) addElement(v Visitee) {
v.parent(m)
m.Elements = append(m.Elements, v)
}
// elements is part of elementContainer
func (m *Message) elements() []Visitee {
return m.Elements
}
func (m *Message) takeLastComment(expectedOnLine int) (last *Comment) {
last, m.Elements = takeLastCommentIfEndsOnLine(m.Elements, expectedOnLine)
return
}
// Doc is part of Documented
func (m *Message) Doc() *Comment {
return m.Comment
}
func (m *Message) parent(v Visitee) { m.Parent = v }

View File

@@ -0,0 +1,140 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Oneof is a field alternate.
type Oneof struct {
Position scanner.Position
Comment *Comment
Name string
Elements []Visitee
Parent Visitee
}
// addElement is part of elementContainer
func (o *Oneof) addElement(v Visitee) {
v.parent(o)
o.Elements = append(o.Elements, v)
}
// elements is part of elementContainer
func (o *Oneof) elements() []Visitee {
return o.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (o *Oneof) takeLastComment(expectedOnLine int) (last *Comment) {
last, o.Elements = takeLastCommentIfEndsOnLine(o.Elements, expectedOnLine)
return last
}
// parse expects:
// oneofName "{" { oneofField | emptyStatement } "}"
func (o *Oneof) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "oneof identifier", o)
}
}
o.Name = lit
pos, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "oneof opening {", o)
}
for {
pos, tok, lit = p.nextTypeName()
switch tok {
case tCOMMENT:
if com := mergeOrReturnComment(o.elements(), lit, pos); com != nil { // not merged?
o.addElement(com)
}
case tIDENT:
f := newOneOfField()
f.Position = pos
f.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1) // TODO call takeLastComment instead?
f.Type = lit
if err := parseFieldAfterType(f.Field, p); err != nil {
return err
}
o.addElement(f)
case tGROUP:
g := new(Group)
g.Position = pos
g.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1)
if err := g.parse(p); err != nil {
return err
}
o.addElement(g)
case tOPTION:
opt := new(Option)
opt.Position = pos
opt.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1)
if err := opt.parse(p); err != nil {
return err
}
o.addElement(opt)
case tSEMICOLON:
maybeScanInlineComment(p, o)
// continue
default:
goto done
}
}
done:
if tok != tRIGHTCURLY {
return p.unexpected(lit, "oneof closing }", o)
}
return nil
}
// Accept dispatches the call to the visitor.
func (o *Oneof) Accept(v Visitor) {
v.VisitOneof(o)
}
// OneOfField is part of Oneof.
type OneOfField struct {
*Field
}
func newOneOfField() *OneOfField { return &OneOfField{Field: new(Field)} }
// Accept dispatches the call to the visitor.
func (o *OneOfField) Accept(v Visitor) {
v.VisitOneofField(o)
}
// Doc is part of Documented
// Note: although Doc() is defined on Field, it must be implemented here as well.
func (o *OneOfField) Doc() *Comment {
return o.Comment
}
func (o *Oneof) parent(v Visitee) { o.Parent = v }

View File

@@ -0,0 +1,365 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"bytes"
"fmt"
"sort"
"text/scanner"
)
// Option is a protoc compiler option
type Option struct {
Position scanner.Position
Comment *Comment
Name string
Constant Literal
IsEmbedded bool
// AggregatedConstants is DEPRECATED. These Literals are populated into Constant.OrderedMap
AggregatedConstants []*NamedLiteral
InlineComment *Comment
Parent Visitee
}
// parse reads an Option body
// ( ident | "(" fullIdent ")" ) { "." ident } "=" constant ";"
func (o *Option) parse(p *Parser) error {
pos, tok, lit := p.nextIdentifier()
if tLEFTPAREN == tok {
pos, tok, lit = p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "option full identifier", o)
}
}
pos, tok, _ = p.next()
if tok != tRIGHTPAREN {
return p.unexpected(lit, "option full identifier closing )", o)
}
o.Name = fmt.Sprintf("(%s)", lit)
} else {
// non full ident
if tIDENT != tok {
if !isKeyword(tok) {
return p.unexpected(lit, "option identifier", o)
}
}
o.Name = lit
}
pos, tok, lit = p.next()
if tDOT == tok {
// extend identifier
pos, tok, lit = p.nextIdent(true) // keyword allowed as start
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "option postfix identifier", o)
}
}
o.Name = fmt.Sprintf("%s.%s", o.Name, lit)
pos, tok, lit = p.next()
}
if tEQUALS != tok {
return p.unexpected(lit, "option value assignment =", o)
}
r := p.peekNonWhitespace()
var err error
// values of an option can have illegal escape sequences
// for the standard Go scanner used by this package.
p.ignoreIllegalEscapesWhile(func() {
if '{' == r {
// aggregate
p.next() // consume {
err = o.parseAggregate(p)
} else {
// non aggregate
l := new(Literal)
l.Position = pos
if e := l.parse(p); e != nil {
err = e
}
o.Constant = *l
}
})
return err
}
// inlineComment is part of commentInliner.
func (o *Option) inlineComment(c *Comment) {
o.InlineComment = c
}
// Accept dispatches the call to the visitor.
func (o *Option) Accept(v Visitor) {
v.VisitOption(o)
}
// Doc is part of Documented
func (o *Option) Doc() *Comment {
return o.Comment
}
// Literal represents intLit,floatLit,strLit or boolLit or a nested structure thereof.
type Literal struct {
Position scanner.Position
Source string
IsString bool
// literal value can be an array literal value (even nested)
Array []*Literal
// literal value can be a map of literals (even nested)
// DEPRECATED: use OrderedMap instead
Map map[string]*Literal
// literal value can be a map of literals (even nested)
// this is done as pairs of name keys and literal values so the original ordering is preserved
OrderedMap LiteralMap
}
// LiteralMap is like a map of *Literal but preserved the ordering.
// Can be iterated yielding *NamedLiteral values.
type LiteralMap []*NamedLiteral
// Get returns a Literal from the map.
func (m LiteralMap) Get(key string) (*Literal, bool) {
for _, each := range m {
if each.Name == key {
// exit on the first match
return each.Literal, true
}
}
return new(Literal), false
}
// SourceRepresentation returns the source (if quoted then use double quote).
func (l Literal) SourceRepresentation() string {
var buf bytes.Buffer
if l.IsString {
buf.WriteRune('"')
}
buf.WriteString(l.Source)
if l.IsString {
buf.WriteRune('"')
}
return buf.String()
}
// parse expects to read a literal constant after =.
func (l *Literal) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok == tLEFTSQUARE {
// collect array elements
array := []*Literal{}
for {
e := new(Literal)
if err := e.parse(p); err != nil {
return err
}
array = append(array, e)
_, tok, lit = p.next()
if tok == tCOMMA {
continue
}
if tok == tRIGHTSQUARE {
break
}
return p.unexpected(lit, ", or ]", l)
}
l.Array = array
l.IsString = false
l.Position = pos
return nil
}
if tLEFTCURLY == tok {
l.Position, l.Source, l.IsString = pos, "", false
constants, err := parseAggregateConstants(p, l)
if err != nil {
return nil
}
l.OrderedMap = LiteralMap(constants)
return nil
}
if "-" == lit {
// negative number
if err := l.parse(p); err != nil {
return err
}
// modify source and position
l.Position, l.Source = pos, "-"+l.Source
return nil
}
source := lit
iss := isString(lit)
if iss {
source = unQuote(source)
}
l.Position, l.Source, l.IsString = pos, source, iss
// peek for multiline strings
for {
pos, tok, lit = p.next()
if isString(lit) {
l.Source += unQuote(lit)
} else {
p.nextPut(pos, tok, lit)
break
}
}
return nil
}
// NamedLiteral associates a name with a Literal
type NamedLiteral struct {
*Literal
Name string
// PrintsColon is true when the Name must be printed with a colon suffix
PrintsColon bool
}
// parseAggregate reads options written using aggregate syntax.
// tLEFTCURLY { has been consumed
func (o *Option) parseAggregate(p *Parser) error {
constants, err := parseAggregateConstants(p, o)
literalMap := map[string]*Literal{}
for _, each := range constants {
literalMap[each.Name] = each.Literal
}
o.Constant = Literal{Map: literalMap, OrderedMap: constants, Position: o.Position}
// reconstruct the old, deprecated field
o.AggregatedConstants = collectAggregatedConstants(literalMap)
return err
}
// flatten the maps of each literal, recursively
// this func exists for deprecated Option.AggregatedConstants.
func collectAggregatedConstants(m map[string]*Literal) (list []*NamedLiteral) {
for k, v := range m {
if v.Map != nil {
sublist := collectAggregatedConstants(v.Map)
for _, each := range sublist {
list = append(list, &NamedLiteral{
Name: k + "." + each.Name,
PrintsColon: true,
Literal: each.Literal,
})
}
} else {
list = append(list, &NamedLiteral{
Name: k,
PrintsColon: true,
Literal: v,
})
}
}
// sort list by position of literal
sort.Sort(byPosition(list))
return
}
type byPosition []*NamedLiteral
func (b byPosition) Less(i, j int) bool {
return b[i].Literal.Position.Line < b[j].Literal.Position.Line
}
func (b byPosition) Len() int { return len(b) }
func (b byPosition) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func parseAggregateConstants(p *Parser, container interface{}) (list []*NamedLiteral, err error) {
for {
pos, tok, lit := p.nextIdentifier()
if tRIGHTSQUARE == tok {
p.nextPut(pos, tok, lit)
// caller has checked for open square ; will consume rightsquare, rightcurly and semicolon
return
}
if tRIGHTCURLY == tok {
return
}
if tSEMICOLON == tok {
// just consume it
continue
//return
}
if tCOMMENT == tok {
// assign to last parsed literal
// TODO: see TestUseOfSemicolonsInAggregatedConstants
continue
}
if tCOMMA == tok {
if len(list) == 0 {
err = p.unexpected(lit, "non-empty option aggregate key", container)
return
}
continue
}
if tIDENT != tok && !isKeyword(tok) {
err = p.unexpected(lit, "option aggregate key", container)
return
}
// workaround issue #59 TODO
if isString(lit) && len(list) > 0 {
// concatenate with previous constant
list[len(list)-1].Source += unQuote(lit)
continue
}
key := lit
printsColon := false
// expect colon, aggregate or plain literal
pos, tok, lit = p.next()
if tCOLON == tok {
// consume it
printsColon = true
pos, tok, lit = p.next()
}
// see if nested aggregate is started
if tLEFTCURLY == tok {
nested, fault := parseAggregateConstants(p, container)
if fault != nil {
err = fault
return
}
// create the map
m := map[string]*Literal{}
for _, each := range nested {
m[each.Name] = each.Literal
}
list = append(list, &NamedLiteral{
Name: key,
PrintsColon: printsColon,
Literal: &Literal{Map: m, OrderedMap: LiteralMap(nested)}})
continue
}
// no aggregate, put back token
p.nextPut(pos, tok, lit)
// now we see plain literal
l := new(Literal)
l.Position = pos
if err = l.parse(p); err != nil {
return
}
list = append(list, &NamedLiteral{Name: key, Literal: l, PrintsColon: printsColon})
}
}
func (o *Option) parent(v Visitee) { o.Parent = v }

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import "text/scanner"
// Package specifies the namespace for all proto elements.
type Package struct {
Position scanner.Position
Comment *Comment
Name string
InlineComment *Comment
Parent Visitee
}
// Doc is part of Documented
func (p *Package) Doc() *Comment {
return p.Comment
}
func (p *Package) parse(pr *Parser) error {
_, tok, lit := pr.nextIdent(true)
if tIDENT != tok {
if !isKeyword(tok) {
return pr.unexpected(lit, "package identifier", p)
}
}
p.Name = lit
return nil
}
// Accept dispatches the call to the visitor.
func (p *Package) Accept(v Visitor) {
v.VisitPackage(p)
}
// inlineComment is part of commentInliner.
func (p *Package) inlineComment(c *Comment) {
p.InlineComment = c
}
func (p *Package) parent(v Visitee) { p.Parent = v }

View File

@@ -0,0 +1,211 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"bytes"
"errors"
"fmt"
"io"
"runtime"
"strconv"
"strings"
"text/scanner"
)
// Parser represents a parser.
type Parser struct {
debug bool
scanner *scanner.Scanner
buf *nextValues
scannerErrors []error
}
// nextValues is to capture the result of next()
type nextValues struct {
pos scanner.Position
tok token
lit string
}
// NewParser returns a new instance of Parser.
func NewParser(r io.Reader) *Parser {
s := new(scanner.Scanner)
s.Init(r)
s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments
p := &Parser{scanner: s}
s.Error = p.handleScanError
return p
}
// handleScanError is called from the underlying Scanner
func (p *Parser) handleScanError(s *scanner.Scanner, msg string) {
p.scannerErrors = append(p.scannerErrors,
fmt.Errorf("go scanner error at %v = %v", s.Position, msg))
}
// ignoreIllegalEscapesWhile is called for scanning constants of an option.
// Such content can have a syntax that is not acceptable by the Go scanner.
// This temporary installs a handler that ignores only one type of error: illegal char escape
func (p *Parser) ignoreIllegalEscapesWhile(block func()) {
// during block call change error handler
p.scanner.Error = func(s *scanner.Scanner, msg string) {
if strings.Contains(msg, "illegal char escape") { // too bad there is no constant for this in scanner pkg
return
}
p.handleScanError(s, msg)
}
block()
// restore
p.scanner.Error = p.handleScanError
}
// Parse parses a proto definition. May return a parse or scanner error.
func (p *Parser) Parse() (*Proto, error) {
proto := new(Proto)
if p.scanner.Filename != "" {
proto.Filename = p.scanner.Filename
}
pro, parseError := proto.parse(p)
// see if it was a scanner error
if len(p.scannerErrors) > 0 {
buf := new(bytes.Buffer)
for _, each := range p.scannerErrors {
fmt.Fprintln(buf, each)
}
return proto, errors.New(buf.String())
}
return pro, parseError
}
// Filename is for reporting. Optional.
func (p *Parser) Filename(f string) {
p.scanner.Filename = f
}
// next returns the next token using the scanner or drain the buffer.
func (p *Parser) next() (pos scanner.Position, tok token, lit string) {
if p.buf != nil {
// consume buf
vals := *p.buf
p.buf = nil
return vals.pos, vals.tok, vals.lit
}
ch := p.scanner.Scan()
if ch == scanner.EOF {
return p.scanner.Position, tEOF, ""
}
lit = p.scanner.TokenText()
return p.scanner.Position, asToken(lit), lit
}
// nextPut sets the buffer
func (p *Parser) nextPut(pos scanner.Position, tok token, lit string) {
p.buf = &nextValues{pos, tok, lit}
}
func (p *Parser) unexpected(found, expected string, obj interface{}) error {
debug := ""
if p.debug {
_, file, line, _ := runtime.Caller(1)
debug = fmt.Sprintf(" at %s:%d (with %#v)", file, line, obj)
}
return fmt.Errorf("%v: found %q but expected [%s]%s", p.scanner.Position, found, expected, debug)
}
func (p *Parser) nextInteger() (i int, err error) {
_, tok, lit := p.next()
if "-" == lit {
i, err = p.nextInteger()
return i * -1, err
}
if tok != tIDENT {
return 0, errors.New("non integer")
}
if strings.HasPrefix(lit, "0x") {
// hex decode
var i64 int64
i64, err = strconv.ParseInt(lit, 0, 64)
return int(i64), err
}
i, err = strconv.Atoi(lit)
return
}
// nextIdentifier consumes tokens which may have one or more dot separators (namespaced idents).
func (p *Parser) nextIdentifier() (pos scanner.Position, tok token, lit string) {
return p.nextIdent(false)
}
// nextTypeName implements the Packages and Name Resolution for finding the name of the type.
func (p *Parser) nextTypeName() (pos scanner.Position, tok token, lit string) {
pos, tok, lit = p.nextIdent(false)
if tDOT == tok {
// leading dot allowed
pos, tok, lit = p.nextIdent(false)
lit = "." + lit
}
return
}
func (p *Parser) nextIdent(keywordStartAllowed bool) (pos scanner.Position, tok token, lit string) {
pos, tok, lit = p.next()
if tIDENT != tok {
// can be keyword
if !(isKeyword(tok) && keywordStartAllowed) {
return
}
// proceed with keyword as first literal
}
startPos := pos
fullLit := lit
// see if identifier is namespaced
for {
r := p.scanner.Peek()
if '.' != r {
break
}
p.next() // consume dot
pos, tok, lit := p.next()
if tIDENT != tok && !isKeyword(tok) {
p.nextPut(pos, tok, lit)
break
}
fullLit = fmt.Sprintf("%s.%s", fullLit, lit)
}
return startPos, tIDENT, fullLit
}
func (p *Parser) peekNonWhitespace() rune {
r := p.scanner.Peek()
if r == scanner.EOF {
return r
}
if isWhitespace(r) {
// consume it
p.scanner.Next()
return p.peekNonWhitespace()
}
return r
}

View File

@@ -0,0 +1,170 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
// Proto represents a .proto definition
type Proto struct {
Filename string
Elements []Visitee
Imports []Import
Enums []Enum
Package []Package
Options []Option
Messages []Message
Services []Service
Extends []Extensions
}
// Accept dispatches the call to the visitor.
func (proto *Proto) Accept(v Visitor) {
// As Proto is not (yet) a Visitee, we enumerate its elements instead
//v.VisitProto(proto)
for _, each := range proto.Elements {
each.Accept(v)
}
}
// addElement is part of elementContainer
func (proto *Proto) addElement(v Visitee) {
v.parent(proto)
proto.Elements = append(proto.Elements, v)
}
// elements is part of elementContainer
func (proto *Proto) elements() []Visitee {
return proto.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last element of the list if it is a Comment.
func (proto *Proto) takeLastComment(expectedOnLine int) (last *Comment) {
last, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, expectedOnLine)
return
}
// parse parsers a complete .proto definition source.
func (proto *Proto) parse(p *Parser) (*Proto, error) {
pro := new(Proto)
for {
pos, tok, lit := p.next()
switch {
case isComment(lit):
if com := mergeOrReturnComment(proto.Elements, lit, pos); com != nil { // not merged?
proto.Elements = append(proto.Elements, com)
}
case tOPTION == tok:
o := new(Option)
o.Position = pos
o.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := o.parse(p); err != nil {
return pro, err
}
pro.Options = append(pro.Options, *o)
proto.addElement(o)
case tSYNTAX == tok:
s := new(Syntax)
s.Position = pos
s.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := s.parse(p); err != nil {
return pro, err
}
proto.addElement(s)
case tIMPORT == tok:
im := new(Import)
im.Position = pos
im.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := im.parse(p); err != nil {
return pro, err
}
pro.Imports = append(pro.Imports, *im)
proto.addElement(im)
case tENUM == tok:
enum := new(Enum)
enum.Position = pos
enum.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := enum.parse(p); err != nil {
return pro, err
}
pro.Enums = append(pro.Enums, *enum)
proto.addElement(enum)
case tSERVICE == tok:
service := new(Service)
service.Position = pos
service.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
err := service.parse(p)
if err != nil {
return pro, err
}
pro.Services = append(pro.Services, *service)
proto.addElement(service)
case tPACKAGE == tok:
pkg := new(Package)
pkg.Position = pos
pkg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := pkg.parse(p); err != nil {
return pro, err
}
pro.Package = append(pro.Package, *pkg)
proto.addElement(pkg)
case tMESSAGE == tok:
msg := new(Message)
msg.Position = pos
msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
if err := msg.parse(p); err != nil {
return pro, err
}
pro.Messages = append(pro.Messages, *msg)
proto.addElement(msg)
// BEGIN proto2
case tEXTEND == tok:
msg := new(Message)
msg.Position = pos
msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1)
msg.IsExtend = true
if err := msg.parse(p); err != nil {
return pro, err
}
proto.addElement(msg)
// END proto2
case tSEMICOLON == tok:
maybeScanInlineComment(p, proto)
// continue
case tEOF == tok:
goto done
default:
return pro, p.unexpected(lit, ".proto element {comment|option|import|syntax|enum|service|package|message}", p)
}
}
done:
return pro, nil
}
func (proto *Proto) parent(v Visitee) {}
// elementContainer unifies types that have elements.
type elementContainer interface {
addElement(v Visitee)
elements() []Visitee
takeLastComment(expectedOnLine int) *Comment
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"fmt"
"strconv"
)
// Range is to specify number intervals (with special end value "max")
type Range struct {
From, To int
Max bool
}
// SourceRepresentation return a single number if from = to. Returns <from> to <to> otherwise unless Max then return <from> to max.
func (r Range) SourceRepresentation() string {
if r.Max {
return fmt.Sprintf("%d to max", r.From)
}
if r.From == r.To {
return strconv.Itoa(r.From)
}
return fmt.Sprintf("%d to %d", r.From, r.To)
}
// parseRanges is used to parse ranges for extensions and reserved
func parseRanges(p *Parser, n Visitee) (list []Range, err error) {
seenTo := false
for {
pos, tok, lit := p.next()
if isString(lit) {
return list, p.unexpected(lit, "integer, <to> <max>", n)
}
switch lit {
case ",":
case "to":
seenTo = true
case ";":
p.nextPut(pos, tok, lit) // allow for inline comment parsing
goto done
case "max":
if !seenTo {
return list, p.unexpected(lit, "to", n)
}
from := list[len(list)-1]
list = append(list[0:len(list)-1], Range{From: from.From, Max: true})
default:
// must be number
i, err := strconv.Atoi(lit)
if err != nil {
return list, p.unexpected(lit, "range integer", n)
}
if seenTo {
// replace last two ranges with one
if len(list) < 1 {
p.unexpected(lit, "integer", n)
}
from := list[len(list)-1]
list = append(list[0:len(list)-1], Range{From: from.From, To: i})
seenTo = false
} else {
list = append(list, Range{From: i, To: i})
}
}
}
done:
return
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import "text/scanner"
// Reserved statements declare a range of field numbers or field names that cannot be used in a message.
type Reserved struct {
Position scanner.Position
Comment *Comment
Ranges []Range
FieldNames []string
InlineComment *Comment
Parent Visitee
}
// inlineComment is part of commentInliner.
func (r *Reserved) inlineComment(c *Comment) {
r.InlineComment = c
}
// Accept dispatches the call to the visitor.
func (r *Reserved) Accept(v Visitor) {
v.VisitReserved(r)
}
func (r *Reserved) parse(p *Parser) error {
for {
pos, tok, lit := p.next()
if len(lit) == 0 {
return p.unexpected(lit, "reserved string or integer", r)
}
// first char that determined tok
ch := []rune(lit)[0]
if isDigit(ch) {
// use unread here because it could be start of ranges
p.nextPut(pos, tok, lit)
list, err := parseRanges(p, r)
if err != nil {
return err
}
r.Ranges = list
continue
}
if isString(lit) {
r.FieldNames = append(r.FieldNames, unQuote(lit))
continue
}
if tSEMICOLON == tok {
p.nextPut(pos, tok, lit)
break
}
}
return nil
}
func (r *Reserved) parent(v Visitee) { r.Parent = v }

View File

@@ -0,0 +1,257 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Service defines a set of RPC calls.
type Service struct {
Position scanner.Position
Comment *Comment
Name string
Elements []Visitee
Parent Visitee
RPCElements []RPC
}
// Accept dispatches the call to the visitor.
func (s *Service) Accept(v Visitor) {
v.VisitService(s)
}
// Doc is part of Documented
func (s *Service) Doc() *Comment {
return s.Comment
}
// addElement is part of elementContainer
func (s *Service) addElement(v Visitee) {
v.parent(s)
s.Elements = append(s.Elements, v)
}
// elements is part of elementContainer
func (s *Service) elements() []Visitee {
return s.Elements
}
// takeLastComment is part of elementContainer
// removes and returns the last elements of the list if it is a Comment.
func (s *Service) takeLastComment(expectedOnLine int) (last *Comment) {
last, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, expectedOnLine)
return
}
// parse continues after reading "service"
func (s *Service) parse(p *Parser) error {
pos, tok, lit := p.nextIdentifier()
if tok != tIDENT {
if !isKeyword(tok) {
return p.unexpected(lit, "service identifier", s)
}
}
s.Name = lit
pos, tok, lit = p.next()
if tok != tLEFTCURLY {
return p.unexpected(lit, "service opening {", s)
}
for {
pos, tok, lit = p.next()
switch tok {
case tCOMMENT:
if com := mergeOrReturnComment(s.Elements, lit, pos); com != nil { // not merged?
s.addElement(com)
}
case tOPTION:
opt := new(Option)
opt.Position = pos
opt.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.elements(), pos.Line-1)
if err := opt.parse(p); err != nil {
return err
}
s.addElement(opt)
case tRPC:
rpc := new(RPC)
rpc.Position = pos
rpc.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, pos.Line-1)
err := rpc.parse(p)
if err != nil {
return err
}
s.RPCElements = append(s.RPCElements, *rpc)
s.addElement(rpc)
maybeScanInlineComment(p, s)
case tSEMICOLON:
maybeScanInlineComment(p, s)
case tRIGHTCURLY:
goto done
default:
return p.unexpected(lit, "service comment|rpc", s)
}
}
done:
return nil
}
func (s *Service) parent(v Visitee) { s.Parent = v }
// RPC represents an rpc entry in a message.
type RPC struct {
Position scanner.Position
Comment *Comment
Name string
RequestType string
StreamsRequest bool
ReturnsType string
StreamsReturns bool
Elements []Visitee
InlineComment *Comment
Parent Visitee
// Options field is DEPRECATED, use Elements instead.
Options []*Option
}
// Accept dispatches the call to the visitor.
func (r *RPC) Accept(v Visitor) {
v.VisitRPC(r)
}
// Doc is part of Documented
func (r *RPC) Doc() *Comment {
return r.Comment
}
// inlineComment is part of commentInliner.
func (r *RPC) inlineComment(c *Comment) {
r.InlineComment = c
}
// parse continues after reading "rpc"
func (r *RPC) parse(p *Parser) error {
pos, tok, lit := p.next()
if tok != tIDENT {
return p.unexpected(lit, "rpc method", r)
}
r.Name = lit
pos, tok, lit = p.next()
if tok != tLEFTPAREN {
return p.unexpected(lit, "rpc type opening (", r)
}
pos, tok, lit = p.nextIdentifier()
if tSTREAM == tok {
r.StreamsRequest = true
pos, tok, lit = p.nextIdentifier()
}
if tok != tIDENT {
return p.unexpected(lit, "rpc stream | request type", r)
}
r.RequestType = lit
pos, tok, lit = p.next()
if tok != tRIGHTPAREN {
return p.unexpected(lit, "rpc type closing )", r)
}
pos, tok, lit = p.next()
if tok != tRETURNS {
return p.unexpected(lit, "rpc returns", r)
}
pos, tok, lit = p.next()
if tok != tLEFTPAREN {
return p.unexpected(lit, "rpc type opening (", r)
}
pos, tok, lit = p.nextIdentifier()
if tSTREAM == tok {
r.StreamsReturns = true
pos, tok, lit = p.nextIdentifier()
}
if tok == tDOT {
pos, tok, lit = p.nextIdentifier()
}
if tok != tIDENT {
return p.unexpected(lit, "rpc stream | returns type", r)
}
r.ReturnsType = lit
pos, tok, lit = p.next()
if tok != tRIGHTPAREN {
return p.unexpected(lit, "rpc type closing )", r)
}
pos, tok, lit = p.next()
if tSEMICOLON == tok {
p.nextPut(pos, tok, lit) // allow for inline comment parsing
return nil
}
if tLEFTCURLY == tok {
// parse options
for {
pos, tok, lit = p.next()
if tRIGHTCURLY == tok {
break
}
if isComment(lit) {
if com := mergeOrReturnComment(r.elements(), lit, pos); com != nil { // not merged?
r.addElement(com)
continue
}
}
if tSEMICOLON == tok {
maybeScanInlineComment(p, r)
continue
}
if tOPTION == tok {
o := new(Option)
o.Position = pos
if err := o.parse(p); err != nil {
return err
}
r.addElement(o)
}
}
}
return nil
}
// addElement is part of elementContainer
func (r *RPC) addElement(v Visitee) {
v.parent(r)
r.Elements = append(r.Elements, v)
// handle deprecated field
if option, ok := v.(*Option); ok {
r.Options = append(r.Options, option)
}
}
// elements is part of elementContainer
func (r *RPC) elements() []Visitee {
return r.Elements
}
func (r *RPC) takeLastComment(expectedOnLine int) (last *Comment) {
last, r.Elements = takeLastCommentIfEndsOnLine(r.Elements, expectedOnLine)
return
}
func (r *RPC) parent(v Visitee) { r.Parent = v }

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"text/scanner"
)
// Syntax should have value "proto"
type Syntax struct {
Position scanner.Position
Comment *Comment
Value string
InlineComment *Comment
Parent Visitee
}
func (s *Syntax) parse(p *Parser) error {
if _, tok, lit := p.next(); tok != tEQUALS {
return p.unexpected(lit, "syntax =", s)
}
_, _, lit := p.next()
if !isString(lit) {
return p.unexpected(lit, "syntax string constant", s)
}
s.Value = unQuote(lit)
return nil
}
// Accept dispatches the call to the visitor.
func (s *Syntax) Accept(v Visitor) {
v.VisitSyntax(s)
}
// Doc is part of Documented
func (s *Syntax) Doc() *Comment {
return s.Comment
}
// inlineComment is part of commentInliner.
func (s *Syntax) inlineComment(c *Comment) {
s.InlineComment = c
}
func (s *Syntax) parent(v Visitee) { s.Parent = v }

View File

@@ -0,0 +1,212 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
import (
"strings"
)
// token represents a lexical token.
type token int
const (
// Special tokens
tILLEGAL token = iota
tEOF
tWS
// Literals
tIDENT
// Misc characters
tSEMICOLON // ;
tCOLON // :
tEQUALS // =
tQUOTE // "
tSINGLEQUOTE // '
tLEFTPAREN // (
tRIGHTPAREN // )
tLEFTCURLY // {
tRIGHTCURLY // }
tLEFTSQUARE // [
tRIGHTSQUARE // ]
tCOMMENT // /
tLESS // <
tGREATER // >
tCOMMA // ,
tDOT // .
// Keywords
keywordsStart
tSYNTAX
tSERVICE
tRPC
tRETURNS
tMESSAGE
tIMPORT
tPACKAGE
tOPTION
tREPEATED
tWEAK
tPUBLIC
// special fields
tONEOF
tMAP
tRESERVED
tENUM
tSTREAM
// BEGIN proto2
tOPTIONAL
tGROUP
tEXTENSIONS
tEXTEND
tREQUIRED
// END proto2
keywordsEnd
)
// typeTokens exists for future validation
// const typeTokens = "double float int32 int64 uint32 uint64 sint32 sint64 fixed32 sfixed32 sfixed64 bool string bytes"
// isKeyword returns if tok is in the keywords range
func isKeyword(tok token) bool {
return keywordsStart < tok && tok < keywordsEnd
}
// isWhitespace checks for space,tab and newline
func isWhitespace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n'
}
// isDigit returns true if the rune is a digit.
func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') }
// isString checks if the literal is quoted (single or double).
func isString(lit string) bool {
return (strings.HasPrefix(lit, "\"") &&
strings.HasSuffix(lit, "\"")) ||
(strings.HasPrefix(lit, "'") &&
strings.HasSuffix(lit, "'"))
}
func isComment(lit string) bool {
return strings.HasPrefix(lit, "//") || strings.HasPrefix(lit, "/*")
}
func unQuote(lit string) string {
return strings.Trim(lit, "\"'")
}
func asToken(literal string) token {
switch literal {
// delimiters
case ";":
return tSEMICOLON
case ":":
return tCOLON
case "=":
return tEQUALS
case "\"":
return tQUOTE
case "'":
return tSINGLEQUOTE
case "(":
return tLEFTPAREN
case ")":
return tRIGHTPAREN
case "{":
return tLEFTCURLY
case "}":
return tRIGHTCURLY
case "[":
return tLEFTSQUARE
case "]":
return tRIGHTSQUARE
case "<":
return tLESS
case ">":
return tGREATER
case ",":
return tCOMMA
case ".":
return tDOT
// words
case "syntax":
return tSYNTAX
case "service":
return tSERVICE
case "rpc":
return tRPC
case "returns":
return tRETURNS
case "option":
return tOPTION
case "message":
return tMESSAGE
case "import":
return tIMPORT
case "package":
return tPACKAGE
case "oneof":
return tONEOF
// special fields
case "map":
return tMAP
case "reserved":
return tRESERVED
case "enum":
return tENUM
case "repeated":
return tREPEATED
case "weak":
return tWEAK
case "public":
return tPUBLIC
case "stream":
return tSTREAM
// proto2
case "optional":
return tOPTIONAL
case "group":
return tGROUP
case "extensions":
return tEXTENSIONS
case "extend":
return tEXTEND
case "required":
return tREQUIRED
case "ws":
return tWS
case "ill":
return tILLEGAL
default:
// special cases
if isComment(literal) {
return tCOMMENT
}
return tIDENT
}
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
// Visitor is for dispatching Proto elements.
type Visitor interface {
//VisitProto(p *Proto)
VisitMessage(m *Message)
VisitService(v *Service)
VisitSyntax(s *Syntax)
VisitPackage(p *Package)
VisitOption(o *Option)
VisitImport(i *Import)
VisitNormalField(i *NormalField)
VisitEnumField(i *EnumField)
VisitEnum(e *Enum)
VisitComment(e *Comment)
VisitOneof(o *Oneof)
VisitOneofField(o *OneOfField)
VisitReserved(r *Reserved)
VisitRPC(r *RPC)
VisitMapField(f *MapField)
// proto2
VisitGroup(g *Group)
VisitExtensions(e *Extensions)
}
// Visitee is implemented by all Proto elements.
type Visitee interface {
Accept(v Visitor)
parent(e Visitee)
}
// Documented is for types that may have an associated comment (not inlined).
type Documented interface {
Doc() *Comment
}

View File

@@ -0,0 +1,97 @@
// Copyright (c) 2018 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package proto
// Handler is a type of function that accepts a Visitee.
type Handler func(v Visitee)
// Walk recursively pays a visit to all Visitees of a Proto and calls each handler with it.
func Walk(proto *Proto, handlers ...Handler) {
walk(proto, handlers...)
}
func walk(container elementContainer, handlers ...Handler) {
for _, eachElement := range container.elements() {
for _, eachFilter := range handlers {
eachFilter(eachElement)
}
if next, ok := eachElement.(elementContainer); ok {
walk(next, handlers...)
}
}
}
// WithMessage returns a Handler that will call the apply function when the Visitee is a Message.
func WithMessage(apply func(*Message)) Handler {
return func(v Visitee) {
if s, ok := v.(*Message); ok {
apply(s)
}
}
}
// WithOption returns a Handler that will call the apply function when the Visitee is a Option.
func WithOption(apply func(*Option)) Handler {
return func(v Visitee) {
if s, ok := v.(*Option); ok {
apply(s)
}
}
}
// WithEnum returns a Handler that will call the apply function when the Visitee is a Enum.
func WithEnum(apply func(*Enum)) Handler {
return func(v Visitee) {
if s, ok := v.(*Enum); ok {
apply(s)
}
}
}
// WithOneof returns a Handler that will call the apply function when the Visitee is a Oneof.
func WithOneof(apply func(*Oneof)) Handler {
return func(v Visitee) {
if s, ok := v.(*Oneof); ok {
apply(s)
}
}
}
// WithService returns a Handler that will call the apply function when the Visitee is a Service.
func WithService(apply func(*Service)) Handler {
return func(v Visitee) {
if s, ok := v.(*Service); ok {
apply(s)
}
}
}
// WithRPC returns a Handler that will call the apply function when the Visitee is a RPC.
func WithRPC(apply func(*RPC)) Handler {
return func(v Visitee) {
if s, ok := v.(*RPC); ok {
apply(s)
}
}
}

View File

@@ -0,0 +1,691 @@
package service
import (
"context"
"fmt"
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/melloi/conf"
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
//DoPtest do post
func (s *Service) DoPtest(c context.Context, ptestParam model.DoPtestParam) (resp model.DoPtestResp, err error) {
var (
testNameNick string
testNameNicks []string
)
if ptestParam.TestNameNick == "" {
tim := strconv.FormatInt(time.Now().Unix(), 10)
for _, testName := range ptestParam.TestNames {
testNameNick = testName + tim
testNameNicks = append(testNameNicks, testNameNick)
}
}
return s.DoPtestByJmeter(c, ptestParam, testNameNicks)
}
//StopPtest stop test
func (s *Service) StopPtest(c context.Context, ptestJob model.PtestJob) (err error) {
var (
jobNames []string
ptestJobs []*model.PtestJob
)
ptestJob.Active = 1
if ptestJobs, err = s.dao.QueryPtestJob(&ptestJob); err != nil {
log.Error("s.dao.QueryPtestJob err :(%v)", err)
return
}
for _, ptestJob := range ptestJobs {
jobNames = append(jobNames, ptestJob.JobName)
}
for _, jobName := range jobNames {
s.DeleteJob(context.TODO(), jobName)
}
return
}
//ReducePtest reduce ptest
func (s *Service) ReducePtest(c context.Context, reducePtest model.ReducePtest) (message string, err error) {
if reducePtest.JobName == "" {
message = "请输入 jobName"
return
}
if _, err = s.DeleteJob(c, reducePtest.JobName); err != nil {
message = "调用删除 job 接口失败"
log.Error("s.DeleteJob err :(%v)", err)
return
}
if err = s.dao.DeletePtestJob(reducePtest.ID); err != nil {
message = "sql 执行失败"
log.Error("s.dao.DeletePtestJob err :(%v)", err)
return
}
message = "success"
return
}
//QueryAllJobFree Query AllJob Free
func (s *Service) QueryAllJobFree(c context.Context, ptesJob *model.PtestJob) ([]*model.PtestJob, error) {
return s.dao.QueryPtestJob(ptesJob)
}
//QueryOrStopAllPtestByJobName query or stop all test by job name
func (s *Service) QueryOrStopAllPtestByJobName(c context.Context, reportSuID int, IsDelete bool, testStatus int) (ptestjobsd []*model.PtestJob, err error) {
// 删除所有正在允许此接口的容器
ptestJobd := model.PtestJob{ReportSuID: reportSuID, Active: 1}
if ptestjobsd, err = s.dao.QueryPtestJob(&ptestJobd); err != nil {
log.Error("s.dao.QueryPtestJob err (%v)", err)
return
}
if IsDelete {
for _, ptestJob := range ptestjobsd {
if _, err = s.DeleteJob(context.TODO(), ptestJob.JobName); err != nil {
log.Error("s.DeleteJob err :(%v)", err)
return
}
if err = s.dao.DeletePtestJob(ptestJob.ID); err != nil {
log.Error("s.dao.DeletePtestJob err (%v)", err)
return
}
if err = s.dao.UpdateReportStatusByID(reportSuID, testStatus); err != nil {
log.Error("s.UpdateReportStatusByID err :(%v)", err)
return
}
}
}
return
}
//DoAddPtest doadd test stress
func (s *Service) DoAddPtest(c context.Context, ptestAdd model.PtestAdd) (status string, err error) {
status = "fail"
var (
resp model.DoPtestResp
grpc *model.GRPC
scripts []*model.Script
resportSumm []*model.ReportSummary
)
ptestParam := model.DoPtestParam{}
// 此处 ScriptType 取自 reportSummary , 0-http 1-grpc 2-scene
if ptestAdd.ScriptType == model.PROTOCOL_GRPC {
if grpc, err = s.QueryGrpcById(ptestAdd.ScriptID); err != nil {
log.Error("Query GRPC err: (%v)", err)
return status, err
}
ptestParam = model.DoPtestParam{
UserName: grpc.UpdateBy, // 用户名
LoadTime: grpc.LoadTime, //运行时间
TestNames: StringToSlice(grpc.TaskName), //接口名转数组
FileName: grpc.JmxPath, // jmx文件
ResJtl: ptestAdd.ResJtl, // jtl时间戳
JmeterLog: ptestAdd.JmeterLog, // jmeterlog时间戳
Department: grpc.Department,
Project: grpc.Project,
IsDebug: false,
APP: grpc.APP,
ScriptID: grpc.ID,
Cookie: "", // 用不到
URL: grpc.ServiceName, // 用于微信通知
LabelIDs: nil,
Domain: grpc.HostName, // 微信通知Domain
FileSplit: false, // 文件切割
SplitNum: 0, // 切割数量
JarPath: grpc.JarPath,
Type: model.PROTOCOL_GRPC, //grpc
AddPtest: true, // 加压
}
}
if ptestAdd.ScriptType == model.PROTOCOL_SCENE {
ptestScene := model.DoPtestSceneParam{
SceneID: ptestAdd.SceneId,
UserName: ptestAdd.UserName,
}
if resp, err = s.DoScenePtest(c, ptestScene, true, ""); err != nil {
log.Error("s.DoScenePtest err :(%v)", err)
return status, err
}
}
if ptestAdd.ScriptType == model.PROTOCOL_HTTP {
script := model.Script{ID: ptestAdd.ScriptID}
if scripts, err = s.QueryScripts(&script, 1, 5); err != nil {
log.Error("QueryScripts err :(%v)", err)
return status, err
}
if len(scripts) > 0 {
// http 模板
ptestParam = model.DoPtestParam{
UserName: script.UpdateBy,
TestNames: QueryTestNamesByJmfile(scripts[0].SavePath),
FileName: scripts[0].SavePath,
LoadTime: 1800,
Upload: scripts[0].Upload,
ProjectName: scripts[0].ProjectName,
ResJtl: ptestAdd.ResJtl,
JmeterLog: ptestAdd.JmeterLog,
Department: scripts[0].Department,
Project: scripts[0].Project,
APP: scripts[0].App,
ScriptID: scripts[0].ID,
AddPtest: true,
Domain: scripts[0].Domain,
FileSplit: scripts[0].FileSplit,
SplitNum: scripts[0].SplitNum,
DockerSum: ptestAdd.DockerSum,
Type: model.PROTOCOL_HTTP,
APIHeader: scripts[0].APIHeader,
}
}
}
if ptestAdd.ScriptType != model.PROTOCOL_SCENE {
if resp, err = s.DoPtestByJmeter(c, ptestParam, nil); err != nil {
log.Error("DoPtestByJmeter err: (%v)", err)
return
}
log.Info("add ---jobName:(%s)", resp.JobName)
}
status = "success"
// 更新reportSummary数据表
reportSu := model.ReportSummary{ID: ptestAdd.ReportSuID}
if resportSumm, err = s.dao.QueryReportSurys(&reportSu); err != nil {
return
}
ptestJob := model.PtestJob{JobName: resp.JobName, ScriptID: ptestAdd.ScriptID, ReportSuID: ptestAdd.ReportSuID, Active: 1, ExecuteID: ptestAdd.ExecuteID, HostIP: resp.HostIP}
log.Info("add ---jobName:(%s)", resp.JobName)
if _, err = s.dao.AddPtestJob(&ptestJob); err != nil {
log.Error("s.dao.AddPtestJob err (%v)", err)
return
}
DockerSum := resportSumm[0].DockerSum + 1
if err = s.dao.UpdateReportDockByID(resportSumm[0].ID, DockerSum); err != nil {
log.Error("s.dao.UpdateReportDockByID err (%v)", err)
return
}
return
}
//AddPtest add ptest
func (s *Service) AddPtest(c context.Context, ptestAdd model.PtestAdd) (err error) {
go s.DoAddPtestWithSleep(c, ptestAdd)
return
}
//DoAddPtestWithSleep Do AddPtestWith Sleep
func (s *Service) DoAddPtestWithSleep(c context.Context, ptestAdd model.PtestAdd) (err error) {
var ptestJobs []*model.PtestJob
if ptestAdd.DockerNum == 0 {
ptestAdd.DockerNum = 1
}
if ptestAdd.SleepTime == 0 {
ptestAdd.SleepTime = 1
}
for i := 1; i <= ptestAdd.DockerNum; i++ {
time.Sleep(time.Duration(ptestAdd.SleepTime) * time.Second)
// 判断主容器状态,主容器被删除,则不再添加新的容器
ptestJob := model.PtestJob{JobName: ptestAdd.JobName}
if ptestJobs, err = s.dao.QueryPtestJob(&ptestJob); err != nil {
log.Error("s.dao.QueryPtestJob err :(%v)", err)
}
if len(ptestJobs) > 0 {
if ptestJobs[0].Active != -1 {
go s.DoAddPtest(context.TODO(), ptestAdd)
}
}
}
return
}
//DoPtestByFile do test by file
func (s *Service) DoPtestByFile(c context.Context, script model.Script, resJtl, jmeterLog string) (resp model.DoPtestResp, err error) {
var scripts []*model.Script
if scripts, err = s.QueryScripts(&script, 1, 5); err != nil {
log.Error("QueryScripts err :(%v)", err)
return
}
scriptID := scripts[0].ID
testNames := QueryTestNamesByJmfile(scripts[0].SavePath)
ptestParam := model.DoPtestParam{
UserName: script.UpdateBy,
TestNames: testNames,
FileName: scripts[0].SavePath,
LoadTime: 1800,
Upload: scripts[0].Upload,
ProjectName: scripts[0].ProjectName,
ResJtl: resJtl,
JmeterLog: jmeterLog,
Department: scripts[0].Department,
Project: scripts[0].Project,
APP: scripts[0].App,
ScriptID: scripts[0].ID,
Fusing: scripts[0].Fusing,
UseBusinessStop: scripts[0].UseBusinessStop,
BusinessStopPercent: scripts[0].BusinessStopPercent,
}
if resp, err = s.DoPtest(context.TODO(), ptestParam); err != nil {
log.Error("DoPtest error :(%v)", err)
return
}
resp.ScriptID = scriptID
return
}
//DoPtestArr QueryScriptsInID
func (s *Service) DoPtestArr(c context.Context, PtestBatch model.PtestBatch, cookie string) (status string, err error) {
var scripts []*model.Script
if scripts, err = s.dao.QueryScriptsInID(PtestBatch.IDArr); err != nil {
status = "fail"
log.Error("QueryScriptsInID err --- :(%v) ", err)
return status, err
}
for _, script := range scripts {
JmeterLog := script.JmeterLog + strconv.FormatInt(time.Now().Unix(), 10)
ResJtl := script.ResJtl + strconv.FormatInt(time.Now().Unix(), 10)
ptestParam := model.DoPtestParam{
UserName: PtestBatch.UserName,
FileName: script.SavePath,
ProjectName: script.ProjectName,
Upload: script.Upload,
ResJtl: ResJtl,
JmeterLog: JmeterLog,
Department: script.Department,
Project: script.Project,
APP: script.App,
ScriptID: script.ID,
URL: script.URL,
Cookie: cookie,
Domain: script.Domain,
FileSplit: script.FileSplit,
SplitNum: script.SplitNum,
Fusing: script.Fusing,
APIHeader: script.APIHeader,
}
if script.Upload {
ptestParam.LoadTime = 1800
ptestParam.TestNames = QueryTestNamesByJmfile(script.SavePath)
} else {
ptestParam.LoadTime = script.LoadTime
ptestParam.TestNames = StringToSlice(script.TestName)
}
go s.DoPtest(context.TODO(), ptestParam)
}
status = "success"
return
}
//DoPtestByScriptId do ptest by scriptid
func (s *Service) DoPtestByScriptId(c context.Context, script *model.Script, cookie, executor string) (resp model.DoPtestResp, err error) {
var scripts []*model.Script
if scripts, err = s.QueryScripts(script, 1, 5); err != nil {
log.Error("QueryScripts err :(%v)", err)
return
}
ptestParam := model.DoPtestParam{
TestNames: StringToSlice(scripts[0].TestName),
ProjectName: scripts[0].ProjectName,
FileName: scripts[0].SavePath,
LoadTime: scripts[0].LoadTime,
ResJtl: scripts[0].ResJtl,
JmeterLog: scripts[0].JmeterLog,
UserName: executor,
Department: scripts[0].Department,
Project: scripts[0].Project,
APP: scripts[0].App,
ScriptID: scripts[0].ID,
URL: scripts[0].URL,
Cookie: cookie,
Domain: scripts[0].Domain,
FileSplit: scripts[0].FileSplit,
SplitNum: scripts[0].SplitNum,
Fusing: scripts[0].Fusing,
APIHeader: scripts[0].APIHeader,
Upload: scripts[0].Upload,
UseBusinessStop: scripts[0].UseBusinessStop,
BusinessStopPercent: scripts[0].BusinessStopPercent,
}
if ptestParam.Upload {
ptestParam.TestNames = QueryTestNamesByJmfile(scripts[0].SavePath)
}
return s.DoPtest(c, ptestParam)
}
//DoPtestByJmeter do ptest by jmeter
func (s *Service) DoPtestByJmeter(c context.Context, ptestParam model.DoPtestParam, testNameNicks []string) (resp model.DoPtestResp, err error) {
var (
du string
Debug int
CPUCore int
reportSuID int
scriptSnapIDs []int
beginTime string
token string
Index = 0
btm time.Time
dus time.Duration
tempRes *model.PaasJobQueryStatus
hostIP string
command string
)
lay := "2006-01-02 15:04:05"
executeID := strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
if !ptestParam.AddPtest {
// 不加压,增加新的 log
ptestParam.ResJtl = fmt.Sprintf("%s%s", ptestParam.ResJtl, executeID)
ptestParam.JmeterLog = fmt.Sprintf("%s%s", ptestParam.JmeterLog, executeID)
}
jobNamed := executeID + "run"
jobName := string([]rune(jobNamed)[10:])
// add filesplit messages to env variables
if !ptestParam.AddPtest {
Index = 1
} else {
Index = ptestParam.DockerSum + 1
}
ptestParam.EnvInfo = "FileSplit:" + strconv.FormatBool(ptestParam.FileSplit) + ",SplitNum:" + strconv.Itoa(ptestParam.SplitNum) +
",Index:" + strconv.Itoa(Index)
// 为压测容器绑定host
if ptestParam.Upload {
HostInfoByUploadSc(ptestParam.Domain)
} else {
if ptestParam.Type == model.PROTOCOL_HTTP {
HostInfo(ptestParam.Domain, ptestParam.APIHeader)
} else if ptestParam.Type == model.PROTOCOL_SCENE {
HostInfoList(ptestParam.Scripts)
}
}
if _, _, err = CreateResJtlAndJmeterLog(ptestParam); err != nil {
log.Info("create resjtl or jmeterlog err :(%v)", err)
return
}
//获取 Debug,CPUCore,command
Debug, CPUCore, command = s.CreateCommand(ptestParam)
jobInfo := model.Job{FileName: ptestParam.FileName, CPU: CPUCore, Memory: 8096, Parallelism: 1, Name: jobName, ResJtl: ptestParam.ResJtl,
JmeterLog: ptestParam.JmeterLog, EnvInfo: ptestParam.EnvInfo, JarPath: ptestParam.JarPath, Command: command}
beginTime = time.Now().Format(lay)
if btm, err = time.Parse(lay, beginTime); err != nil {
return
}
resd := int(btm.Unix()) % 5
du = "-" + strconv.Itoa(resd) + "s"
if dus, err = time.ParseDuration(du); err != nil {
return
}
beginTime = btm.Add(dus).Format(lay)
res := make(chan interface{}, 10000)
resHostIp := make(chan interface{}, 10000)
tm := time.Now()
for i := 0; i <= ptestParam.LoadTime+10; i += 5 {
tm = tm.Add(time.Second * 5)
}
timeout := time.After(time.Until(tm))
log.Info("job set timeout util --- :(%s) ", time.Until(tm))
if ptestParam.ExecuDockerSum == 0 {
ptestParam.ExecuDockerSum = 1
}
if _, err = s.AddJob(context.TODO(), jobInfo); err != nil {
log.Error("add ptest job err:(%v)", err)
return
}
if token, err = s.RmToken(c); err != nil {
log.Error("get token err:(%v)", err)
return
}
//获取物理机ip
if tempRes, err = s.Job(c, jobName); err != nil {
hostIP = ""
}
if len(tempRes.Data.Pods) > 0 {
hostIP = tempRes.Data.Pods[0].HostIP
}
// 如果不是增加容器,生成一份快照
if !ptestParam.AddPtest {
if scriptSnapIDs, err = s.AddSnap(c, ptestParam, executeID, jobName, jobNamed); err != nil || len(scriptSnapIDs) == 0 {
s.DeleteJob(context.TODO(), jobName)
return
}
// 从页面上直接输入参数生成的单场景脚本testNames 和 testNameNicks 长度都是 1reportSummary 测试计划为空
if reportSuID, err = s.AddReSummaryByPtest(ptestParam, jobName, jobNamed, testNameNicks, scriptSnapIDs[0], Debug); err != nil {
log.Error("s.dao.AddReportSummary err :(%v)", err)
s.DeleteJob(context.TODO(), jobName)
return
}
//容器写入 ptest_job 表
ptestJob := model.PtestJob{ScriptID: ptestParam.ScriptID, ReportSuID: reportSuID, JobName: jobName, Active: 1, ExecuteID: jobNamed, HostIP: hostIP}
if _, err = s.dao.AddPtestJob(&ptestJob); err != nil {
log.Error("s.dao.AddPtestJob err :(%v)", err)
s.DeleteJob(context.TODO(), jobName)
return
}
//获取场景中每个接口的熔断成功率的值
fusingList := make([]int, len(ptestParam.Scripts))
useBusiStopList := make([]bool, len(ptestParam.Scripts))
busiStopPercentList := make([]int, len(ptestParam.Scripts))
for index, script := range ptestParam.Scripts {
fusingList[index] = script.Fusing
useBusiStopList[index] = script.UseBusinessStop
busiStopPercentList[index] = script.BusinessStopPercent
}
AddReGraphTimer := model.AddReGraphTimer{
ScriptID: ptestParam.ScriptID,
JobName: jobName,
BeginTime: beginTime,
Token: token,
TestNames: ptestParam.TestNames,
TestNameNicks: testNameNicks,
ReportSuID: reportSuID,
Fusing: ptestParam.Fusing,
FusingList: fusingList,
TestType: ptestParam.Type,
UseBusinessStop: ptestParam.UseBusinessStop,
BusinessStopPercent: ptestParam.BusinessStopPercent,
UseBusiStopList: useBusiStopList,
BusiStopPercentList: busiStopPercentList,
}
//场景脚本的逻辑可能存在多个test_name
if ptestParam.Type == model.PROTOCOL_SCENE && ptestParam.TestNameNick != "" {
AddReGraphTimer.TestNameNicks = ptestParam.TestNameNicks
AddReGraphTimer.TestNames = ptestParam.TestNames
}
//数据同步job
log.Info("-------开始执行数据同步job,loadTime :(%v)", ptestParam.LoadTime)
go s.addReGraphTimer(context.TODO(), AddReGraphTimer, res, timeout)
//查询容器hostIp
go s.addPtestJobHostIp(context.TODO(), AddReGraphTimer, resHostIp, timeout)
//增加label
if len(ptestParam.LabelIDs) > 0 && !ptestParam.IsDebug {
for _, lableID := range ptestParam.LabelIDs {
lrl := model.LabelRelation{LabelID: int64(lableID), Type: model.ScriptType, TargetID: int64(ptestParam.ScriptID)}
s.AddLabelRelation(&lrl)
lrll := model.LabelRelation{LabelID: int64(lableID), Type: model.ReportType, TargetID: int64(reportSuID)}
s.AddLabelRelation(&lrll)
}
}
//如果不是debug 且配置发生通知为true则发送微信通知
if !ptestParam.IsDebug && conf.Conf.Wechat.SendMessage {
serviceName := ptestParam.Department + "." + ptestParam.Project + "." + ptestParam.APP
var userService map[string][]string
if userService, err = s.QueryDependServiceAdmins(c, serviceName, s.getSessionInCookie(ptestParam.Cookie)); err != nil {
log.Error("query depend service admin error(%v)", err)
return
}
// 发送群通知
content := AddWechatContent(ptestParam, reportSuID, jobName, userService)
go s.AddWechatSend(context.TODO(), ptestParam.Cookie, content)
// 发送依赖服务通知
for user := range userService {
toUser := StringToSlice(user)
content = AddWechatDependServiceContent(ptestParam, userService, reportSuID, user)
go s.dao.PushWechatMsgToPerson(context.TODO(), ptestParam.Cookie, toUser, content)
}
}
}
resp = model.DoPtestResp{BeginTime: beginTime, ReportSuID: reportSuID, JobName: jobName, ScriptSnapIDs: scriptSnapIDs,
JmeterLog: ptestParam.JmeterLog, JtlLog: ptestParam.ResJtl, JmxFile: ptestParam.FileName, LoadTime: ptestParam.LoadTime, HostIP: hostIP}
return
}
//QueryTestNameNick query test nick name
func QueryTestNameNick(TestName string, testNameNicks []string) (testNameNick string) {
for _, testNameNickd := range testNameNicks {
if strings.Contains(testNameNickd, TestName) {
testNameNick = testNameNickd
break
}
}
return testNameNick
}
//IsUniqObject check whether object is unique
func IsUniqObject(aa []string) (t bool) {
for _, a := range aa {
if a == aa[0] {
t = true
continue
}
if a != aa[0] {
t = false
break
}
}
return t
}
//StringToSlice convert string to string slice
func StringToSlice(str string) (strs []string) {
strs = append(strs, str)
return
}
//RemoveRepByMap 去除切片钟的重复元素
func RemoveRepByMap(slc []string) (result []string) {
tempMap := map[string]byte{} // 存放不重复主键
for _, e := range slc {
l := len(tempMap)
tempMap[e] = 0
if len(tempMap) != l { // 加入map后map长度变化则元素不重复
result = append(result, e)
}
}
return result
}
//SliceToString join slice elements to string
func SliceToString(strs []string, inter string) (result string) {
for _, str := range strs {
result = result + inter + str
}
return string([]rune(result)[1:])
}
//QueryTestNamesByJmfile query test names by jmeter file
func QueryTestNamesByJmfile(fileName string) (testNames []string) {
var (
buff []byte
err error
)
if buff, err = ioutil.ReadFile(fileName); err != nil {
log.Error("open script file failed! error %v", err)
return
}
reg := regexp.MustCompile("SampleGui\"(.*)\" enabled=\"true")
reg2 := regexp.MustCompile("testname=\"(.*)\" enabled=\"true")
results := reg.FindAllString(string(buff), -1)
for _, res := range results {
resd := reg2.FindString(res)
resdd := strings.Replace(resd, "\" enabled=\"true", "", -1)
resddd := strings.Replace(resdd, "testname=\"", "", -1)
testNames = append(testNames, resddd)
}
return
}
//ExistsInSlice check whether the string in the slice
func ExistsInSlice(str string, strs []string) bool {
for _, st := range strs {
if st == str {
return true
}
}
return false
}
//PingDomain ping domain
func PingDomain(ptestParam model.DoPtestParam) (pingString string) {
var pingStrings []string
if ptestParam.Type == model.PROTOCOL_HTTP {
pingString = " ping -c 1 " + ptestParam.Domain + " |tee -a " + ptestParam.JmeterLog
} else if ptestParam.Type == model.PROTOCOL_SCENE {
for _, script := range ptestParam.Scripts {
pingStrings = append(pingStrings, " ping -c 1 "+script.Domain+" |tee -a "+ptestParam.JmeterLog)
}
pingString = SliceToString(pingStrings, "&")
}
return
}
//CreateResJtlAndJmeterLog Create ResJt lAnd JmeterLog
func CreateResJtlAndJmeterLog(ptestParam model.DoPtestParam) (ResJtlFile, JmeterLogFile *os.File, err error) {
if ptestParam.ResJtl != "" {
if ResJtlFile, err = os.Create(ptestParam.ResJtl); err != nil {
log.Error("create ResJtl error :(%v)", err)
return
}
ResJtlFile.WriteString("此处显示 err 日志")
defer ResJtlFile.Close()
}
if ptestParam.JmeterLog != "" {
if JmeterLogFile, err = os.Create(ptestParam.JmeterLog); err != nil {
log.Error("create JmeterLog error :(%v)", err)
return
}
JmeterLogFile.WriteString("此处显示启动日志执行debug 则显示debug 日志")
defer JmeterLogFile.Close()
}
return
}
//CreateCommand Create Command
func (s *Service) CreateCommand(ptestParam model.DoPtestParam) (debug, CPUCore int, command string) {
// ping 所有接口的 domain
pingString := PingDomain(ptestParam)
cpJar := ""
if ptestParam.JarPath != "" {
cpJar = fmt.Sprintf("cp %s %s & ", ptestParam.JarPath, s.c.Jmeter.JmeterExtLibPathContainer)
}
// 调试逻辑
if ptestParam.IsDebug {
CPUCore = s.c.Paas.CPUCoreDebug
debug = 1
command = cpJar + " mkdir /data/jmeter-log & jmeter -n -t " + ptestParam.FileName + " -j " + ptestParam.JmeterLog + " -l " + ptestParam.ResJtl + " -F ;" + pingString
} else {
CPUCore = s.c.Paas.CPUCore
debug = -1
command = cpJar + " mkdir /data/jmeter-log & jmeter -n -t " + ptestParam.FileName + " -j " + ptestParam.JmeterLog + " -l " + ptestParam.ResJtl + " -F"
}
return
}

View File

@@ -0,0 +1,108 @@
package service
import (
"net/url"
"sort"
"time"
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
//TreesQuery Get service tree
func (s *Service) TreesQuery() (*model.TreeList, error) {
return s.dao.TreesQuery()
}
//TreeNumQuery Get service tree num
func (s *Service) TreeNumQuery() (*model.NumList, error) {
return s.dao.TreeNumQuery()
}
//TopHttpQuery Get top 10 url
func (s *Service) TopHttpQuery() (res *model.TopAPIRes, err error) {
if res, err = s.dao.TopHttpQuery(); err != nil {
log.Error("service.rank error:(%v)", err)
return
}
for _, api := range res.APIList {
u, errURL := url.Parse(api.URL)
if errURL != nil {
log.Error("service.rank error:(%v)", errURL)
return
}
api.URL = u.Host + u.Path
}
return
}
//TopGrpcQuery Top Grpc Query
func (s *Service) TopGrpcQuery() (*model.GrpcRes, error) {
return s.dao.TopGrpcQuery()
}
//TopSceneQuery Top Scene Query
func (s *Service) TopSceneQuery() (*model.SceneRes, error) {
return s.dao.TopSceneQuery()
}
//TopDeptQuery Get top 10 department
func (s *Service) TopDeptQuery() (*model.TopDeptRes, error) {
return s.dao.TopDeptQuery()
}
//BuildLineQuery Get test line
func (s *Service) BuildLineQuery(rank *model.Rank, summary *model.ReportSummary) (res *model.BuildLineRes, err error) {
var timePart time.Duration
//根据传入时间
timeLayout := "2006-01-02 15:04:05"
if rank.StartTime == "" && rank.EndTime == "" {
rank.StartTime = time.Now().Add(time.Hour * -24).Format(timeLayout)
rank.EndTime = time.Now().Format(timeLayout)
} else if rank.StartTime == "" {
loc, _ := time.LoadLocation("Local") //重要:获取时区
theTime, _ := time.ParseInLocation(timeLayout, rank.EndTime, loc) //使用模板在对应时区转化为time.time类型
if timePart, err = time.ParseDuration("-24h"); err != nil {
log.Error("service.rank error:(%v)", err)
return
}
rank.StartTime = theTime.Add(timePart).Format(timeLayout)
} else if rank.EndTime == "" {
rank.EndTime = time.Now().Format(timeLayout)
}
if res, err = s.dao.BuildLineQuery(rank, summary); err != nil {
log.Error("service.rank error:(%v)", err)
return
}
var myDateMap = make(map[string]int)
for _, bu := range res.BuildList {
if _, ok := myDateMap[bu.Date]; ok {
myDateMap[bu.Date]++
} else {
myDateMap[bu.Date] = 1
}
}
sortedKeys := make([]string, 0)
for k := range myDateMap {
sortedKeys = append(sortedKeys, k)
}
sort.Strings(sortedKeys)
var mySortedMap = make(map[string]int)
res.BuildList = []*model.Build{}
for _, k := range sortedKeys {
mySortedMap[k] = myDateMap[k]
bu := model.Build{Date: k, Count: myDateMap[k]}
res.BuildList = append(res.BuildList, &bu)
}
return
}
//StateLineQuery Get test state line
func (s *Service) StateLineQuery() (*model.StateLineRes, error) {
return s.dao.StateLineQuery()
}

View File

@@ -0,0 +1,108 @@
package service
import (
"go-common/app/admin/ep/melloi/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceTreesQuery(t *testing.T) {
convey.Convey("TreesQuery", t, func(convCtx convey.C) {
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.TreesQuery()
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceTreeNumQuery(t *testing.T) {
convey.Convey("TreeNumQuery", t, func(convCtx convey.C) {
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.TreeNumQuery()
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceTopHttpQuery(t *testing.T) {
convey.Convey("TopHttpQuery", t, func(convCtx convey.C) {
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := s.TopHttpQuery()
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceTopGrpcQuery(t *testing.T) {
convey.Convey("TopGrpcQuery", t, func(convCtx convey.C) {
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.TopGrpcQuery()
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceTopSceneQuery(t *testing.T) {
convey.Convey("TopSceneQuery", t, func(convCtx convey.C) {
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.TopSceneQuery()
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceTopDeptQuery(t *testing.T) {
convey.Convey("TopDeptQuery", t, func(convCtx convey.C) {
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.TopDeptQuery()
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceBuildLineQuery(t *testing.T) {
convey.Convey("BuildLineQuery", t, func(convCtx convey.C) {
var (
rank = &model.Rank{}
summary = &model.ReportSummary{}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := s.BuildLineQuery(rank, summary)
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceStateLineQuery(t *testing.T) {
convey.Convey("StateLineQuery", t, func(convCtx convey.C) {
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.StateLineQuery()
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}

View File

@@ -0,0 +1,713 @@
package service
import (
"context"
"encoding/json"
"math"
"sort"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/melloi/conf"
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
// QueryReportSummarys query report summary
func (s *Service) QueryReportSummarys(c context.Context, sessionID string, qrsr *model.QueryReportSuRequest) (qrs *model.QueryReportSuResponse, err error) {
// 获取服务树节点
var treeNodes, treeNodesd []string
if treeNodesd, err = s.QueryUserRoleNode(c, sessionID); err != nil {
log.Error("QueryUserRoleNode (%v): ", err)
return
}
treeNodes = append(treeNodesd, "")
if ExistsInSlice(qrsr.Executor, conf.Conf.Melloi.Executor) {
if qrs, err = s.dao.QueryReportSummarysWhiteName(&qrsr.ReportSummary, qrsr.SearchAll, qrsr.PageNum, qrsr.PageSize); err != nil {
return
}
} else {
if qrs, err = s.dao.QueryReportSummarys(&qrsr.ReportSummary, qrsr.SearchAll, qrsr.PageNum, qrsr.PageSize, treeNodes); err != nil {
return
}
}
// 获取label
for _, report := range qrs.ReportSummarys {
lr := model.LabelRelation{Type: model.ReportType, TargetID: int64(report.ID)}
if report.Labels, err = s.dao.QueryLabelRelation(&lr); err != nil {
return
}
}
return
}
// QueryReportByID Query Report By ID
func (s *Service) QueryReportByID(id int) (summary *model.ReportSummary, err error) {
return s.dao.QueryReportSuryByID(id)
}
// CountQueryReportSummarys count query report summarys
func (s *Service) CountQueryReportSummarys(reportSummary *model.ReportSummary) (int, error) {
return s.dao.CountQueryReportSummarys(reportSummary)
}
// UpdateReportSummary update report summary
func (s *Service) UpdateReportSummary(reportSummary *model.ReportSummary) (status string, err error) {
if err = s.dao.UpdateReportSummary(reportSummary); err != nil {
status = "update fail"
return
}
status = "success"
return
}
// UpdateReportStatus update report summary status
func (s *Service) UpdateReportStatus(status int) error {
return s.dao.UpdateReportStatus(status)
}
// AddReSummaryByPtest Add ReSummary By Ptest
func (s *Service) AddReSummaryByPtest(ptestParam model.DoPtestParam, jobName, executeID string, testNameNicks []string, snapID, Debug int) (reportSuID int, err error) {
reportSummary := model.ReportSummary{
JobName: jobName,
TestStatus: 2,
UserName: ptestParam.UserName,
ResJtl: ptestParam.ResJtl,
JmeterLog: ptestParam.JmeterLog,
Department: ptestParam.Department,
Project: ptestParam.Project,
APP: ptestParam.APP,
ScriptID: ptestParam.ScriptID,
DockerSum: 1,
Active: 1,
Debug: Debug,
ScriptSnapID: snapID,
ExecuteID: executeID,
SceneID: ptestParam.SceneID,
Type: ptestParam.Type,
LoadTime: ptestParam.LoadTime,
}
//Upload==true这里表示从前端上传的脚本testNames 和 testNameNicks 长度可能大于1需要遍历写入 reportSummary
if ptestParam.Upload {
reportSummary.TestNameNick = SliceToString(testNameNicks, ",")
reportSummary.TestName = ptestParam.ProjectName
reportSummary.Type = model.PROTOCOL_SCENE
}
// 从页面上直接输入参数生成的单场景脚本testNames 和 testNameNicks 长度都是 1
// 生成的场景脚本ptestParam.TestNameNick 不为空
if !ptestParam.Upload {
for _, testName := range ptestParam.TestNames {
reportSummary.TestNameNick = QueryTestNameNick(testName, testNameNicks)
reportSummary.TestName = testName
}
//场景脚本的 reportSummary 逻辑
if ptestParam.Type == model.PROTOCOL_SCENE && ptestParam.TestNameNick != "" {
reportSummary.TestNameNick = ptestParam.TestNameNick
reportSummary.TestName = ptestParam.SceneName
}
}
if reportSuID, err = s.dao.AddReportSummary(&reportSummary); err != nil {
log.Error("s.dao.AddReportSummary err :(%v)", err)
return
}
return
}
// AddReportSummary add report summary
func (s *Service) AddReportSummary(reportSummary *model.ReportSummary) (status string, reportSuID int, err error) {
var total int
if total, err = s.dao.CountQueryReportSummarys(reportSummary); err != nil {
log.Error("CountQueryReportSummarys error:(%v)", err)
status = "fail"
return
}
if (total == 1) || (reportSummary.TestName == "") {
status = "数据已存在或者无接口名"
} else {
if reportSuID, err = s.dao.AddReportSummary(reportSummary); err != nil {
status = "fail"
return
}
status = "success"
}
return
}
// QueryReGraph query reGraph
func (s *Service) QueryReGraph(testNameNicks []string) (reportGraphssd [][]model.ReportGraph, err error) {
var (
TestNames []string
reportGraphss []model.ReportGraph
reportGraphs []model.ReportGraph
)
if reportGraphs, err = s.dao.QueryReportGraph(testNameNicks); err != nil {
log.Error("dao.QueryReportGraph error:(%v)", err)
return
}
if len(reportGraphs) > 0 {
for _, reportGraph := range reportGraphs {
TestNames = append(TestNames, reportGraph.TestName)
}
TestNamesd := RemoveRepByMap(TestNames)
for _, testName := range TestNamesd {
for _, reportGraph := range reportGraphs {
if reportGraph.TestName == testName {
reportGraphss = append(reportGraphss, reportGraph)
}
}
reportGraphssd = append(reportGraphssd, reportGraphss)
reportGraphss = []model.ReportGraph{}
}
}
return
}
// QueryClientMoni query client moni
func (s *Service) QueryClientMoni(cli *model.ClientMoni) (clientMonisd [][]*model.ClientMoni, err error) {
var (
jobNames []string
clientMoniss []*model.ClientMoni
clientMonis []*model.ClientMoni
)
if clientMonis, err = s.dao.QueryClientMoni(cli); err != nil {
log.Error("dao.QueryClientMoni error:(%v)", err)
return
}
if len(clientMonis) > 0 {
for _, clientMoni := range clientMonis {
jobNames = append(jobNames, clientMoni.JobName)
}
jobNamesd := RemoveRepByMap(jobNames)
for _, jobNamed := range jobNamesd {
for _, clientMoni := range clientMonis {
if clientMoni.JobName == jobNamed {
clientMoniss = append(clientMoniss, clientMoni)
}
}
clientMonisd = append(clientMonisd, clientMoniss)
clientMoniss = []*model.ClientMoni{}
}
}
return
}
//QueryReGraphAvg query reGraph
func (s *Service) QueryReGraphAvg(testNameNicks []string) (reportGraphAvgs []model.ReportGraph, err error) {
var reportGraphssd [][]model.ReportGraph
if reportGraphssd, err = s.QueryReGraph(testNameNicks); err != nil {
log.Error("QueryReGraph error :(%s)", err)
return
}
for _, reportGraphsd := range reportGraphssd {
reportGraph := ReportGraphAvg(reportGraphsd)
reportGraphAvgs = append(reportGraphAvgs, reportGraph)
}
return
}
//ReportGraphAvg report graph avg
func ReportGraphAvg(reportGraphs []model.ReportGraph) (reportGraph model.ReportGraph) {
var sumTime, sumQPS, sumNetIo, min, max, sumCount, sumError, sumCodeEll, sumCodeWll, sumCodeWly, sumCodeWle, sumCodeWls,
sumCodeSll, sumCodeSly, sumCodeSls, sumCodeKong, sumCodeNonHTTP, sumCodeOthers, fiftyTime, ninetyTime, ninetyFiveTime,
ninetyNineTime, sumCode301, sumCode302, timeCount, sumQPSRecent, QPSRecent int
var mins, maxs []int
if len(reportGraphs) > 0 {
for _, reportGraph := range reportGraphs {
mins = append(mins, reportGraph.Min)
maxs = append(maxs, reportGraph.Max)
sumCount = sumCount + reportGraph.Count
timeCount = reportGraph.Count * reportGraph.AvgTime
sumTime = sumTime + timeCount
sumError = sumError + reportGraph.Error
sumQPS = sumQPS + reportGraph.QPS
sumNetIo = sumNetIo + reportGraph.NetIo
sumCodeEll = sumCodeEll + reportGraph.CodeEll
sumCodeWll = sumCodeWll + reportGraph.CodeWll
sumCodeWly = sumCodeWly + reportGraph.CodeWly
sumCodeWle = sumCodeWle + reportGraph.CodeWle
sumCodeWls = sumCodeWls + reportGraph.CodeWls
sumCodeSll = sumCodeSll + reportGraph.CodeSll
sumCodeSly = sumCodeSly + reportGraph.CodeSly
sumCodeSls = sumCodeSls + reportGraph.CodeSls
sumCodeKong = sumCodeKong + reportGraph.CodeKong
sumCode301 = sumCode301 + reportGraph.Code301
sumCode302 = sumCode302 + reportGraph.Code302
sumCodeNonHTTP = sumCodeNonHTTP + reportGraph.CodeNonHTTP
sumCodeOthers = sumCodeOthers + reportGraph.CodeOthers
fiftyTime = fiftyTime + reportGraph.FiftyTime
ninetyTime = ninetyTime + reportGraph.NinetyTime
ninetyFiveTime = ninetyFiveTime + reportGraph.NinetyFiveTime
ninetyNineTime = ninetyNineTime + reportGraph.NinetyNineTime
}
// 取最后 recent次统计的平均 qps作为最近一段时间的qps
recent := conf.Conf.Melloi.Recent
if len(reportGraphs) > recent {
for i := 1; i <= recent; i++ {
sumQPSRecent += reportGraphs[len(reportGraphs)-i].QPS
QPSRecent = sumQPSRecent / recent
}
} else {
QPSRecent = sumQPS / len(reportGraphs)
}
if len(mins) != 0 {
sort.Ints(mins)
min = mins[0]
sort.Ints(maxs)
max = maxs[len(maxs)-1]
}
failPercentd := float64(sumError) * 100 / float64(sumCount)
n10 := math.Pow10(3)
fail := math.Trunc((failPercentd+0.5/n10)*n10) / n10
failStr := strconv.FormatFloat(fail, 'f', -1, 64)
failPercent := failStr + "%"
num := len(reportGraphs)
reportGraph = model.ReportGraph{
TestName: reportGraphs[0].TestName, TestNameNick: reportGraphs[0].TestNameNick, Max: max, Min: min, Count: sumCount, Error: sumError,
NetIo: sumNetIo / num, QPS: sumQPS / num, AvgTime: sumTime / sumCount, Ctime: reportGraphs[len(reportGraphs)-1].Ctime,
CodeEll: sumCodeEll, CodeSll: sumCodeSll, CodeSly: sumCodeSly, CodeSls: sumCodeSls, CodeWll: sumCodeWll, CodeWly: sumCodeWly,
CodeWle: sumCodeWle, CodeWls: sumCodeWls, CodeNonHTTP: sumCodeNonHTTP, CodeKong: sumCodeKong, CodeOthers: sumCodeOthers,
FailPercent: failPercent, FiftyTime: fiftyTime / num, NinetyTime: ninetyTime / num, NinetyFiveTime: ninetyFiveTime / num,
NinetyNineTime: ninetyNineTime / num, Code301: sumCode301, Code302: sumCode302, BeginTime: reportGraphs[0].Ctime, QpsRecent: QPSRecent,
}
}
return
}
//addReGraphTimer add regraph timer
func (s *Service) addReGraphTimer(c context.Context, addReGrapht model.AddReGraphTimer, res chan interface{}, timeout <-chan time.Time) {
var (
status = 1
reportSummary model.ReportSummary
testNamesd []string
podNames []string
beginTimed string
afterTimed string
beginTime = addReGrapht.BeginTime
testNames = addReGrapht.TestNames
jobName = addReGrapht.JobName
token = addReGrapht.Token
testNameNicks = addReGrapht.TestNameNicks
RetMap *model.PaasJobQueryStatus
scriptID = addReGrapht.ScriptID
reportSuID = addReGrapht.ReportSuID
ptestJobs []*model.PtestJob
err error
btm time.Time
firstRetMap *model.PaasJobQueryStatus
reportGraphs []model.ReportGraph
JSON []byte
fusingList = addReGrapht.FusingList
useBusiStopList = addReGrapht.UseBusiStopList
busiStopPercentList = addReGrapht.BusiStopPercentList
)
timer := time.NewTicker(5 * time.Second)
done := make(chan bool, 1)
num := 1
go func() {
defer close(res)
a := 1
for {
if btm, err = time.Parse("2006-01-02 15:04:05", beginTime); err != nil {
timeout = time.After(time.Until(time.Now().Add(1 * time.Second)))
return
}
select {
case <-timer.C:
elapsedTime := (num - 1) * 5
//实时当前任务的所有容器
if ptestJobs, err = s.QueryOrStopAllPtestByJobName(context.TODO(), reportSuID, false, 3); err != nil {
log.Error("get all Job err (%v)", err)
}
//遍历容器并查询每个容器的cpu
if len(ptestJobs) > 0 {
for _, ptestJob := range ptestJobs {
if RetMap, err = s.dao.Job(c, token, ptestJob.JobName); err != nil {
log.Error("get job info err (%v)", err)
}
if RetMap != nil && len(RetMap.Data.Pods) > 0 {
for _, pod := range RetMap.Data.Pods {
podNames = append(podNames, pod.Name)
log.Info("containerID :(%s)", pod.ContainerID)
go s.addJobCPU(context.TODO(), pod, scriptID, reportSuID, elapsedTime, ptestJob.JobName)
}
}
}
}
log.Info("podName :(%s)", podNames)
if firstRetMap, err = s.dao.Job(c, token, jobName); err != nil {
log.Error("查询容器状态 error-------(%v)", err)
//接口报错,则删除所有压测容器
timeout = time.After(time.Until(time.Now().Add(1 * time.Second)))
return
}
if JSON, err = json.Marshal(firstRetMap); err != nil {
timeout = time.After(time.Until(time.Now().Add(1 * time.Second)))
}
result := string(JSON)
log.Info("查询容器状态:(%s)", result)
if firstRetMap.Status == 400 {
status = 3
timeout = time.After(time.Until(time.Now().Add(1 * time.Second)))
}
if firstRetMap.Status == 200 && firstRetMap.Data.ActiveNum == 1 {
log.Info("第(%d)次同步数据", a)
if num == 1 {
log.Info("job 执行参数 testName:(%s), beginTime :(%s),elapsedTime :(%d), num: (%d)", testNames, beginTime, elapsedTime, num)
for index, testName := range testNames {
testNameNick := QueryTestNameNick(testName, testNameNicks)
reportGraphAdd := model.ReportGraphAdd{
JobName: jobName,
TestName: testName,
BeginTime: beginTime,
AfterTime: beginTime,
TestNameNick: testNameNick,
PodNames: podNames,
ElapsedTime: elapsedTime,
ReportSuID: reportSuID,
UseBusinessStop: addReGrapht.UseBusinessStop,
BusinessStopPercent: addReGrapht.BusinessStopPercent,
}
if addReGrapht.TestType == model.PROTOCOL_SCENE {
reportGraphAdd.Fusing = fusingList[index]
reportGraphAdd.UseBusinessStop = useBusiStopList[index]
reportGraphAdd.BusinessStopPercent = busiStopPercentList[index]
} else {
reportGraphAdd.Fusing = addReGrapht.Fusing
}
go s.addReportGraph(context.TODO(), reportGraphAdd)
}
} else {
for i := 1; i <= num-1; i++ {
btm = btm.Add(time.Second * 5)
aft := btm.Add(time.Second * 2)
beginTimed = btm.Format("2006-01-02 15:04:05")
afterTimed = aft.Format("2006-01-02 15:04:05")
}
log.Info("job 执行参数 testName:(%s),beginTime :(%s),elapsedTime :(%d)", testNames, beginTimed, elapsedTime)
for index, testName := range testNames {
testNameNick := QueryTestNameNick(testName, testNameNicks)
reportGraphAdd := model.ReportGraphAdd{
JobName: jobName,
TestName: testName,
BeginTime: beginTimed,
AfterTime: afterTimed,
TestNameNick: testNameNick,
PodNames: podNames,
ElapsedTime: elapsedTime,
ReportSuID: reportSuID,
UseBusinessStop: addReGrapht.UseBusinessStop,
BusinessStopPercent: addReGrapht.BusinessStopPercent,
}
if addReGrapht.TestType == model.PROTOCOL_SCENE {
reportGraphAdd.Fusing = fusingList[index]
reportGraphAdd.UseBusinessStop = useBusiStopList[index]
reportGraphAdd.BusinessStopPercent = busiStopPercentList[index]
} else {
reportGraphAdd.Fusing = addReGrapht.Fusing
}
go s.addReportGraph(context.TODO(), reportGraphAdd)
}
}
}
podNames = []string{}
if firstRetMap.Status == 200 && firstRetMap.Data.ActiveNum == 0 {
timeout = time.After(time.Until(time.Now().Add(1 * time.Second)))
}
num++
res <- a
a++
case <-timeout:
close(done)
log.Info("real stop time (%s)", time.Now().Format("2006-01-02 15:04:05"))
// 关闭所有压测该项目或者接口的容器
go s.QueryOrStopAllPtestByJobName(context.TODO(), reportSuID, true, status)
if reportGraphs, err = s.dao.QueryReportGraph(testNameNicks); err != nil {
log.Error("query reportGraphs error :(%v) ", err)
return
}
for _, reportGraph := range reportGraphs {
testNamesd = append(testNames, reportGraph.TestName)
}
// 如果reportGraphs 的成员都是同一个testName ,则执行如下代码,表示进行结果合并,生成 reportSummary
if IsUniqObject(testNamesd) {
if len(reportGraphs) > 0 {
reportGraph := ReportGraphAvg(reportGraphs)
reportSummary = model.ReportSummary{
ID: reportSuID,
TestName: reportGraph.TestName,
TestNameNick: reportGraph.TestNameNick,
Count: reportGraph.Count,
Error: reportGraph.Error,
QPS: reportGraph.QPS,
AvgTime: reportGraph.AvgTime,
NetIo: reportGraph.NetIo,
Min: reportGraph.Min,
Max: reportGraph.Max,
}
reportSummary.TestStatus = status
} else {
log.Info("sorry,your test duration is too short ..... ")
reportSummary = model.ReportSummary{ID: reportSuID, TestStatus: 3}
}
s.dao.UpdateReportSummary(&reportSummary)
}
//场景压测、多个接口走该逻辑
if !IsUniqObject(testNamesd) && len(testNameNicks) > 0 {
if len(reportGraphs) > 0 {
reportSummary = model.ReportSummary{ID: reportSuID, TestStatus: status}
} else {
reportSummary = model.ReportSummary{ID: reportSuID, TestStatus: 3}
}
}
s.dao.UpdateReportSummary(&reportSummary)
return
}
}
}()
<-done
}
//addPtestJobHostIp add regraph timer
func (s *Service) addPtestJobHostIp(c context.Context, addReGrapht model.AddReGraphTimer, res chan interface{}, timeout <-chan time.Time) {
var (
RetMap *model.PaasJobQueryStatus
token = addReGrapht.Token
err error
ptestJobs []*model.PtestJob
ptestJobsd []*model.PtestJob
firstRetMap *model.PaasJobQueryStatus
)
timer := time.NewTicker(10 * time.Second)
done := make(chan bool, 1)
go func() {
defer close(res)
a := 1
for {
select {
case <-timer.C:
if firstRetMap, err = s.dao.Job(c, token, addReGrapht.JobName); err != nil {
log.Error("查询容器状态 error-------(%v)", err)
//接口报错,则删除所有压测容器
return
}
if firstRetMap == nil || firstRetMap.Status == 400 || (firstRetMap.Status == 200 && firstRetMap.Data.ActiveNum == 0) {
return
}
ptestJob := model.PtestJob{ReportSuID: addReGrapht.ReportSuID, Active: 1}
if ptestJobs, err = s.dao.QueryPtestJob(&ptestJob); err != nil {
return
}
for _, job := range ptestJobs {
if job.HostIP == "" || job.JobIP == "" || job.JobID == "" {
ptestJobsd = append(ptestJobsd, job)
}
}
if len(ptestJobsd) > 0 {
for _, ptestJob := range ptestJobsd {
//查询容器状态
if RetMap, err = s.dao.Job(c, token, ptestJob.JobName); err != nil {
log.Error("get job info err (%v)", err)
}
if RetMap != nil && len(RetMap.Data.Pods) > 0 {
for _, pod := range RetMap.Data.Pods {
ptestjo := model.PtestJob{ID: ptestJob.ID, HostIP: pod.HostIP, JobID: pod.ContainerID, JobIP: pod.IP}
//更新容器hostIp
s.dao.UpdatePtestJob(ptestjo)
}
}
}
}
res <- a
a++
case <-timeout:
close(done)
return
}
}
}()
<-done
}
//addJobCPU job cpu
func (s *Service) addJobCPU(c context.Context, pod model.PodInfo, scriptID, reportSuID, elapsedTime int, jobName string) (err error) {
var (
clientMoni = model.ClientMoni{
ScriptID: scriptID,
ReportSuID: reportSuID,
JobName: jobName,
JobNameAll: pod.Name,
ElapsdTime: elapsedTime,
}
dockerStats *model.DockerStats
CPUResult *model.PaasQueryJobCPUResult
)
//首先查我们自己的容器cpu 获取服务
if pod.ContainerID != "" {
if dockerStats, err = s.QueryJobCPUByEP(context.TODO(), pod.ContainerID, pod.HostIP); err != nil {
log.Error("query CPU err ...(%s)", err)
}
if dockerStats != nil && dockerStats.CPU != "" {
cpu := strings.Replace(dockerStats.CPU, "%", "", 1)
cpufloat, _ := strconv.ParseFloat(cpu, 32)
cpuFinal := cpufloat
cpuString := strconv.FormatFloat(cpuFinal, 'f', -1, 32)
clientMoni.CPUUsed = cpuString
}
if dockerStats == nil || dockerStats.CPU == "" {
//如果我们的服务挂了,就查 moni
if CPUResult, err = s.QueryJobCPU(context.TODO(), pod.Name); err != nil {
log.Error("query client cpu err(%v)", err)
return
}
if len(CPUResult.Data) > 0 {
for _, data := range CPUResult.Data {
if len(data.Value) > 1 {
clientMoni.CPUUsed = data.Value[1].(string)
}
}
}
}
}
//cpu 信息记录到数据库
if _, err = s.AddClientMoni(&clientMoni); err != nil {
log.Error("add ClientMoni err(%v)", err)
return
}
return
}
//addReportGraph add reportGraph
func (s *Service) addReportGraph(c context.Context, reportGraphAdd model.ReportGraphAdd) {
var (
sumTime, sumQPS, sumNetIo, sumCodeEll, sumCodeWll, sumCodeWly, sumCodeWle, sumCodeWls, sumCodeSll, sumCodeSly, sumCodeSls,
sumCodeKong, sumCodeNonHTTP, sumCodeOthers, sumCount, sumError, sumThreads, fiftyTime, ninetyTime, ninetyFiveTime,
timeCount, ninetyNineTime, sumCode301, sumCode302 int
mins, maxs []int
reportTimelys []*model.ReportTimely
err error
)
busiStopPerc := reportGraphAdd.BusinessStopPercent
if reportTimelys, err = s.dao.QueryReTimely(reportGraphAdd.TestName, reportGraphAdd.BeginTime, reportGraphAdd.AfterTime, reportGraphAdd.PodNames); err != nil {
return
}
log.Info("------reportTimelys:(%v)", reportTimelys)
if len(reportTimelys) > 0 {
for _, reportTimely := range reportTimelys {
mins = append(mins, reportTimely.Min)
maxs = append(maxs, reportTimely.Max)
sumCount = sumCount + reportTimely.Count
//计算一个容器的接口响应总时间
timeCount = reportTimely.Count * reportTimely.AvgTime
//计算所有容器的接口响应时间
sumTime = sumTime + timeCount
sumQPS = sumQPS + reportTimely.QPS
sumError = sumError + reportTimely.Error
sumNetIo = sumNetIo + reportTimely.NetIo
sumCodeEll = sumCodeEll + reportTimely.CodeEll
sumCodeWll = sumCodeWll + reportTimely.CodeWll
sumCodeWly = sumCodeWly + reportTimely.CodeWly
sumCodeWle = sumCodeWle + reportTimely.CodeWle
sumCodeWls = sumCodeWls + reportTimely.CodeWls
sumCodeSll = sumCodeSll + reportTimely.CodeSll
sumCodeSly = sumCodeSly + reportTimely.CodeSly
sumCodeSls = sumCodeSls + reportTimely.CodeSls
sumCodeKong = sumCodeKong + reportTimely.CodeKong
sumCodeNonHTTP = sumCodeNonHTTP + reportTimely.CodeNonHTTP
sumCodeOthers = sumCodeOthers + reportTimely.CodeOthers
sumCode301 = sumCode301 + reportTimely.Code301
sumCode302 = sumCode302 + reportTimely.Code302
sumThreads = sumThreads + reportTimely.ThreadsSum
fiftyTime = fiftyTime + reportTimely.FiftyTime
ninetyTime = ninetyTime + reportTimely.NinetyTime
ninetyFiveTime = ninetyFiveTime + reportTimely.NinetyFiveTime
ninetyNineTime = ninetyNineTime + reportTimely.NinetyNineTime
}
sort.Ints(mins)
min := mins[0]
sort.Ints(maxs)
max := maxs[len(maxs)-1]
log.Info("test_nick_name ----- :(%s)", reportGraphAdd.TestNameNick)
reportGraph := model.ReportGraph{
TestName: reportGraphAdd.TestName, TestNameNick: reportGraphAdd.TestNameNick,
Count: sumCount,
QPS: sumQPS,
Error: sumError,
AvgTime: sumTime / sumCount,
NetIo: sumNetIo,
Min: min,
Max: max,
ElapsdTime: reportGraphAdd.ElapsedTime,
CodeEll: sumCodeEll,
CodeWll: sumCodeWll,
CodeWly: sumCodeWly,
CodeWle: sumCodeWle,
CodeWls: sumCodeWls,
CodeSll: sumCodeSll,
CodeSly: sumCodeSly,
CodeSls: sumCodeSls,
CodeKong: sumCodeKong,
CodeNonHTTP: sumCodeNonHTTP,
CodeOthers: sumCodeOthers,
ThreadsSum: sumThreads,
FiftyTime: fiftyTime / len(reportTimelys),
NinetyTime: ninetyTime / len(reportTimelys),
NinetyFiveTime: ninetyFiveTime / len(reportTimelys),
NinetyNineTime: ninetyNineTime / len(reportTimelys),
Code301: sumCode301,
Code302: sumCode302,
}
suCodeRate, suBusinessRate := CalcuSuccess(reportGraph)
if reportGraphAdd.Fusing == 0 {
reportGraphAdd.Fusing = conf.Conf.Melloi.DefaultFusing
}
//压测熔断
//使用业务熔断只要http code 和 业务断言错误满足一条阈值,则熔断
if reportGraphAdd.UseBusinessStop {
if busiStopPerc == 0 {
busiStopPerc = conf.Conf.Melloi.DefaultBusinessRate
}
if suCodeRate < reportGraphAdd.Fusing || suBusinessRate < busiStopPerc {
log.Info("接口失败率超过设置阈值,执行自动熔断,jobName (%s)", reportGraphAdd.JobName)
if _, err = s.DeleteJob(c, reportGraphAdd.JobName); err != nil {
return
}
reportsu := model.ReportSummary{ID: reportGraphAdd.ReportSuID, IsFusing: true, BusinessValue: busiStopPerc, UseBusinessStop: reportGraphAdd.UseBusinessStop,
FusingTestName: reportGraphAdd.TestName, SuccessCodeRate: suCodeRate, SuccessBusinessRate: suBusinessRate, FusingValue: reportGraphAdd.Fusing}
s.dao.UpdateReportSummary(&reportsu)
}
} else { //不使用业务熔断
if suCodeRate < reportGraphAdd.Fusing {
log.Info("接口失败率超过设置阈值,执行自动熔断,jobName (%s)", reportGraphAdd.JobName)
if _, err = s.DeleteJob(c, reportGraphAdd.JobName); err != nil {
return
}
reportsu := model.ReportSummary{ID: reportGraphAdd.ReportSuID, IsFusing: true, BusinessValue: busiStopPerc,
FusingTestName: reportGraphAdd.TestName, SuccessCodeRate: suCodeRate, SuccessBusinessRate: suBusinessRate, FusingValue: reportGraphAdd.Fusing}
s.dao.UpdateReportSummary(&reportsu)
}
}
s.dao.AddReportGraph(&reportGraph)
return
}
}
//CalcuSuccess calcu success
func CalcuSuccess(reportGraph model.ReportGraph) (successCodeRate, successBusinessRate int) {
success := reportGraph.CodeEll + reportGraph.CodeKong + reportGraph.Code301 + reportGraph.Code302
successCodeRate = success * 100 / reportGraph.Count
successBusinessRate = (reportGraph.Count - reportGraph.Error) * 100 / reportGraph.Count
return
}
// DelReportSummary delete report summary
func (s *Service) DelReportSummary(id int) error {
return s.dao.DelReportSummary(id)
}

View File

@@ -0,0 +1,31 @@
package service
import (
"testing"
"go-common/app/admin/ep/melloi/model"
. "github.com/smartystreets/goconvey/convey"
)
func Test_Report(t *testing.T) {
reportSummaryC := model.ReportSummary{
TestName: "testGrpc",
}
Convey("query reportGraph", t, func() {
var strs = []string{"hoopchina1534255529"}
regraphs, _ := s.QueryReGraph(strs)
So(len(regraphs), ShouldBeGreaterThan, 0)
})
Convey("count query report summarys", t, func() {
count, _ := s.CountQueryReportSummarys(&reportSummaryC)
So(count, ShouldBeGreaterThan, 0)
})
Convey("add reportGraph", t, func() {
//reportSummarys:= s.addReportGraph(&reportSummaryC,1,10)
//So(len(reportSummarys), ShouldBeGreaterThan, 0)
})
}

View File

@@ -0,0 +1,613 @@
package service
import (
"context"
"encoding/json"
"html/template"
"io"
"os"
"sort"
"strconv"
"strings"
"time"
"go-common/app/admin/ep/melloi/conf"
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
// QueryDraft Query Draft
func (s *Service) QueryDraft(scene *model.Scene) (*model.QueryDraft, error) {
return s.dao.QueryDraft(scene)
}
// UpdateScene Update Scene
func (s *Service) UpdateScene(scene *model.Scene) (fusing int, err error) {
scriptIDList := make([]int, len(scene.Scripts))
for index, script := range scene.Scripts {
scriptIDList[index] = script.ID
}
fusing, err = s.dao.UpdateScene(scene, scriptIDList)
return
}
// AddScene Add Scene
func (s *Service) AddScene(scene *model.Scene) (id int, err error) {
id, err = s.dao.AddScene(scene)
return
}
// QueryAPI Query API
func (s *Service) QueryAPI(scene *model.Scene) (*model.QueryAPIs, error) {
return s.dao.QueryAPI(scene)
}
// AddConfig Add Config
func (s *Service) AddConfig(script *model.Script) error {
return s.dao.AddConfig(script)
}
// QueryTree Query Tree
func (s *Service) QueryTree(script *model.Script) (*model.ShowTree, error) {
return s.dao.QueryTree(script)
}
// QueryScenesByPage Query Scene By Page
func (s *Service) QueryScenesByPage(c context.Context, sessionID string, qsrq *model.QuerySceneRequest) (rsp *model.QuerySceneResponse, err error) {
// 获取服务树节点
var (
treeNodes []string
treeNodesd []string
)
if treeNodesd, err = s.QueryUserRoleNode(c, sessionID); err != nil {
log.Error("QueryUserRoleNode err (%v):", err)
}
treeNodes = append(treeNodesd, "")
if ExistsInSlice(qsrq.Executor, conf.Conf.Melloi.Executor) {
if rsp, err = s.dao.QueryScenesByPageWhiteName(&qsrq.Scene, qsrq.PageNum, qsrq.PageSize); err != nil {
return
}
} else {
if rsp, err = s.dao.QueryScenesByPage(&qsrq.Scene, qsrq.PageNum, qsrq.PageSize, treeNodes); err != nil {
return
}
}
return
}
//AddAndExecuScene AddSceneAuto add scene auto
func (s *Service) AddAndExecuScene(c context.Context, scene model.Scene, cookie string) (resp model.DoPtestResp, err error) {
var (
buff *template.Template
file *os.File
scrThreadGroup model.ScrThreadGroup
fileName string
testNames []string
testNameNicks []string
loadTimes []int
scripts []*model.Script
jmeterLog string
resJtl string
sceneID int
threadGroup string
)
// id 不是0 说明是输入接口创建的场景根据sceneID 查询到接口列表再根据接口列表生成jmx
// 不是前端 quick-start 页的批量选择,走如下逻辑
if scene.ID != 0 && !scene.IsBatch {
script := model.Script{SceneID: scene.ID}
if scripts, err = s.dao.QueryScripts(&script, 1, 200); err != nil {
log.Error("s.dao.QueryScripts err :(%v)", err)
return
}
scene.Scripts = scripts
}
if scene.IsDebug {
for _, script := range scene.Scripts {
script.ThreadsSum = 1
script.Loops = 5
script.TestName = script.TestName + "_perf_debug"
}
scene.SceneName = scene.SceneName + "_perf_debug"
}
// 非 quick-start 页面的批量选择,走如下逻辑,即 quick-start 页面的批量选择,不生成 scene.jmx 文件
sceneSuffix := strconv.FormatInt(time.Now().Unix(), 10)
if !scene.IsBatch {
scrThreadGroup.Scripts = scene.Scripts
if threadGroup, err = s.GetThreadGroup(scrThreadGroup); err != nil {
log.Error("s.dao.GetThreadGroup (%v)", err)
return
}
scene.ThreadGroup = unescaped(threadGroup)
if buff, err = template.ParseFiles(s.c.Jmeter.JmeterSceneTmp); err != nil {
log.Error("file is not exists (%v)", err)
return
}
scene.ScriptPath = "/data/jmeter-log/" + scene.Department + "/" + scene.Project + "/" + scene.APP + "/" + "scene" + "/" + sceneSuffix + "/"
if err = os.MkdirAll(scene.ScriptPath, 0755); err != nil {
log.Error("Create SavePath Err (%v)", err)
return
}
// 创建脚本文件 脚本路径+场景名+后缀.jmx
if file, err = os.Create(scene.ScriptPath + sceneSuffix + ".jmx"); err != nil {
log.Error("os.Create file err :(%v)", err)
return
}
defer file.Close()
buff = buff.Funcs(template.FuncMap{"unescaped": unescaped})
buff.Execute(io.Writer(file), scene)
fileName = file.Name()
jmeterLog = scene.ScriptPath + "jmg" + sceneSuffix
resJtl = scene.ScriptPath + "jtl" + sceneSuffix
}
scene.JmeterLog = jmeterLog
scene.JmeterFilePath = fileName
scene.ResJtl = resJtl
sceneID = scene.ID
// 批量选择脚本执行场景压测,则走如下逻辑,写 script 表
if scene.ID == 0 || scene.IsBatch {
//前端传入的scene 有 scripts , sceneId 没有传,走如下逻辑,写入scene 表
if !scene.IsBatch {
scene.SceneType = 1
if sceneID, err = s.AddScene(&scene); err != nil {
log.Error("s.AddScene err :(%v)", err)
return
}
scene.ID = sceneID
}
for _, script := range scene.Scripts {
script.IsSave = true
script.SceneID = sceneID
script.ID = 0
script.TestType = model.SCENE_SCRIPT_TYPE
//脚本落库
if resp, err = s.AddAndExcuScript(c, script, "", &scene, false, true); err != nil {
log.Error("s.AddAndExcuScript err :(%v)", err)
return
}
script.ID = resp.ScriptID
scripts = append(scripts, script)
}
scene.Scripts = scripts
}
// 有id需要更新数据库
if scene.ID != 0 && !scene.IsDebug {
if _, err = s.UpdateScene(&scene); err != nil {
log.Error("s.UpdateScene error :(%v)", err)
return
}
}
//获取 testNames testNameNicks loadTimes
for _, script := range scene.Scripts {
testNames = append(testNames, script.TestName)
testNameNicks = append(testNameNicks, script.TestName+sceneSuffix)
loadTimes = append(loadTimes, script.LoadTime)
}
sort.Ints(loadTimes)
//执行压测
if scene.IsExecute && len(scene.Scripts) > 0 {
ptestParam := model.DoPtestParam{
TestNames: testNames,
SceneName: scene.SceneName,
UserName: scene.UserName,
LoadTime: loadTimes[len(loadTimes)-1],
FileName: fileName,
Upload: false,
ProjectName: scene.SceneName,
JmeterLog: scene.JmeterLog,
ResJtl: scene.ResJtl,
Department: scene.Department,
Project: scene.Project,
APP: scene.APP,
ScriptID: 0,
DockerSum: 1,
TestNameNick: SliceToString(testNameNicks, ","),
TestNameNicks: testNameNicks,
Scripts: scene.Scripts,
SceneID: sceneID,
Type: model.PROTOCOL_SCENE, // 场景
IsDebug: scene.IsDebug,
Cookie: cookie,
Fusing: scene.Fusing,
}
if resp, err = s.DoPtest(c, ptestParam); err != nil {
log.Error("s.DoPtest err :(%v)", err)
return
}
//临时写法
resp.ScriptID = sceneID
}
return
}
// SaveScene Save Scene
func (s *Service) SaveScene(scene *model.Scene) error {
return s.dao.SaveScene(scene)
}
// SaveOrder Save Order
func (s *Service) SaveOrder(req model.SaveOrderReq, scene *model.Scene) error {
var (
flag = 1
length = len(req.GroupOrderList)
index = length - 1
bak int
)
if scene.SceneType == 1 {
for i := 0; i < length; i++ {
bak = req.GroupOrderList[i].GroupID
req.GroupOrderList[i].GroupID = flag
//防止越界
if i == index {
break
}
if bak != req.GroupOrderList[i+1].GroupID {
flag++
}
}
} else {
for i := 0; i < length; i++ {
bak = req.GroupOrderList[i].RunOrder
req.GroupOrderList[i].RunOrder = flag
//防止越界
if i == index {
break
}
if bak != req.GroupOrderList[i+1].RunOrder {
flag++
}
}
}
return s.dao.SaveOrder(req.GroupOrderList, scene)
}
// QueryRelation Query Relation
func (s *Service) QueryRelation(script *model.Script) (*model.QueryRelation, error) {
var (
groupId int
err error
)
if _, groupId, err = s.dao.QueryGroupId(script); err != nil {
log.Error("s.dao.QueryGroupId err :(%v)", err)
return nil, err
}
return s.dao.QueryRelation(groupId, script)
}
// DeleteAPI Delete API
func (s *Service) DeleteAPI(script *model.Script) error {
return s.dao.DeleteAPI(script)
}
// DoScenePtest Do Scene Ptest
func (s *Service) DoScenePtest(c context.Context, ptestScene model.DoPtestSceneParam, addPtest bool, cookie string) (resp model.DoPtestResp, err error) {
var (
scenes []*model.Scene
scripts []*model.Script
)
scene := model.Scene{ID: ptestScene.SceneID}
if scenes, err = s.dao.QueryScenes(&scene, 1, 1); err != nil {
log.Error("s.dao.QueryScenes err :(%v)", err)
return
}
script := model.Script{SceneID: ptestScene.SceneID}
if scripts, err = s.dao.QueryScripts(&script, 1, 300); err != nil {
log.Error("s.dao.QueryScripts err :(%v)", err)
return
}
scenes[0].Scripts = scripts
if len(scenes) > 0 {
sceneInfo := GetSceneInfo(scenes[0])
ptestParam := model.DoPtestParam{
TestNames: sceneInfo.TestNames,
SceneName: sceneInfo.SceneName,
UserName: ptestScene.UserName,
LoadTime: sceneInfo.MaxLoadTime,
FileName: scenes[0].JmeterFilePath,
Upload: false,
ProjectName: sceneInfo.SceneName,
ResJtl: sceneInfo.ResJtl,
JmeterLog: sceneInfo.JmeterLog,
Department: scenes[0].Department,
Project: scenes[0].Project,
APP: scenes[0].APP,
ScriptID: 0,
DockerSum: 1,
TestNameNick: SliceToString(sceneInfo.TestNameNicks, ","),
TestNameNicks: sceneInfo.TestNameNicks,
Scripts: sceneInfo.Scripts,
SceneID: ptestScene.SceneID,
Type: model.PROTOCOL_SCENE, // 场景
AddPtest: addPtest,
Cookie: cookie,
}
return s.DoPtest(c, ptestParam)
}
return
}
//DoScenePtestBatch dosceneptest batch
func (s *Service) DoScenePtestBatch(c context.Context, ptestScenes model.DoPtestSceneParams, cookie string) (err error) {
for _, SceneID := range ptestScenes.SceneIDs {
ptestScene := model.DoPtestSceneParam{SceneID: SceneID, UserName: ptestScenes.UserName}
go s.DoScenePtest(context.TODO(), ptestScene, false, cookie)
}
return
}
//GetSceneInfo get sceneInfo
func GetSceneInfo(scene *model.Scene) (sceneInfo model.SceneInfo) {
var (
loadTimes []int
testNames []string
testNameNicks []string
scripts []*model.Script
)
if scene == nil {
return
}
sceneSuffix := strconv.FormatInt(time.Now().Unix(), 10)
for _, script := range scene.Scripts {
loadTimes = append(loadTimes, script.LoadTime)
testNames = append(testNames, script.TestName)
testNameNicks = append(testNameNicks, script.TestName+sceneSuffix)
scripts = append(scripts, script)
}
sort.Ints(loadTimes)
sceneInfo = model.SceneInfo{
MaxLoadTime: loadTimes[len(loadTimes)-1],
JmeterLog: scene.JmeterLog + sceneSuffix,
ResJtl: scene.ResJtl + sceneSuffix,
LoadTimes: loadTimes,
TestNames: testNames,
TestNameNicks: testNameNicks,
SceneName: scene.SceneName,
Scripts: scripts,
}
return
}
// QueryExistAPI Query Exist API
func (s *Service) QueryExistAPI(c context.Context, sessionID string, req *model.APIInfoRequest) (res *model.APIInfoList, err error) {
// 获取服务树节点
var (
treeNodes []string
treeNodesd []string
)
if treeNodesd, err = s.QueryUserRoleNode(c, sessionID); err != nil {
log.Error("QueryUserRoleNode err (%v):", err)
}
treeNodes = append(treeNodesd, "")
if res, err = s.dao.QueryExistAPI(&req.Script, req.PageNum, req.PageSize, req.SceneID, treeNodes); err != nil {
return
}
for _, script := range res.ScriptList {
if script.APIHeader != "" {
if err = json.Unmarshal([]byte(script.APIHeader), &script.Headers); err != nil {
log.Error("get script header err : (%v),scriptId:(%d)", err, script.ID)
}
}
if script.ArgumentString != "" {
if err = json.Unmarshal([]byte(script.ArgumentString), &script.ArgumentsMap); err != nil {
log.Error("get script argument err: (%v), scriptId:(%d)", err, script.ID)
}
}
if script.OutputParams != "" {
if err = json.Unmarshal([]byte(script.OutputParams), &script.OutputParamsMap); err != nil {
log.Error("get script OutputParams err: (%v),scriptId:(%d)", err, script.ID)
}
}
}
return
}
// QueryPreview Query Preview
func (s *Service) QueryPreview(req *model.Script) (preRes *model.PreviewInfoList, err error) {
var (
list *model.GroupList
preList *model.PreviewList
preResd model.PreviewInfoList
)
if list, err = s.dao.QueryGroup(req.SceneID); err != nil {
return
}
for i := 0; i < len(list.GroupList); i++ {
// 或者使用var preInfo = &model.PreviewInfo{}
preInfo := new(model.PreviewInfo)
groupId := list.GroupList[i].GroupID
threadsSum := list.GroupList[i].ThreadsSum
loadTime := list.GroupList[i].LoadTime
readyTime := list.GroupList[i].ReadyTime
if preList, err = s.dao.QueryPreview(req.SceneID, groupId); err != nil {
return
}
preInfo.GroupID = groupId
preInfo.ThreadsSum = threadsSum
preInfo.LoadTime = loadTime
preInfo.ReadyTime = readyTime
preInfo.InfoList = preList.PreList
preResd.PreviewInfoList = append(preResd.PreviewInfoList, preInfo)
preRes = &preResd
}
return
}
// QueryParams Query Params
func (s *Service) QueryParams(req *model.Script) (res *model.UsefulParamsList, tempRes *model.UsefulParamsList, err error) {
//var paramList []string
var (
uParam *model.UsefulParams
)
res = &model.UsefulParamsList{}
if tempRes, err = s.dao.QueryUsefulParams(req.SceneID); err != nil {
return
}
if len(tempRes.ParamsList) > 0 {
for _, tempParam := range tempRes.ParamsList {
if strings.Contains(tempParam.OutputParams, ",") {
tempParamList := strings.Split(tempParam.OutputParams, ",")
for _, param := range tempParamList {
uParam = &model.UsefulParams{}
uParam.OutputParams = strings.Split(strings.Split(param, "\":\"")[0], "\"")[1]
res.ParamsList = append(res.ParamsList, uParam)
}
} else {
uParam = &model.UsefulParams{}
uParam.OutputParams = strings.Split(strings.Split(tempParam.OutputParams, "\":\"")[0], "\"")[1]
res.ParamsList = append(res.ParamsList, uParam)
}
}
} else {
// 赋值空数组
res.ParamsList = []*model.UsefulParams{}
}
return
}
// UpdateBindScene Update Bind Scene
func (s *Service) UpdateBindScene(bindScene *model.BindScene) (err error) {
return s.dao.UpdateBindScene(bindScene)
}
// QueryDrawRelation Query Draw Relation
func (s *Service) QueryDrawRelation(scene *model.Scene) (res model.DrawRelationList, tempRes *model.SaveOrderReq, err error) {
var (
//relationList model.DrawRelationList
edges []*model.Edge
edge *model.Edge
tempEdge *model.Edge
//nodes []*model.Node
node *model.Node
)
if tempRes, err = s.dao.QueryDrawRelation(scene); err != nil {
return
}
for k := 0; k < len(tempRes.GroupOrderList); k++ {
node = &model.Node{}
node.ID = tempRes.GroupOrderList[k].ID
node.Name = tempRes.GroupOrderList[k].TestName
res.Nodes = append(res.Nodes, node)
}
for i := 0; i < len(tempRes.GroupOrderList)-1; i++ {
edge = &model.Edge{}
tempEdge = &model.Edge{}
if tempRes.GroupOrderList[i].GroupID == tempRes.GroupOrderList[i+1].GroupID && tempRes.GroupOrderList[i].RunOrder == tempRes.GroupOrderList[i+1].RunOrder {
edge.Source = tempRes.GroupOrderList[i].TestName
edges = append(edges, edge)
if len(edges) > 0 {
//edges[i+1].Source =
tempEdge.Source = edges[i-1].Source
tempEdge.Target = tempRes.GroupOrderList[i+1].TestName
edges = append(edges, tempEdge)
}
} else if tempRes.GroupOrderList[i].GroupID == tempRes.GroupOrderList[i+1].GroupID && tempRes.GroupOrderList[i].RunOrder != tempRes.GroupOrderList[i+1].RunOrder {
edge.Source = tempRes.GroupOrderList[i].TestName
edge.Target = tempRes.GroupOrderList[i+1].TestName
edges = append(edges, edge)
} else if tempRes.GroupOrderList[i].GroupID != tempRes.GroupOrderList[i+1].GroupID {
edge.Source = tempRes.GroupOrderList[i].TestName
edges = append(edges, edge)
}
}
for j := 0; j < len(edges); j++ {
if edges[j].Target != "" {
//relationList.Edges = append(relationList.Edges, edges[j])
res.Edges = append(res.Edges, edges[j])
}
}
return
}
// DeleteDraft Delete Draft
func (s *Service) DeleteDraft(scene *model.Scene) error {
return s.dao.DeleteDraft(scene)
}
// QueryConfig Query Config
func (s *Service) QueryConfig(script *model.Script) (*model.GroupInfo, error) {
return s.dao.QueryConfig(script)
}
// DeleteScene Delete Scene
func (s *Service) DeleteScene(scene *model.Scene) error {
return s.dao.DeleteScene(scene)
}
//CopyScene copy scene
func (s *Service) CopyScene(c context.Context, scene *model.Scene, cookie string) (addScene model.AddScene, err error) {
var (
scripts []*model.Script
scenes []*model.Scene
sceneID int
resp model.DoPtestResp
)
script := model.Script{SceneID: scene.ID}
scened := model.Scene{ID: scene.ID}
//先根据 scene.ID 查询 scenes 和 scripts
if scenes, err = s.dao.QueryScenes(&scened, 1, 10); err != nil {
log.Error("s.dao.QueryScenes err :(%v)", err)
return
}
if scripts, err = s.dao.QueryScripts(&script, 1, 200); err != nil {
log.Error("s.dao.QueryScripts err :(%v)", err)
return
}
if len(scenes) != 0 {
scenes[0].ID = 0
scenes[0].UserName = scene.UserName
scenes[0].SceneName = scene.SceneName
//将新的scene 写入 数据库,并返回 sceneID
if sceneID, err = s.dao.AddScene(scenes[0]); err != nil {
log.Error("s.dao.AddScene err :(%v)", err)
return
}
// 将新的 script 写入 script 表
for _, sctd := range scripts {
sctd.SceneID = sceneID
sctd.ID = 0
if _, _, _, err = s.dao.AddScript(sctd); err != nil {
return
}
}
sce := model.Scene{
APP: scenes[0].APP,
Department: scenes[0].Department,
Project: scenes[0].Project,
ID: sceneID,
IsDebug: false,
IsExecute: false,
UserName: scene.UserName,
SceneName: scene.SceneName,
}
if resp, err = s.AddAndExecuScene(c, sce, cookie); err != nil {
log.Error("s.AddAndExecuScene err :(%v), (%v)", err, resp)
return
}
addScene.SceneID = sceneID
addScene.UserName = scene.UserName
}
return
}
// QueryFusing Query Fusing
func (s *Service) QueryFusing(script *model.Script) (res *model.FusingInfoList, err error) {
res, err = s.dao.QueryFusing(script)
for i := 0; i < len(res.FusingList)-1; i++ {
if res.FusingList[i] != res.FusingList[i+1] {
res.SetNull = true
return
}
}
return
}

View File

@@ -0,0 +1,447 @@
package service
import (
"context"
"go-common/app/admin/ep/melloi/model"
"testing"
"github.com/smartystreets/goconvey/convey"
)
func TestServiceQueryDraft(t *testing.T) {
convey.Convey("QueryDraft", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
UserName: "chenmeng",
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.QueryDraft(scene)
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceUpdateScene(t *testing.T) {
convey.Convey("UpdateScene", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
ID: 66,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
fusing, err := s.UpdateScene(scene)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(fusing, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceAddScene(t *testing.T) {
convey.Convey("AddScene", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
SceneName: "CMTest001",
UserName: "chenmeng",
Department: "test",
Project: "ep",
APP: "melloi",
IsDraft: 1,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
id, err := s.AddScene(scene)
convCtx.Convey("Then err should be nil.id should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(id, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceQueryAPI(t *testing.T) {
convey.Convey("QueryAPI", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
ID: 325,
SceneType: 1,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.QueryAPI(scene)
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceAddConfig(t *testing.T) {
convey.Convey("AddConfig", t, func(convCtx convey.C) {
var (
script = &model.Script{
ThreadsSum: 120,
LoadTime: 300,
ReadyTime: 10,
SceneID: 325,
GroupID: 1,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.AddConfig(script)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServiceQueryTree(t *testing.T) {
convey.Convey("QueryTree", t, func(convCtx convey.C) {
var (
script = &model.Script{
SceneID: 325,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.QueryTree(script)
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceQueryScenesByPage(t *testing.T) {
convey.Convey("QueryScenesByPage", t, func(convCtx convey.C) {
var (
c = context.Background()
sessionID = ""
scene = &model.Scene{
Department: "test",
Project: "ep",
APP: "melloi",
SceneName: "场景压测cmtest01",
UserName: "chenmeng",
}
page = &model.Pagination{
PageNum: int32(1),
PageSize: int32(20),
}
qsrq = &model.QuerySceneRequest{
Scene: *scene,
Pagination: *page,
Executor: "chenmeng",
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
rsp, err := s.QueryScenesByPage(c, sessionID, qsrq)
convCtx.Convey("Then err should be nil.rsp should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(rsp, convey.ShouldNotBeNil)
})
})
})
}
//func TestServiceAddAndExecuScene(t *testing.T) {
// convey.Convey("AddAndExecuScene", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// scene model.Scene
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// resp, err := s.AddAndExecuScene(c, scene)
// convCtx.Convey("Then err should be nil.resp should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(resp, convey.ShouldNotBeNil)
// })
// })
// })
//}
func TestServiceSaveScene(t *testing.T) {
convey.Convey("SaveScene", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
ID: 66,
IsDraft: 0,
SceneType: 2,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.SaveScene(scene)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServiceSaveOrder(t *testing.T) {
convey.Convey("SaveOrder", t, func(convCtx convey.C) {
var (
reqList = []*model.GroupOrder{
{
GroupID: 1,
RunOrder: 2,
ID: 980,
TestName: "cm-test",
},
{
GroupID: 1,
RunOrder: 1,
ID: 973,
TestName: "status111",
},
}
req = model.SaveOrderReq{
GroupOrderList: reqList,
}
scene = &model.Scene{
SceneType: 1,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.SaveOrder(req, scene)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServiceQueryRelation(t *testing.T) {
convey.Convey("QueryRelation", t, func(convCtx convey.C) {
var (
script = &model.Script{
ID: 1568,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.QueryRelation(script)
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceDeleteAPI(t *testing.T) {
convey.Convey("DeleteAPI", t, func(convCtx convey.C) {
var (
script = &model.Script{
ID: 1568,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.DeleteAPI(script)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
//func TestServiceDoScenePtest(t *testing.T) {
// convey.Convey("DoScenePtest", t, func(convCtx convey.C) {
// var (
// c = context.Background()
// ptestScene model.DoPtestSceneParam
// addPtest bool
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// resp, err := s.DoScenePtest(c, ptestScene, addPtest)
// convCtx.Convey("Then err should be nil.resp should not be nil.", func(convCtx convey.C) {
// convCtx.So(err, convey.ShouldBeNil)
// convCtx.So(resp, convey.ShouldNotBeNil)
// })
// })
// })
//}
func TestServiceDoScenePtestBatch(t *testing.T) {
convey.Convey("DoScenePtestBatch", t, func(convCtx convey.C) {
var (
c = context.Background()
ptestScenes model.DoPtestSceneParams
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.DoScenePtestBatch(c, ptestScenes, "")
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
//func TestServiceGetSceneInfo(t *testing.T) {
// convey.Convey("GetSceneInfo", t, func(convCtx convey.C) {
// var (
// scene = &model.Scene{}
// )
// convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
// sceneInfo := GetSceneInfo(scene)
// convCtx.Convey("Then sceneInfo should not be nil.", func(convCtx convey.C) {
// convCtx.So(sceneInfo, convey.ShouldNotBeNil)
// })
// })
// })
//}
func TestServiceQueryExistAPI(t *testing.T) {
convey.Convey("QueryExistAPI", t, func(convCtx convey.C) {
var (
c = context.Background()
sessionID = "24133ff6e561c0cdd0bfb1a8e7802f1c"
script = &model.Script{
SceneID: 325,
}
page = &model.Pagination{
PageNum: int32(1),
PageSize: int32(10),
}
req = &model.APIInfoRequest{
Script: *script,
Pagination: *page,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, err := s.QueryExistAPI(c, sessionID, req)
convCtx.Convey("Then err should be nil.res should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceQueryPreview(t *testing.T) {
convey.Convey("QueryPreview", t, func(convCtx convey.C) {
var (
req = &model.Script{
SceneID: 325,
GroupID: 1,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
preRes, err := s.QueryPreview(req)
convCtx.Convey("Then err should be nil.preRes should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(preRes, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceQueryParams(t *testing.T) {
convey.Convey("QueryParams", t, func(convCtx convey.C) {
var (
req = &model.Script{
SceneID: 325,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, tempRes, err := s.QueryParams(req)
convCtx.Convey("Then err should be nil.res,tempRes should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(tempRes, convey.ShouldNotBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceUpdateBindScene(t *testing.T) {
convey.Convey("UpdateBindScene", t, func(convCtx convey.C) {
var (
bindScene = &model.BindScene{
SceneID: 325,
ID: "2000",
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.UpdateBindScene(bindScene)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServiceQueryDrawRelation(t *testing.T) {
convey.Convey("QueryDrawRelation", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
ID: 980,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
res, tempRes, err := s.QueryDrawRelation(scene)
convCtx.Convey("Then err should be nil.res,tempRes should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(tempRes, convey.ShouldNotBeNil)
convCtx.So(res, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceDeleteDraft(t *testing.T) {
convey.Convey("DeleteDraft", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
UserName: "chenmeng",
ID: 980,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.DeleteDraft(scene)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}
func TestServiceQueryConfig(t *testing.T) {
convey.Convey("QueryConfig", t, func(convCtx convey.C) {
var (
script = &model.Script{
SceneID: 325,
GroupID: 1,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
p1, err := s.QueryConfig(script)
convCtx.Convey("Then err should be nil.p1 should not be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
convCtx.So(p1, convey.ShouldNotBeNil)
})
})
})
}
func TestServiceDeleteScene(t *testing.T) {
convey.Convey("DeleteScene", t, func(convCtx convey.C) {
var (
scene = &model.Scene{
ID: 325,
}
)
convCtx.Convey("When everything goes positive", func(convCtx convey.C) {
err := s.DeleteScene(scene)
convCtx.Convey("Then err should be nil.", func(convCtx convey.C) {
convCtx.So(err, convey.ShouldBeNil)
})
})
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
package service
import (
"context"
"encoding/json"
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
//AddSciptSnap add scriptSnap
func (s *Service) AddSciptSnap(ptestParam model.DoPtestParam, excuteId string) (scriptSnapIds []int, err error) {
var (
scripts []*model.Script
scriptSnapId int
)
//场景脚本快照保存逻辑
if ptestParam.Type == model.SCENE_SCRIPT_TYPE {
for _, script := range ptestParam.Scripts {
script.SceneID = ptestParam.SceneID
if scriptSnapId, err = s.AddScriptSnapInfo(script, excuteId); err != nil {
log.Error("s.AddScriptSnapInfo err :(%v)", err)
return
}
scriptSnapIds = append(scriptSnapIds, scriptSnapId)
}
return
}
//单场景http 脚本的快照保存
if scripts, err = s.QueryScripts(&model.Script{ID: ptestParam.ScriptID}, 1, 5); err != nil {
log.Error(" s.QueryScripts err :(%v)", err)
return
}
if scriptSnapId, err = s.AddScriptSnapInfo(scripts[0], excuteId); err != nil {
log.Error("s.AddScriptSnapInfo err :(%v)", err)
return
}
scriptSnapIds = append(scriptSnapIds, scriptSnapId)
return
}
//AddScriptSnapInfo Add scriptSnap Info
func (s *Service) AddScriptSnapInfo(script *model.Script, excuteId string) (scriptSnapId int, err error) {
var JSON []byte
scriptSnap := model.ScriptSnap{ScriptID: script.ID}
script.ID = 0
if JSON, err = json.Marshal(script); err != nil {
log.Error(" json.Marshal(script) error :(%v)", err)
return
}
if err = json.Unmarshal([]byte(string(JSON)), &scriptSnap); err != nil {
return
}
scriptSnap.ExecuteID = excuteId
if scriptSnapId, err = s.dao.AddScriptSnap(&scriptSnap); err != nil {
log.Error("s.dao.AddScriptSnap error :(%v)", err)
return
}
return
}
//AddSnap add snap
func (s *Service) AddSnap(c context.Context, ptestParam model.DoPtestParam, executeID, jobName, jobNamed string) (scriptSnapIDs []int, err error) {
if ptestParam.Type == model.PROTOCOL_GRPC {
if scriptSnapIDs, err = s.AddGRPCSnap(ptestParam.ScriptID, executeID); err != nil {
log.Error("save grpc snap failed,(%v)", err)
s.DeleteJob(context.TODO(), jobName)
return
}
} else {
//用jobNamed 表示执行id即是 ExcuteId
if scriptSnapIDs, err = s.AddSciptSnap(ptestParam, jobNamed); err != nil {
log.Error("s.AddSciptSnap err :(%v)", err)
s.DeleteJob(context.TODO(), jobName)
return
}
}
return
}
// AddGRPCSnap Add GRPC Snap
func (s *Service) AddGRPCSnap(grpcID int, executeID string) (snapIDs []int, err error) {
var (
grpc *model.GRPC
grpcSnap *model.GRPCSnap
j []byte
)
if grpc, err = s.dao.QueryGRPCByID(grpcID); err != nil {
return
}
grpcSnap = &model.GRPCSnap{GRPCID: grpc.ID, ExecuteID: executeID}
grpc.ID = 0
if j, err = json.Marshal(grpc); err != nil {
return
}
if err = json.Unmarshal([]byte(string(j)), grpcSnap); err != nil {
return
}
if err = s.dao.CreateGRPCSnap(grpcSnap); err != nil {
return
}
return []int{grpcSnap.ID}, nil
}

View File

@@ -0,0 +1,56 @@
package service
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"go-common/app/admin/ep/melloi/model"
)
var (
script = model.Script{
ID: 1,
Type: 1,
TestName: "testName",
ThreadsSum: 10,
LoadTime: 10,
ReadyTime: 100,
ProcType: "https",
URL: "live.bilibili.com/as/xxx",
Domain: "live.bilibili.com",
Port: "80",
Path: "/x/v2/search?actionKey=appkey&appkey=27eb53fc9058f8c3&build=6790&device=phone&duration=0&from_source=app_search&highlight=1&keyword=${test}&access_key=${access_key}",
Method: "Get",
UpdateBy: "hujianping",
JmeterLog: "/data/jmeter-log/test/ep/melloi/",
ResJtl: "/data/jmeter-log/test/ep/melloi/",
}
fileWrite = false
)
func Test_Script(t *testing.T) {
Convey("add script", t, func() {
_, _, err := s.AddScript(&script, fileWrite)
So(err, ShouldBeNil)
})
Convey("query script", t, func() {
_, err := s.QueryScripts(&script, 1, 1)
So(err, ShouldBeNil)
})
Convey("count query script", t, func() {
cs := s.CountQueryScripts(&script)
So(cs, ShouldNotBeNil)
})
Convey("delete script", t, func() {
err := s.DelScript(script.ID)
So(err, ShouldBeNil)
})
Convey("update script", t, func() {
_, err := s.UpdateScript(&script)
So(err, ShouldBeNil)
})
Convey("add jmeter sample", t, func() {
_, err := s.AddJmeterSample(&script)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,41 @@
package service
import (
"context"
"go-common/app/admin/ep/melloi/conf"
"go-common/app/admin/ep/melloi/dao"
"github.com/jinzhu/gorm"
"github.com/robfig/cron"
)
// Service struct
type Service struct {
c *conf.Config
dao *dao.Dao
DB *gorm.DB
cron *cron.Cron
}
// New init
func New(c *conf.Config) (s *Service) {
s = &Service{
c: c,
dao: dao.New(c),
cron: cron.New(),
}
s.cron.Start()
return s
}
// Close Service
func (s *Service) Close() {
s.dao.Close()
}
//Ping test interface
func (s *Service) Ping(c context.Context) (err error) {
err = s.dao.Ping(c)
return
}

View File

@@ -0,0 +1,24 @@
package service
import (
"context"
"flag"
"path/filepath"
"time"
"go-common/app/admin/ep/melloi/conf"
)
var (
s *Service
c context.Context
)
func init() {
dir, _ := filepath.Abs("../cmd/convey-test.toml")
flag.Set("conf", dir)
conf.Init()
s = New(conf.Conf)
c = context.TODO()
time.Sleep(time.Second)
}

View File

@@ -0,0 +1,48 @@
package service
import (
"os"
"strconv"
"strings"
"time"
"go-common/library/log"
)
const pathPerm = 0775
// uniqueFolderPath Unique Folder Path
func (s *Service) uniqueFolderPath(path string) (uniquePath string, err error) {
uniquePath = path + strconv.Itoa(time.Now().Nanosecond()) + "/"
for {
var isExists bool
if isExists, err = exists(uniquePath); err != nil {
return
}
if !isExists {
if err = os.MkdirAll(uniquePath, pathPerm); err != nil {
uniquePath = ""
log.Error("Create err ... (%v)", err)
return
}
break
} else {
uniquePath = path + strconv.Itoa(time.Now().Nanosecond()) + "/"
}
}
return
}
// getSessionInCookie get session
func (s *Service) getSessionInCookie(cookie string) (session string) {
cookieStr := strings.Split(cookie, ";")
for _, value := range cookieStr {
strt := strings.TrimSpace(value)
strs := strings.Split(strt, "=")
if strs[0] == "_AJSESSIONID" {
session = strs[1]
return
}
}
return
}

View File

@@ -0,0 +1,130 @@
package service
import (
"context"
"go-common/app/admin/ep/melloi/model"
"go-common/library/ecode"
)
// QueryServiceTreeToken get tree token by user sessionID
func (s *Service) QueryServiceTreeToken(c context.Context, sessionID string) (token string, err error) {
return s.dao.QueryServiceTreeToken(c, sessionID)
}
// QueryUserRoleNode QueryUserRoleNode
func (s *Service) QueryUserRoleNode(c context.Context, sessionID string) (apps []string, err error) {
var (
roleApp []*model.RoleApp
token string
)
if token, err = s.QueryServiceTreeToken(c, sessionID); err != nil {
return
}
if roleApp, err = s.dao.QueryUserRoleApp(c, token); err != nil {
err = ecode.MerlinGetUserTreeFailed
return
}
for _, app := range roleApp {
// Role 1:管理员 2:研发 3:测试 4:运维 5:访客
// Type 1.公司 2.部门 3.项目 4. 应用 5.环境 6.挂载点
if app.Role <= 3 && app.Type == 4 {
apps = append(apps, app.Name)
}
}
return
}
// QueryUserTree get user tree
func (s *Service) QueryUserTree(c context.Context, sessionID string) (firstRetMap []map[string]interface{}, err error) {
var (
treeMap *model.UserTree
token string
)
if token, err = s.QueryServiceTreeToken(c, sessionID); err != nil {
err = ecode.MerlinGetUserTreeFailed
return
}
if treeMap, err = s.dao.QueryUserTree(c, token); err != nil {
err = ecode.MerlinGetUserTreeFailed
return
}
if treeMap.Bilibili == nil {
return
}
firstLevelMapTmp := treeMap.Bilibili
firstLevelMap := firstLevelMapTmp["children"].(map[string]interface{})
if firstLevelMap == nil {
return
}
for firstLevelKey, firstLevelChildren := range firstLevelMap {
var secondRetMap []map[string]interface{}
secondLevelMapTmp := firstLevelChildren.(map[string]interface{})
if secondLevelMapTmp["children"] == nil {
continue
}
secondLevelMap := secondLevelMapTmp["children"].(map[string]interface{})
for secondLevelKey, secondLevelChildren := range secondLevelMap {
var thirdRetMap []map[string]interface{}
thirdLevelMapTmp := secondLevelChildren.(map[string]interface{})
if thirdLevelMapTmp["children"] == nil {
continue
}
thirdLevelMap := thirdLevelMapTmp["children"].(map[string]interface{})
for thirdLevelKey, thirdLevelChildren := range thirdLevelMap {
if thirdLevelKey != "" && thirdLevelChildren != nil {
thirdTmp := make(map[string]interface{})
thirdTmp["value"] = thirdLevelKey
thirdTmp["label"] = thirdLevelKey
thirdRetMap = append(thirdRetMap, thirdTmp)
}
}
if secondLevelKey != "" {
secondTmp := make(map[string]interface{})
secondTmp["value"] = secondLevelKey
secondTmp["label"] = secondLevelKey
secondTmp["children"] = thirdRetMap
secondRetMap = append(secondRetMap, secondTmp)
}
}
if firstLevelKey != "" {
firstTmp := make(map[string]interface{})
firstTmp["value"] = firstLevelKey
firstTmp["label"] = firstLevelKey
firstTmp["children"] = secondRetMap
firstRetMap = append(firstRetMap, firstTmp)
}
}
return
}
// QueryTreeAdmin get tree admin
func (s *Service) QueryTreeAdmin(c context.Context, path string, sessionID string) (roles []*model.TreeRole, err error) {
var (
tar *model.TreeAdminResponse
token string
)
if token, err = s.QueryServiceTreeToken(c, sessionID); err != nil {
return
}
if tar, err = s.dao.QueryTreeAdmin(c, path, token); err != nil {
err = ecode.MerlinGetUserTreeFailed
return
}
//todo: 如果没有服务树权限需要申请
for _, v := range tar.Data {
if v.Role == 1 {
roles = append(roles, v)
}
}
return
}

View File

@@ -0,0 +1,31 @@
package service
import (
"go-common/app/admin/ep/melloi/model"
"go-common/library/log"
)
// QueryUser query user info
func (s *Service) QueryUser(userName string) (user *model.User, err error) {
return s.CreateUser(userName)
}
// CreateUser create user
func (s *Service) CreateUser(userName string) (userData *model.User, err error) {
//此处因为业务因素出现错误需要继续执行不能retrun !!!
if userData, err = s.dao.QueryUserByUserName(userName); err != nil {
log.Error("s.dao.QueryUserByUserName err :(%v)", err)
}
if userData.ID == 0 {
user := model.User{Name: userName, Email: userName + "@bilibili.com", Active: "1", Accept: -1}
s.dao.AddUser(&user)
userData, err = s.dao.QueryUserByUserName(userName)
}
return
}
// UpdateUser update user
func (s *Service) UpdateUser(user *model.User) error {
return s.dao.UpdateUser(user)
}

View File

@@ -0,0 +1,14 @@
package service
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_QueryUserInfo(t *testing.T) {
Convey("query user or create user", t, func() {
_, err := s.QueryUser("zhanglu")
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,90 @@
package service
import (
"context"
"strconv"
"time"
"go-common/app/admin/ep/melloi/model"
)
//AddWechatSend add wechat send
func (s *Service) AddWechatSend(c context.Context, cookie, content string) (msgSendRes *model.MsgSendRes, err error) {
return s.dao.AddWechatSend(c, cookie, content)
}
// AddWechatContent Add Wechat Content
func AddWechatContent(ptestParam model.DoPtestParam, reportSuID int, jobName string, userService map[string][]string) (content string) {
var (
url string
lay = "2006-01-02 15:04:05"
ptestDetailURL string
serviceList = make(map[string][]string)
serviceDep string
serviceName string
)
if ptestParam.Type == model.PROTOCOL_HTTP || ptestParam.Type == model.PROTOCOL_SCENE {
ptestDetailURL = "http://melloi.bilibili.co/#/ptest-detail?reportSuId=" + strconv.Itoa(reportSuID)
}
if ptestParam.Type == model.PROTOCOL_GRPC {
ptestDetailURL = "http://melloi.bilibili.co/#/ptest-detail-grpc?reportSuId=" + strconv.Itoa(reportSuID)
}
url = ptestParam.URL
if ptestParam.Type == model.PROTOCOL_SCENE {
for _, script := range ptestParam.Scripts {
url = url + "\n" + script.URL
}
}
// 增加依赖服务列表
for _, v := range userService {
for _, service := range v {
serviceList[service] = nil
}
}
for k := range serviceList {
serviceDep += "\n" + k
}
loadTime := strconv.Itoa(ptestParam.LoadTime) + "s"
if ptestParam.Upload {
loadTime = "脚本用户上传时间1800s以内"
url = "脚本用户上传url 未知"
}
serviceName = ptestParam.Department + "." + ptestParam.Project + "." + ptestParam.APP
content = "执行人:" + ptestParam.UserName + "\n压测服务" + serviceName + "\n" + "压测接口:" + url + "\n开始时间" + time.Now().Format(lay) + "\n持续时间" +
loadTime + "\n压测容器" + jobName + "\n报告地址" + ptestDetailURL + "\n压测依赖服务" + serviceDep
return
}
// AddWechatDependServiceContent add wechat depend Service Content
func AddWechatDependServiceContent(ptestParam model.DoPtestParam, userService map[string][]string, reportSuId int, user string) (content string) {
var (
url string
lay = "2006-01-02 15:04:05"
ptestDetailURL string
serviceList string
)
if ptestParam.Type == model.PROTOCOL_HTTP || ptestParam.Type == model.PROTOCOL_SCENE {
ptestDetailURL = "http://melloi.bilibili.co/#/ptest-detail?reportSuId=" + strconv.Itoa(reportSuId)
}
if ptestParam.Type == model.PROTOCOL_GRPC {
ptestDetailURL = "http://melloi.bilibili.co/#/ptest-detail-grpc?reportSuId=" + strconv.Itoa(reportSuId)
}
url = ptestParam.URL
if ptestParam.Type == model.PROTOCOL_SCENE {
for _, script := range ptestParam.Scripts {
url = url + "\n" + script.URL
}
}
for _, service := range userService[user] {
serviceList += "\n" + service
}
serviceName := ptestParam.Department + "." + ptestParam.Project + "." + ptestParam.APP
content = "[Melloi压测依赖提醒] \n 压测服务:" + serviceName + "\n 压测接口:" + ptestParam.URL + "\n 压测时间:" + time.Now().Format(lay) + "\n 压测时长: " +
strconv.Itoa(ptestParam.LoadTime) + "\n 报告地址:" + ptestDetailURL + "\n 依赖服务:" + serviceList
return
}