goc/pkg/cover/server.go

336 lines
9.2 KiB
Go
Raw Normal View History

/*
2020-05-25 16:19:20 +00:00
Copyright 2020 Qiniu Cloud (qiniu.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cover
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
2020-09-01 13:56:56 +00:00
"regexp"
"github.com/gin-gonic/gin"
2020-06-15 07:29:49 +00:00
log "github.com/sirupsen/logrus"
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
)
// LogFile a file to save log.
const LogFile = "goc.log"
2020-09-25 07:13:36 +00:00
type server struct {
PersistenceFile string
Store Store
}
// NewFileBasedServer new a file based server with persistenceFile
func NewFileBasedServer(persistenceFile string) (*server, error) {
store, err := NewFileStore(persistenceFile)
if err != nil {
return nil, err
}
return &server{
PersistenceFile: persistenceFile,
Store: store,
}, nil
}
// NewMemoryBasedServer new a memory based server without persistenceFile
func NewMemoryBasedServer() *server {
return &server{
Store: NewMemoryStore(),
}
2020-06-12 11:07:21 +00:00
}
2020-06-12 11:07:21 +00:00
// Run starts coverage host center
2020-09-25 07:13:36 +00:00
func (s *server) Run(port string) {
f, err := os.Create(LogFile)
if err != nil {
log.Fatalf("failed to create log file %s, err: %v", LogFile, err)
}
// both log to stdout and file by default
mw := io.MultiWriter(f, os.Stdout)
r := s.Route(mw)
2020-06-12 11:07:21 +00:00
log.Fatal(r.Run(port))
}
// Router init goc server engine
2020-09-25 07:13:36 +00:00
func (s *server) Route(w io.Writer) *gin.Engine {
if w != nil {
gin.DefaultWriter = w
2020-06-12 11:07:21 +00:00
}
r := gin.Default()
2020-06-12 06:08:15 +00:00
// api to show the registered services
2020-09-25 07:13:36 +00:00
r.StaticFile("static", "./"+s.PersistenceFile)
v1 := r.Group("/v1")
{
v1.POST("/cover/register", s.registerService)
v1.GET("/cover/profile", s.profile)
v1.POST("/cover/profile", s.profile)
v1.POST("/cover/clear", s.clear)
v1.POST("/cover/init", s.initSystem)
v1.GET("/cover/list", s.listServices)
}
2020-06-12 11:07:21 +00:00
return r
}
// ServiceUnderTest is a entry under being tested
type ServiceUnderTest struct {
Name string `form:"name" json:"name" binding:"required"`
Address string `form:"address" json:"address" binding:"required"`
}
2020-09-01 13:56:56 +00:00
// ProfileParam is param of profile API
2020-07-09 12:55:07 +00:00
type ProfileParam struct {
2020-09-04 13:16:42 +00:00
Force bool `form:"force" json:"force"`
Service []string `form:"service" json:"service"`
Address []string `form:"address" json:"address"`
CoverFilePatterns []string `form:"coverfile" json:"coverfile"`
2020-07-09 12:55:07 +00:00
}
2020-06-12 06:08:15 +00:00
//listServices list all the registered services
2020-09-25 07:13:36 +00:00
func (s *server) listServices(c *gin.Context) {
services := s.Store.GetAll()
2020-06-12 06:08:15 +00:00
c.JSON(http.StatusOK, services)
}
2020-09-25 07:13:36 +00:00
func (s *server) registerService(c *gin.Context) {
var service ServiceUnderTest
if err := c.ShouldBind(&service); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
u, err := url.Parse(service.Address)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
realIP := c.ClientIP()
if host != realIP {
2020-07-26 09:03:47 +00:00
log.Printf("the registered host %s of service %s is different with the real one %s, here we choose the real one", service.Name, host, realIP)
service.Address = fmt.Sprintf("http://%s:%s", realIP, port)
}
2020-07-16 04:19:26 +00:00
address := s.Store.Get(service.Name)
2020-07-16 04:19:26 +00:00
if !contains(address, service.Address) {
2020-09-25 07:13:36 +00:00
if err := s.Store.Add(service); err != nil && err != ErrServiceAlreadyRegistered {
2020-07-16 04:19:26 +00:00
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
2020-06-12 11:07:21 +00:00
c.JSON(http.StatusOK, gin.H{"result": "success"})
2020-07-16 04:19:26 +00:00
return
}
2020-09-04 13:16:42 +00:00
// profile API examples:
// POST /v1/cover/profile
// { "force": "true", "service":["a","b"], "address":["c","d"],"coverfile":["e","f"] }
2020-09-25 07:13:36 +00:00
func (s *server) profile(c *gin.Context) {
2020-09-04 13:16:42 +00:00
var body ProfileParam
if err := c.ShouldBind(&body); err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"error": err.Error()})
2020-07-09 12:55:07 +00:00
return
}
2020-09-01 13:56:56 +00:00
allInfos := s.Store.GetAll()
2020-09-04 13:16:42 +00:00
filterAddrList, err := filterAddrs(body.Service, body.Address, body.Force, allInfos)
2020-07-16 04:19:26 +00:00
if err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"error": err.Error()})
2020-07-17 06:50:03 +00:00
return
2020-07-09 12:55:07 +00:00
}
2020-07-16 04:19:26 +00:00
var mergedProfiles = make([][]*cover.Profile, 0)
for _, addr := range filterAddrList {
pp, err := NewWorker(addr).Profile(ProfileParam{})
if err != nil {
2020-09-04 13:16:42 +00:00
if body.Force {
2020-07-17 06:50:03 +00:00
log.Warnf("get profile from [%s] failed, error: %s", addr, err.Error())
continue
}
2020-09-04 13:16:42 +00:00
c.JSON(http.StatusExpectationFailed, gin.H{"error": fmt.Sprintf("failed to get profile from %s, error %s", addr, err.Error())})
return
}
2020-09-04 13:16:42 +00:00
profile, err := convertProfile(pp)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
mergedProfiles = append(mergedProfiles, profile)
}
if len(mergedProfiles) == 0 {
2020-09-09 11:38:16 +00:00
c.JSON(http.StatusExpectationFailed, gin.H{"error": "no profiles"})
return
}
merged, err := cov.MergeMultipleProfiles(mergedProfiles)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
2020-09-04 13:16:42 +00:00
if len(body.CoverFilePatterns) > 0 {
merged, err = filterProfile(body.CoverFilePatterns, merged)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to filter profile based on the patterns: %v, error: %v", body.CoverFilePatterns, err)})
return
}
}
if err := cov.DumpProfile(merged, c.Writer); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
}
2020-09-04 13:16:42 +00:00
// filterProfile filters profiles of the packages matching the coverFile pattern
func filterProfile(coverFile []string, profiles []*cover.Profile) ([]*cover.Profile, error) {
2020-09-01 13:56:56 +00:00
var out = make([]*cover.Profile, 0)
for _, profile := range profiles {
2020-09-04 13:16:42 +00:00
for _, pattern := range coverFile {
2020-09-01 13:56:56 +00:00
matched, err := regexp.MatchString(pattern, profile.FileName)
if err != nil {
return nil, fmt.Errorf("filterProfile failed with pattern %s for profile %s, err: %v", pattern, profile.FileName, err)
2020-09-01 13:56:56 +00:00
}
if matched {
out = append(out, profile)
2020-09-04 13:16:42 +00:00
break // no need to check again for the file
2020-09-01 13:56:56 +00:00
}
}
}
return out, nil
}
2020-09-25 07:13:36 +00:00
func (s *server) clear(c *gin.Context) {
2020-09-11 07:45:45 +00:00
var body ProfileParam
if err := c.ShouldBind(&body); err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"error": err.Error()})
return
}
svrsUnderTest := s.Store.GetAll()
2020-09-11 07:45:45 +00:00
filterAddrList, err := filterAddrs(body.Service, body.Address, true, svrsUnderTest)
if err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"error": err.Error()})
return
}
for _, addr := range filterAddrList {
pp, err := NewWorker(addr).Clear(ProfileParam{})
if err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"error": err.Error()})
return
}
2020-09-11 07:45:45 +00:00
fmt.Fprintf(c.Writer, "Register service %s coverage counter %s", addr, string(pp))
}
2020-09-11 07:45:45 +00:00
}
2020-09-25 07:13:36 +00:00
func (s *server) initSystem(c *gin.Context) {
if err := s.Store.Init(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, "")
}
func convertProfile(p []byte) ([]*cover.Profile, error) {
// Annoyingly, ParseProfiles only accepts a filename, so we have to write the bytes to disk
// so it can read them back.
// We could probably also just give it /dev/stdin, but that'll break on Windows.
tf, err := ioutil.TempFile("", "")
if err != nil {
return nil, fmt.Errorf("failed to create temp file, err: %v", err)
}
defer tf.Close()
defer os.Remove(tf.Name())
if _, err := io.Copy(tf, bytes.NewReader(p)); err != nil {
return nil, fmt.Errorf("failed to copy data to temp file, err: %v", err)
}
return cover.ParseProfiles(tf.Name())
}
func contains(arr []string, str string) bool {
for _, element := range arr {
if str == element {
return true
}
}
return false
}
2020-07-16 04:19:26 +00:00
// filterAddrs filter address list by given service and address list
2020-07-17 06:50:03 +00:00
func filterAddrs(serviceList, addressList []string, force bool, allInfos map[string][]string) (filterAddrList []string, err error) {
addressAll := []string{}
for _, addr := range allInfos {
addressAll = append(addressAll, addr...)
}
2020-09-04 13:16:42 +00:00
2020-07-17 06:50:03 +00:00
if len(serviceList) != 0 && len(addressList) != 0 {
return nil, fmt.Errorf("use 'service' flag and 'address' flag at the same time may cause ambiguity, please use them separately")
2020-07-16 04:19:26 +00:00
}
2020-09-04 13:16:42 +00:00
// Add matched services to map
2020-07-17 06:50:03 +00:00
for _, name := range serviceList {
if addr, ok := allInfos[name]; ok {
filterAddrList = append(filterAddrList, addr...)
continue // jump to match the next service
2020-07-16 04:19:26 +00:00
}
if !force {
return nil, fmt.Errorf("service [%s] not found", name)
}
2020-07-17 06:50:03 +00:00
log.Warnf("service [%s] not found", name)
}
2020-09-04 13:16:42 +00:00
// Add matched addresses to map
2020-07-17 06:50:03 +00:00
for _, addr := range addressList {
if contains(addressAll, addr) {
filterAddrList = append(filterAddrList, addr)
continue
}
if !force {
return nil, fmt.Errorf("address [%s] not found", addr)
}
2020-07-17 06:50:03 +00:00
log.Warnf("address [%s] not found", addr)
}
2020-09-04 13:16:42 +00:00
2020-07-17 06:50:03 +00:00
if len(addressList) == 0 && len(serviceList) == 0 {
filterAddrList = addressAll
}
2020-09-04 13:16:42 +00:00
2020-09-25 07:13:36 +00:00
// Return all services when all param is nil
2020-07-17 06:50:03 +00:00
return filterAddrList, nil
}