package monkey import ( "autogo/common" "autogo/controllers" "autogo/dbsql" "autogo/models" "fmt" "net/http" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/spf13/cast" ) // @Tags Monkey相关 /api/monkey/v1/ // @Summary 新建Monkey任务 // @Description 新建Monkey任务,创建成功后将会发送指令到monkey服务,初始status为WAITTING-等待中 // @accept x-www-form-urlencoded // @Param project formData string false "项目名" // @Param package_url formData string false "测试包下载链接" // @Param package_name formData string true "应用包名" // @Param launch_activity formData string false "启动activity页" // @Param platform formData string true "平台adr/ios" // @Param run_time formData string false "运行时间(s),默认两小时(7200s)" // @Param creator formData string false "任务创建者" // @Param mode formData string false "调用服务的方式,0-cli模式/1-api模式,默认cli" // @Success 200 {object} models.Response "返回创建的任务id" // @Router /api/monkey/v1/create_task [post] func CreateTask(c *gin.Context) { rsp := controllers.NewResponse() db, err := dbsql.GetConn(dbsql.DSN) if err != nil { rsp.Error(err.Error()) c.JSON(http.StatusOK, rsp) } defer dbsql.Close(db) var task models.MonkeyTask task.Project = c.PostForm("project") task.PackageURL = c.PostForm("package_url") task.PackageName = c.PostForm("package_name") task.LaunchActivity = c.PostForm("launch_activity") task.Platform = c.PostForm("platform") task.RunTime = cast.ToInt(c.DefaultPostForm("run_time", "7200")) task.Creator = c.PostForm("creator") task.Status = "WAITTING" // || task.Creator == "" if task.PackageName == "" || task.LaunchActivity == "" || task.Platform == "" || task.RunTime <= 10 { rsp.Error("get formdata error") rsp.Data = task c.JSON(http.StatusOK, rsp) return } var devices []models.Device is_online := false db.Model(models.Device{}).Where("project = ?", task.Project).Find(&devices) if len(devices) < 1 { rsp.Error("该产品没有注册设备,请检查") c.JSON(http.StatusOK, rsp) return } m := GetAndroidDevices() for _, v := range devices { if _, ok := m[v.Udid]; ok { is_online = true break } } if !is_online { rsp.Error("该产品没有设备在线,请检查") c.JSON(http.StatusOK, rsp) return } db.Model(models.MonkeyTask{}).Create(&task) if db.Error != nil { rsp.Error(db.Error.Error()) c.JSON(http.StatusOK, rsp) } rsp.Success() rsp.Data = task c.JSON(http.StatusOK, rsp) go RunAndroidMonkeyCmd(task, "") } // @Tags Monkey相关 /api/monkey/v1/ // @Summary 更新Monkey任务状态 // @Description 更新Monkey任务状态,进行中为RUNNING,已完成为FINISH,如有错误为ERROR // @accept x-www-form-urlencoded // @Param task_id formData int true "任务id" // @Param status formData string false "要更新的任务状态" // @Success 200 {object} models.Response "返回更新后的任务信息" // @Router /api/monkey/v1/task/status [post] func UpdateTaskStatus(c *gin.Context) { rsp := controllers.NewResponse() task_id := cast.ToInt(c.PostForm("task_id")) status := c.PostForm("status") if task_id <= 0 { rsp.Error("task_id error") c.JSON(http.StatusOK, rsp) return } db, err := dbsql.GetConn(dbsql.DSN) if err != nil { rsp.Error(err.Error()) c.JSON(http.StatusOK, rsp) return } defer dbsql.Close(db) var task models.MonkeyTask db.Where("id = ?", task_id).Find(&task) if task.Id <= 0 { rsp.Error("任务不存在") c.JSON(http.StatusOK, rsp) return } task.Status = status db.Save(&task) fmt.Println("更新设备状态为", status) if status == "FINISH" { db.Table(task.TableName()).Model(models.MonkeyTask{}).Where("id = ?", task.Id).Update("end_time", time.Now().Unix()) common.PushMonkeyResult(task) } rsp.Success() rsp.Data = task c.JSON(http.StatusOK, rsp) } // @Tags Monkey相关 /api/monkey/v1/ // @Summary 更新Monkey任务崩溃数量 // @Description 更新Monkey任务崩溃数量 // @accept x-www-form-urlencoded // @Param task_id formData int true "任务id" // @Param crash_count formData string false "要更新的崩溃数量" // @Success 200 {object} models.Response "返回更新后的任务信息" // @Router /api/monkey/v1/task/crash_count [post] func UpdateTaskCrashCount(c *gin.Context) { rsp := controllers.NewResponse() task_id := cast.ToInt(c.PostForm("task_id")) // crash_count := cast.ToInt(c.DefaultPostForm("crash_count", "0")) if task_id <= 0 { rsp.Error("task_id error") c.JSON(http.StatusOK, rsp) return } // if crash_count <= 0 { // rsp.Error("crash_count 错误或等于0,不更新") // c.JSON(http.StatusOK, rsp) // return // } db, err := dbsql.GetConn(dbsql.DSN) if err != nil { rsp.Error(err.Error()) c.JSON(http.StatusOK, rsp) } defer dbsql.Close(db) var count int64 db.Model(models.MonkeyResult{}).Where("task_id = ?", task_id).Count(&count) // var task models.MonkeyTask // db.Where("id = ?", task_id).Find(&task) // if task.Id <= 0 { // rsp.Error("任务不存在") // c.JSON(http.StatusOK, rsp) // return // } db.Model(models.MonkeyTask{}).Where("id = ?", task_id).Update("crash_count", count) rsp.Success() rsp.Data = count c.JSON(http.StatusOK, rsp) } // @Tags Monkey相关 /api/monkey/v1/ // @Summary 获取任务列表 // @Description 获取任务列表 // @accept x-www-form-urlencoded // @Param page_size query int false "每页大小,默认为10" // @Param page_index query int false "第几页,默认为第一页" // @Param project query string false "项目名称" // @Param product query string false "产品名称" // @Param version query string false "版本号,模糊匹配" // @Param pf query string false "系统平台:ios/adr" // @Param has_error query int false "传1筛选有异常的任务" // @Param status query string false "任务状态(大写)" // @Success 200 {object} models.Response "返回更新后的任务信息" // @Router /api/monkey/v1/tasks [get] func GetTasks(c *gin.Context) { var rsp models.Response var p models.Page pageIndex, err := strconv.Atoi(c.DefaultQuery("page_index", "1")) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } p.Index = pageIndex pageSize, err := strconv.Atoi(c.DefaultQuery("page_size", "10")) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } p.Size = pageSize // param-项目 project := c.DefaultQuery("project", "") // param-系统平台 pf := c.DefaultQuery("pf", "") if pf != "" && pf != "adr" && pf != "ios" { c.JSON(http.StatusOK, rsp.Error("参数pf异常")) return } // param-是否有异常 has_error := cast.ToInt(c.DefaultQuery("has_error", "0")) // param-任务状态 status := c.DefaultQuery("status", "") db, err := dbsql.GetConn(dbsql.DSN) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } defer dbsql.Close(db) var list []models.MonkeyTask db = db.Model(&models.MonkeyTask{}) var lenght int64 // 参数判断 if project != "" { db = db.Where("project = ?", project) } if c.Query("product") != "" { db = db.Where("product = ?", c.Query("product")) } if c.Query("version") != "" { db = db.Where("version LIKE ?", "%"+c.Query("version")+"%") } if pf != "" { db = db.Where("platform = ?", pf) } if has_error == 1 { db = db.Where("crash_count > 0") } if status != "" { db = db.Where("status = ?", status) } db = db.Where("is_del = 0").Count(&lenght) db = dbsql.SetPageQuery(db, pageIndex, pageSize) db.Order("id desc").Find(&list) // lenght = len(list) dbsql.Close(db) db, _ = dbsql.GetConn(dbsql.DSN) for i, _ := range list { var count int64 db.Model(models.MonkeyActivity{}).Where("task_id = ?", list[i].Id).Count(&count) list[i].CoveredAcitvities = int(count) // 如果状态在 进行中 or 取消中,则检查进程状态 // if v.Status == "RUNNING" || v.Status == "CANCELING" { // var pids []models.MonkeyPid // db.Model(models.MonkeyPid{}).Where("task_id = ?", v.Id).Find(&pids) // if code := checkTaskPids(pids); code == 0 { // list[i].Status = "RUNNING" // } else if code == 1 { // if v.Status == "CANCELING" { // list[i].Status = "CANCEL" // db.Model(models.MonkeyTask{}).Where("id = ?", v.Id).Update("status", list[i].Status) // } else if v.Status == "RUNNING" { // list[i].Status = "FINISH" // db.Model(models.MonkeyTask{}).Where("id = ?", v.Id).Update("status", list[i].Status) // } // } // } } dbsql.Close(db) if p.Size >= int(lenght) { p.MaxPage = 1 } else { p.MaxPage = int64(lenght/int64(p.Size) + 1) } p.List = list p.Total = int(lenght) rsp.Success() rsp.Data = p c.JSON(http.StatusOK, rsp) } // @Tags Monkey相关 /api/monkey/v1/ // @Summary 获取monkey任务信息 // @Description 根据id获取monkey任务信息 // @accept x-www-form-urlencoded // @Param id query int false "任务id" // @Success 200 {object} models.Response "返回monkey任务信息" // @Router /api/monkey/v1/task [get] func GetTaskById(c *gin.Context) { var rsp models.Response db, err := dbsql.GetConn(dbsql.DSN) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } defer dbsql.Close(db) var data models.MonkeyTask db = db.Model(&models.MonkeyTask{}) db.Model(models.MonkeyTask{}).Where("id = ?", c.DefaultQuery("id", "0")).Last(&data) dbsql.Close(db) db, _ = dbsql.GetConn(dbsql.DSN) var count int64 db.Where("task_id = ?", data.Id).Count(&count) data.CoveredAcitvities = int(count) dbsql.Close(db) if data.Id < 1 { rsp.Error("查找不到monkey任务信息,id=" + c.Query("id")) c.JSON(http.StatusOK, rsp) return } rsp.Success() rsp.Data = data c.JSON(http.StatusOK, rsp) } // @Tags 文件相关 /webdav // @Summary 上传Logcat日志 // @Description 上传Logcat日志 // @accept x-www-form-urlencoded // @Success 200 {object} models.Response "返回成功或失败" // @Router /webdav/monkey/task/:id/:filename [put] func TaskReport(c *gin.Context) { fmt.Println(c.Request.Header) data, err := c.GetRawData() if err != nil { fmt.Println(err) c.String(http.StatusOK, "OK") return } fmt.Println(len(data)) CreateDir("./files") CreateDir("./files/monkey") CreateDir("./files/monkey/log") CreateDir("./files/monkey/log/" + c.Param("id")) CreateAndWriteFile("./files//monkey/log/"+c.Param("id")+"/"+c.Param("filename"), data) c.String(http.StatusOK, "OK") } // @Tags Monkey相关 /api/monkey/v1/ // @Summary 上报进程id // @Description 上传进程id并关联到任务 // @accept x-www-form-urlencoded // @Param task_id formData int true "任务id" // @Param pid formData int true "进程id" // @Success 200 {object} models.Response "返回成功或失败" // @Router /api/monkey/v1/task/pid [post] func UpdatePids(c *gin.Context) { var rsp models.Response db, err := dbsql.GetConn(dbsql.DSN) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } defer dbsql.Close(db) var obj models.MonkeyPid if task_id := cast.ToInt(c.PostForm("task_id")); task_id > 0 { obj.TaskId = task_id } else { rsp.Error("task_id获取失败") c.JSON(http.StatusOK, rsp) return } if pid := cast.ToInt(c.PostForm("pid")); pid > 0 { obj.PId = pid } else { rsp.Error("pid获取失败") c.JSON(http.StatusOK, rsp) return } db.Create(&obj) rsp.Success() rsp.Data = obj c.JSON(http.StatusOK, rsp) } // @Tags Monkey相关 /api/monkey/v1/ // @Summary 上报运行设备 // @Description 上报运行设备Udid,调用后追加到指定任务的设备字段中 // @accept x-www-form-urlencoded // @Param task_id formData int true "任务id" // @Param udid formData string true "设备udid" // @Success 200 {object} models.Response "返回成功或失败" // @Router /api/monkey/v1/task/update_devices [post] func UpdateDevices(c *gin.Context) { var rsp models.Response task_id := cast.ToInt(c.PostForm("task_id")) if task_id < 1 { rsp.Error("task_id获取失败") c.JSON(http.StatusOK, rsp) return } udid := c.PostForm("udid") if udid == "" { rsp.Error("pid获取失败") c.JSON(http.StatusOK, rsp) return } db, err := dbsql.GetConn(dbsql.DSN) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } defer dbsql.Close(db) var data models.MonkeyTask db = db.Model(&models.MonkeyTask{}) db.Model(models.MonkeyTask{}).Where("id = ?", task_id).Last(&data) // 处理 if strings.Contains(data.Devices, ",") || len(data.Devices) > 0 { data.Devices += "," + udid } else { data.Devices = udid } db.Model(models.MonkeyTask{}).Where("id = ?", task_id).Update("devices", data.Devices) rsp.Success() rsp.Data = data c.JSON(http.StatusOK, rsp) } // @Tags Monkey相关 /api/monkey/v1/ // @Summary 停止monkey任务 // @Description 停止monkey任务,杀死所有关联任务的进程id // @accept x-www-form-urlencoded // @Param task_id formData int true "任务id" // @Success 200 {object} models.Response "返回成功或失败" // @Router /api/monkey/v1/task/stop [post] func StopMonkeyTask(c *gin.Context) { var rsp models.Response task_id := cast.ToInt(c.PostForm("task_id")) if task_id < 1 { c.JSON(http.StatusOK, rsp.Error("task_id error: "+c.PostForm("task_id"))) return } db, err := dbsql.GetConn(dbsql.DSN) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } defer dbsql.Close(db) list := GetTaskFromDocker(task_id) for _, v := range list { StopDockerContainer(v.Name) } // 重置设备状态 var task models.MonkeyTask db.Table(task.TableName()).Model(models.MonkeyTask{}).Where("id = ?", task_id).Last(&task) if strings.Contains(task.Devices, ",") { // 多台设备 for _, v := range strings.Split(task.Devices, ",") { db.Table("device").Model(models.Device{}).Where("udid = ?", v).Update("status", "online") } } else if len(task.Devices) > 1 { // 有一台设备 if task.Platform == "adr" { db.Table("device").Model(models.Device{}).Where("udid = ?", task.Devices).Update("status", "online") } else if task.Platform == "ios" { db_oms, err := dbsql.GetConn(dbsql.DSN_qaoms()) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } defer dbsql.Close(db_oms) var dv models.DeviceV2 db_oms.Table("qa_devices").Model(models.DeviceV2{}).Where("udid = ?", task.Devices).Last(&dv) dv.Status = "online" dv.StatusDetails = "Monkey-任务停止,释放设备" db_oms.Save(&dv) } } db.Table("monkey_task").Model(models.MonkeyTask{}).Where("id = ?", task_id).Update("status", "CANCEL") db.Table("monkey_task").Model(models.MonkeyTask{}).Where("id = ?", task_id).Update("end_time", time.Now().Unix()) rsp.Data = "done" c.JSON(http.StatusOK, rsp.Success()) return var pids []int if task_id := cast.ToInt(c.PostForm("task_id")); task_id > 0 { db, err := dbsql.GetConn(dbsql.DSN) if err != nil { c.JSON(http.StatusOK, rsp.Error(err.Error())) return } defer dbsql.Close(db) var task models.MonkeyTask db.Model(models.MonkeyTask{}).Where("id = ?", task_id).First(&task) // 如果monkey任务未开始(status=WAITTING,INIT),直接更新状态为CANCEL if task.Status == "WAITTING" || task.Status == "INIT" { db.Model(models.MonkeyTask{}).Where("id = ?", task_id).Update("status", "CANCEL") rsp.Success() rsp.Data = "from " + task.Status + " to CANCEL" c.JSON(http.StatusOK, rsp) return } db.Model(models.MonkeyTask{}).Where("id = ?", task_id).Update("status", "CANCELING") // 更新设备状态 db.Model(models.MonkeyTask{}).Where("id = ?", task_id).Last(&task) if strings.Contains(task.Devices, ",") { } else { var device models.Device db.Table("device").Model(models.Device{}).Where("udid = ?", task.Devices).Last(&device) if device.Status == "busy" { db.Table("device").Model(models.Device{}).Where("udid = ?", task.Devices).Update("status", "online") } } var list []models.MonkeyPid db.Table("monkey_pid").Model(models.MonkeyPid{}).Where("task_id = ? and is_del = 0", task_id).Find(&list) for _, v := range list { killPid(v.PId) pids = append(pids, v.PId) } //判断是否停止成功 var pids []models.MonkeyPid db.Table("monkey_pid").Model(models.MonkeyPid{}).Where("task_id = ?", task.Id).Find(&pids) if code := checkTaskPids(pids); code == 1 { task.Status = "CANCEL" db.Model(models.MonkeyTask{}).Where("id = ?", task.Id).Update("status", task.Status) } } rsp.Success() rsp.Data = pids c.JSON(http.StatusOK, rsp) }