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"
)
// 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-05-13 08:27:19 +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 ) {
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 )
2020-09-23 06:14:01 +00:00
r := s . Route ( mw )
2020-06-12 11:07:21 +00:00
log . Fatal ( r . Run ( port ) )
}
2020-09-23 06:14:01 +00:00
// Router init goc server engine
2020-09-25 07:13:36 +00:00
func ( s * server ) Route ( 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-09-25 07:13:36 +00:00
r . StaticFile ( "static" , "./" + s . PersistenceFile )
2020-05-13 08:27:19 +00:00
v1 := r . Group ( "/v1" )
{
2020-09-23 06:14:01 +00:00
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-09-20 12:29:17 +00:00
v1 . POST ( "/cover/remove" , s . removeServices )
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
}
2020-09-23 06:14:01 +00:00
// ServiceUnderTest is a entry under being tested
type ServiceUnderTest struct {
2020-05-13 08:27:19 +00:00
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-12-18 02:43:48 +00:00
SkipFilePatterns [ ] string ` form:"skipfile" json:"skipfile" `
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 ) {
2020-09-23 06:14:01 +00:00
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 ) {
2020-09-23 06:14:01 +00:00
var service ServiceUnderTest
2020-05-13 08:27:19 +00:00
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 ( )
2021-03-22 12:38:33 +00:00
// only for IPV4
// refer: https://github.com/qiniu/goc/issues/177
if net . ParseIP ( realIP ) . To4 ( ) != nil && 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
2020-09-23 06:14:01 +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-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-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
2020-09-23 06:14:01 +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
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-09 11:38:16 +00:00
c . JSON ( http . StatusExpectationFailed , gin . H { "error" : "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-12-18 02:43:48 +00:00
if len ( body . SkipFilePatterns ) > 0 {
merged , err = skipProfile ( body . SkipFilePatterns , merged )
if err != nil {
c . JSON ( http . StatusInternalServerError , gin . H { "error" : fmt . Sprintf ( "failed to skip profile based on the patterns: %v, error: %v" , body . SkipFilePatterns , 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-12-18 03:07:04 +00:00
// skipProfile skips profiles of the packages matching the skipFile pattern
2020-12-18 02:43:48 +00:00
func skipProfile ( skipFile [ ] string , profiles [ ] * cover . Profile ) ( [ ] * cover . Profile , error ) {
var out = make ( [ ] * cover . Profile , 0 )
for _ , profile := range profiles {
var shouldSkip bool
for _ , pattern := range skipFile {
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 )
}
if matched {
shouldSkip = true
break // no need to check again for the file
}
}
if ! shouldSkip {
out = append ( out , profile )
}
}
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
}
2020-09-23 06:14:01 +00:00
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-05-25 13:04:46 +00:00
}
2020-09-11 07:45:45 +00:00
fmt . Fprintf ( c . Writer , "Register service %s coverage counter %s" , addr , string ( pp ) )
2020-05-13 08:27:19 +00:00
}
2020-09-11 07:45:45 +00:00
2020-05-13 08:27:19 +00:00
}
2020-09-25 07:13:36 +00:00
func ( s * server ) initSystem ( c * gin . Context ) {
2020-09-23 06:14:01 +00:00
if err := s . Store . 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 , "" )
}
2020-09-20 12:29:17 +00:00
func ( s * server ) removeServices ( c * gin . Context ) {
var body ProfileParam
if err := c . ShouldBind ( & body ) ; err != nil {
c . JSON ( http . StatusExpectationFailed , gin . H { "error" : err . Error ( ) } )
return
}
svrsUnderTest := s . Store . GetAll ( )
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 {
err := s . Store . Remove ( addr )
if err != nil {
c . JSON ( http . StatusExpectationFailed , gin . H { "error" : err . Error ( ) } )
return
}
fmt . Fprintf ( c . Writer , "Register service %s removed from the center." , addr )
}
}
2020-05-13 08:27:19 +00:00
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-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
2020-07-16 12:58:58 +00:00
}