2020-05-13 08:27:19 +00:00
/ *
2020-05-25 16:19:20 +00:00
Copyright 2020 Qiniu Cloud ( qiniu . com )
2020-05-13 08:27:19 +00:00
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"
2020-05-13 08:27:19 +00:00
"github.com/gin-gonic/gin"
2020-06-15 07:29:49 +00:00
log "github.com/sirupsen/logrus"
2020-05-13 08:27:19 +00:00
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
)
2020-06-15 09:16:50 +00:00
// DefaultStore implements the IPersistence interface
var DefaultStore Store
2020-05-13 08:27:19 +00:00
// LogFile a file to save log.
const LogFile = "goc.log"
2020-06-12 11:07:21 +00:00
func init ( ) {
2020-06-15 09:16:50 +00:00
DefaultStore = NewFileStore ( )
2020-06-12 11:07:21 +00:00
}
2020-05-13 08:27:19 +00:00
2020-06-12 11:07:21 +00:00
// Run starts coverage host center
func Run ( port string ) {
2020-05-13 08:27:19 +00:00
f , err := os . Create ( LogFile )
if err != nil {
log . Fatalf ( "failed to create log file %s, err: %v" , LogFile , err )
}
2020-06-15 09:34:54 +00:00
// both log to stdout and file by default
mw := io . MultiWriter ( f , os . Stdout )
r := GocServer ( mw )
2020-06-12 11:07:21 +00:00
log . Fatal ( r . Run ( port ) )
}
// GocServer init goc server engine
func GocServer ( w io . Writer ) * gin . Engine {
2020-06-15 09:34:54 +00:00
if w != nil {
gin . DefaultWriter = w
2020-06-12 11:07:21 +00:00
}
2020-05-13 08:27:19 +00:00
r := gin . Default ( )
2020-06-12 06:08:15 +00:00
// api to show the registered services
2020-05-13 08:27:19 +00:00
r . StaticFile ( PersistenceFile , "./" + PersistenceFile )
v1 := r . Group ( "/v1" )
{
v1 . POST ( "/cover/register" , registerService )
2020-07-10 07:17:14 +00:00
v1 . GET ( "/cover/profile" , profile )
2020-09-04 13:16:42 +00:00
v1 . POST ( "/cover/profile" , profile )
2020-05-13 08:27:19 +00:00
v1 . POST ( "/cover/clear" , clear )
v1 . POST ( "/cover/init" , initSystem )
2020-06-12 06:08:15 +00:00
v1 . GET ( "/cover/list" , listServices )
2020-05-13 08:27:19 +00:00
}
2020-06-12 11:07:21 +00:00
return r
2020-05-13 08:27:19 +00:00
}
// Service is a entry under being tested
type Service 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
func listServices ( c * gin . Context ) {
2020-06-15 09:16:50 +00:00
services := DefaultStore . GetAll ( )
2020-06-12 06:08:15 +00:00
c . JSON ( http . StatusOK , services )
}
2020-05-13 08:27:19 +00:00
func registerService ( c * gin . Context ) {
var service Service
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 )
2020-05-13 08:27:19 +00:00
service . Address = fmt . Sprintf ( "http://%s:%s" , realIP , port )
}
2020-07-16 04:19:26 +00:00
address := DefaultStore . Get ( service . Name )
if ! contains ( address , service . Address ) {
if err := DefaultStore . Add ( service ) ; err != nil {
c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ( ) } )
return
}
2020-05-13 08:27:19 +00:00
}
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-05-13 08:27:19 +00:00
}
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-05-13 08:27:19 +00:00
func 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
2020-07-17 06:50:03 +00:00
allInfos := DefaultStore . 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
2020-07-10 07:17:14 +00:00
var mergedProfiles = make ( [ ] [ ] * cover . Profile , 0 )
2020-07-16 12:58:58 +00:00
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 ( ) )
2020-07-16 12:58:58 +00:00
continue
2020-05-13 08:27:19 +00:00
}
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 ( ) ) } )
2020-07-16 12:58:58 +00:00
return
}
2020-09-04 13:16:42 +00:00
2020-07-16 12:58:58 +00:00
profile , err := convertProfile ( pp )
if err != nil {
c . JSON ( http . StatusInternalServerError , gin . H { "error" : err . Error ( ) } )
return
2020-05-13 08:27:19 +00:00
}
2020-07-16 12:58:58 +00:00
mergedProfiles = append ( mergedProfiles , profile )
2020-05-13 08:27:19 +00:00
}
if len ( mergedProfiles ) == 0 {
2020-09-05 10:32:55 +00:00
c . JSON ( http . StatusOK , gin . H { "message" : "no profiles" } )
2020-05-13 08:27:19 +00:00
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
}
}
2020-05-13 08:27:19 +00:00
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 {
2020-09-05 08:27:23 +00:00
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-05-13 08:27:19 +00:00
func clear ( c * gin . Context ) {
2020-06-15 09:16:50 +00:00
svrsUnderTest := DefaultStore . GetAll ( )
2020-05-25 13:04:46 +00:00
for svc , addrs := range svrsUnderTest {
for _ , addr := range addrs {
2020-06-12 11:07:21 +00:00
pp , err := NewWorker ( addr ) . Clear ( )
2020-05-25 13:04:46 +00:00
if err != nil {
c . JSON ( http . StatusExpectationFailed , gin . H { "error" : err . Error ( ) } )
return
}
fmt . Fprintf ( c . Writer , "Register service %s: %s coverage counter %s" , svc , addr , string ( pp ) )
}
2020-05-13 08:27:19 +00:00
}
}
func initSystem ( c * gin . Context ) {
2020-06-15 09:16:50 +00:00
if err := DefaultStore . Init ( ) ; err != nil {
2020-05-13 08:27:19 +00:00
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 ( ) )
}
2020-07-10 07:17:14 +00:00
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
2020-07-16 12:58:58 +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-07-16 12:58:58 +00:00
}
2020-09-04 13:16:42 +00:00
2020-07-17 06:50:03 +00:00
if len ( serviceList ) != 0 && len ( addressList ) != 0 {
2020-07-16 12:58:58 +00:00
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
2020-07-16 12:58:58 +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 ... )
2020-07-16 12:58:58 +00:00
continue // jump to match the next service
2020-07-16 04:19:26 +00:00
}
2020-07-16 12:58:58 +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-07-16 12:58:58 +00:00
}
2020-09-04 13:16:42 +00:00
2020-07-16 12:58:58 +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 )
2020-07-16 12:58:58 +00:00
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-07-16 12:58:58 +00:00
}
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-07-16 12:58:58 +00:00
}
2020-09-04 13:16:42 +00:00
2020-07-16 12:58:58 +00:00
// Return all servers when all param is nil
2020-07-17 06:50:03 +00:00
return filterAddrList , nil
2020-07-16 12:58:58 +00:00
}