Merge pull request #37 from CarlJi/0610

Feature: add goc run command
This commit is contained in:
qiniu-bot 2020-06-16 20:29:13 +08:00 committed by GitHub
commit 103dafe41d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 505 additions and 262 deletions

View File

@ -18,6 +18,7 @@ package cmd
import (
"github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra"
)
@ -45,14 +46,10 @@ goc build -- -ldflags "-extldflags -static" -tags="embed kodo"
Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewBuild(buildFlags, packages, buildOutput)
// remove temporary directory if needed
defer func() {
if !debugGoc {
gocBuild.Clean()
}
}()
defer gocBuild.Clean()
// doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory
doCover(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir)
cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory
gocBuild.Build()
return

View File

@ -13,6 +13,9 @@ var (
buildFlags string
packages string
appArgs string
goRunExecFlag string
goRunArguments string
)
// addBasicFlags adds a
@ -41,6 +44,8 @@ func addRunFlags(cmdset *pflag.FlagSet) {
addBuildFlags(cmdset)
cmdset.Lookup("packages").Usage = "specify the package name, only ., ./... and *.go are supported"
cmdset.StringVar(&appArgs, "appargs", "", "specify the application's arguments")
cmdset.StringVar(&goRunExecFlag, "exec", "", "same as -exec flag in 'go run' command")
cmdset.StringVar(&goRunArguments, "arguments", "", "same as 'arguments' in 'go run' command")
// bind to viper
viper.BindPFlags(cmdset)
}

View File

@ -17,14 +17,10 @@
package cmd
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"os"
"strings"
"github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var coverCmd = &cobra.Command{
@ -52,7 +48,7 @@ goc cover --center=http://127.0.0.1:7777 --target=/path/to/target --mode=atomic
log.Fatalf("unknown -mode %v", mode)
}
doCover(buildFlags, "", target)
cover.Execute(buildFlags, "", target, mode, center)
},
}
@ -61,166 +57,3 @@ func init() {
addCommonFlags(coverCmd.Flags())
rootCmd.AddCommand(coverCmd)
}
func doCover(args string, newgopath string, target string) {
if !isDirExist(target) {
log.Fatalf("target directory %s not exist", target)
}
listArgs := []string{"-json"}
if len(args) != 0 {
listArgs = append(listArgs, args)
}
listArgs = append(listArgs, "./...")
pkgs := cover.ListPackages(target, strings.Join(listArgs, " "), newgopath)
var seen = make(map[string]*cover.PackageCover)
var seenCache = make(map[string]*cover.PackageCover)
for _, pkg := range pkgs {
if pkg.Name == "main" {
log.Printf("handle package: %v", pkg.ImportPath)
// inject the main package
mainCover, err := cover.AddCounters(pkg, mode, newgopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", pkg.ImportPath, err)
}
// new a testcover for this service
tc := cover.TestCover{
Mode: mode,
Center: center,
MainPkgCover: mainCover,
}
// handle its dependency
var internalPkgCache = make(map[string][]*cover.PackageCover)
tc.CacheCover = make(map[string]*cover.PackageCover)
for _, dep := range pkg.Deps {
if packageCover, ok := seen[dep]; ok {
tc.DepsCover = append(tc.DepsCover, packageCover)
continue
}
//only focus package neither standard Go library nor dependency library
if depPkg, ok := pkgs[dep]; ok {
if findInternal(dep) {
//scan exist cache cover to tc.CacheCover
if cache, ok := seenCache[dep]; ok {
log.Printf("cache cover exist: %s", cache.Package.ImportPath)
tc.CacheCover[cache.Package.Dir] = cache
continue
}
// add counter for internal package
inPkgCover, err := cover.AddCounters(depPkg, mode, newgopath)
if err != nil {
log.Fatalf("failed to add counters for internal pkg %s, err: %v", depPkg.ImportPath, err)
}
parentDir := getInternalParent(depPkg.Dir)
parentImportPath := getInternalParent(depPkg.ImportPath)
//if internal parent dir or import is root path, ignore the dep. the dep is Go library nor dependency library
if parentDir == "" {
continue
}
if parentImportPath == "" {
continue
}
pkg := &cover.Package{
ImportPath: parentImportPath,
Dir: parentDir,
}
// Some internal package have same parent dir or import path
// Cache all vars by internal parent dir for all child internal counter vars
cacheCover := cover.AddCacheCover(pkg, inPkgCover)
if v, ok := tc.CacheCover[cacheCover.Package.Dir]; ok {
for cVar, val := range v.Vars {
cacheCover.Vars[cVar] = val
}
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
} else {
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
}
// Cache all internal vars to internal parent package
inCover := cover.CacheInternalCover(inPkgCover)
if v, ok := internalPkgCache[cacheCover.Package.Dir]; ok {
v = append(v, inCover)
internalPkgCache[cacheCover.Package.Dir] = v
} else {
var covers []*cover.PackageCover
covers = append(covers, inCover)
internalPkgCache[cacheCover.Package.Dir] = covers
}
seenCache[dep] = cacheCover
continue
}
packageCover, err := cover.AddCounters(depPkg, mode, newgopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
}
tc.DepsCover = append(tc.DepsCover, packageCover)
seen[dep] = packageCover
}
}
if errs := cover.InjectCacheCounters(internalPkgCache, tc.CacheCover); len(errs) > 0 {
log.Fatalf("failed to inject cache counters for package: %s, err: %v", pkg.ImportPath, errs)
}
// inject Http Cover APIs
var httpCoverApis = fmt.Sprintf("%s/http_cover_apis_auto_generated.go", pkg.Dir)
if err := cover.InjectCountersHandlers(tc, httpCoverApis); err != nil {
log.Fatalf("failed to inject counters for package: %s, err: %v", pkg.ImportPath, err)
}
}
}
}
func isDirExist(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
// Refer: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L1334:6
// findInternal looks for the final "internal" path element in the given import path.
// If there isn't one, findInternal returns ok=false.
// Otherwise, findInternal returns ok=true and the index of the "internal".
func findInternal(path string) bool {
// Three cases, depending on internal at start/end of string or not.
// The order matters: we must return the index of the final element,
// because the final one produces the most restrictive requirement
// on the importer.
switch {
case strings.HasSuffix(path, "/internal"):
return true
case strings.Contains(path, "/internal/"):
return true
case path == "internal", strings.HasPrefix(path, "internal/"):
return true
}
return false
}
func getInternalParent(path string) string {
switch {
case strings.HasSuffix(path, "/internal"):
return strings.Split(path, "/internal")[0]
case strings.Contains(path, "/internal/"):
return strings.Split(path, "/internal/")[0]
case path == "internal":
return ""
case strings.HasPrefix(path, "internal/"):
return strings.Split(path, "internal/")[0]
}
return ""
}

View File

@ -18,6 +18,7 @@ package cmd
import (
"github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
"github.com/spf13/cobra"
)
@ -42,14 +43,10 @@ goc build --buildflags="-ldflags '-extldflags -static' -tags='embed kodo'"
Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewInstall(buildFlags, packages)
// remove temporary directory if needed
defer func() {
if !debugGoc {
gocBuild.Clean()
}
}()
defer gocBuild.Clean()
// doCover with original buildFlags, with new GOPATH( tmp:original )
// in the tmp directory
doCover(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir)
cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, center)
// do install in the temporary directory
gocBuild.Install()
},

73
cmd/run.go Normal file
View File

@ -0,0 +1,73 @@
/*
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 cmd
import (
"fmt"
"io/ioutil"
"net"
"github.com/qiniu/goc/pkg/build"
"github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var runCmd = &cobra.Command{
Use: "run",
Short: "Run covers and runs the named main Go package",
Long: `Run covers and runs the named main Go package,
It is exactly behave as 'go run .' in addition of some internal goc features.`,
Example: `
goc run .
`,
Run: func(cmd *cobra.Command, args []string) {
gocBuild := build.NewBuild(buildFlags, packages, buildOutput)
gocBuild.GoRunExecFlag = goRunExecFlag
gocBuild.GoRunArguments = goRunArguments
defer gocBuild.Clean()
// only save services in memory
cover.DefaultStore = cover.NewMemoryStore()
// start goc server
var l = newLocalListener()
go cover.GocServer(ioutil.Discard).RunListener(l)
gocServer := fmt.Sprintf("http://%s", l.Addr().String())
fmt.Printf("[goc] goc server started: %s \n", gocServer)
// execute covers for the target source with original buildFlags and new GOPATH( tmp:original )
cover.Execute(buildFlags, gocBuild.NewGOPATH, gocBuild.TmpDir, mode, gocServer)
gocBuild.Run()
},
}
func init() {
addRunFlags(runCmd.Flags())
rootCmd.AddCommand(runCmd)
}
func newLocalListener() net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
log.Fatalf("failed to listen on a port: %v", err)
}
}
return l
}

View File

@ -35,10 +35,17 @@ type Build struct {
TmpDir string // the temporary directory to build the project
TmpWorkingDir string // the working directory in the temporary directory, which is corresponding to the current directory in the project directory
IsMod bool // determine whether it is a Mod project
BuildFlags string // Build flags
Packages string // Packages that needs to build
Root string // Project Root
Target string // the binary name that go build generate
// keep compatible with go commands:
// go run [build flags] [-exec xprog] package [arguments...]
// go build [-o output] [-i] [build flags] [packages]
// go install [-i] [build flags] [packages]
BuildFlags string // Build flags
Packages string // Packages that needs to build
GoRunExecFlag string // for the -exec flags in go run command
GoRunArguments string // for the '[arguments]' parameters in go run command
}
// NewBuild creates a Build struct which can build from goc temporary directory,
@ -108,7 +115,30 @@ func (b *Build) determineOutputDir(outputDir string) string {
func (b *Build) validatePackageForBuild() bool {
if b.Packages == "." {
return true
} else {
return false
}
return false
}
// Run excutes the main package in addition with the internal goc features
func (b *Build) Run() {
cmd := exec.Command("/bin/bash", "-c", "go run "+b.BuildFlags+" "+b.GoRunExecFlag+" "+b.Packages+" "+b.GoRunArguments)
cmd.Dir = b.TmpWorkingDir
if b.NewGOPATH != "" {
// Change to temp GOPATH for go install command
cmd.Env = append(os.Environ(), fmt.Sprintf("GOPATH=%v", b.NewGOPATH))
}
log.Printf("go build cmd is: %v", cmd.Args)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
log.Fatalf("Fail to start command: %v. The error is: %v", cmd.Args, err)
}
if err = cmd.Wait(); err != nil {
log.Fatalf("Fail to execute command: %v. The error is: %v", cmd.Args, err)
}
}

View File

@ -17,9 +17,8 @@
package build
import (
log "github.com/sirupsen/logrus"
"github.com/otiai10/copy"
log "github.com/sirupsen/logrus"
)
func (b *Build) cpGoModulesProject() {

View File

@ -18,9 +18,10 @@ package build
import (
"fmt"
log "github.com/sirupsen/logrus"
"os"
"os/exec"
log "github.com/sirupsen/logrus"
)
// NewInstall creates a Build struct which can install from goc temporary directory
@ -59,7 +60,6 @@ func (b *Build) Install() {
func (b *Build) validatePackageForInstall() bool {
if b.Packages == "." || b.Packages == "./..." {
return true
} else {
return false
}
return false
}

View File

@ -17,12 +17,12 @@
package build
import (
log "github.com/sirupsen/logrus"
"os"
"path/filepath"
"github.com/otiai10/copy"
"github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
)
func (b *Build) cpLegacyProject() {

View File

@ -23,9 +23,9 @@ import (
"path/filepath"
"strings"
log "github.com/sirupsen/logrus"
"github.com/qiniu/goc/pkg/cover"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
func (b *Build) MvProjectsToTmp() {
@ -156,5 +156,8 @@ func (b *Build) findWhereToInstall() string {
// Clean clears up the temporary workspace
func (b *Build) Clean() error {
if !viper.GetBool("debug") {
return os.RemoveAll(b.TmpDir)
}
return nil
}

View File

@ -22,7 +22,6 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
@ -33,6 +32,8 @@ import (
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus"
)
@ -112,6 +113,125 @@ type PackageError struct {
Err string // the error itself
}
//Execute execute go tool cover for all the .go files in the target folder
func Execute(args, newGopath, target, mode, center string) {
if !isDirExist(target) {
log.Fatalf("target directory %s not exist", target)
}
listArgs := []string{"-json"}
if len(args) != 0 {
listArgs = append(listArgs, args)
}
listArgs = append(listArgs, "./...")
pkgs := ListPackages(target, strings.Join(listArgs, " "), newGopath)
var seen = make(map[string]*PackageCover)
var seenCache = make(map[string]*PackageCover)
for _, pkg := range pkgs {
if pkg.Name == "main" {
log.Printf("handle package: %v", pkg.ImportPath)
// inject the main package
mainCover, err := AddCounters(pkg, mode, newGopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", pkg.ImportPath, err)
}
// new a testcover for this service
tc := TestCover{
Mode: mode,
Center: center,
MainPkgCover: mainCover,
}
// handle its dependency
var internalPkgCache = make(map[string][]*PackageCover)
tc.CacheCover = make(map[string]*PackageCover)
for _, dep := range pkg.Deps {
if packageCover, ok := seen[dep]; ok {
tc.DepsCover = append(tc.DepsCover, packageCover)
continue
}
//only focus package neither standard Go library nor dependency library
if depPkg, ok := pkgs[dep]; ok {
if hasInternalPath(dep) {
//scan exist cache cover to tc.CacheCover
if cache, ok := seenCache[dep]; ok {
log.Printf("cache cover exist: %s", cache.Package.ImportPath)
tc.CacheCover[cache.Package.Dir] = cache
continue
}
// add counter for internal package
inPkgCover, err := AddCounters(depPkg, mode, newGopath)
if err != nil {
log.Fatalf("failed to add counters for internal pkg %s, err: %v", depPkg.ImportPath, err)
}
parentDir := getInternalParent(depPkg.Dir)
parentImportPath := getInternalParent(depPkg.ImportPath)
//if internal parent dir or import is root path, ignore the dep. the dep is Go library nor dependency library
if parentDir == "" {
continue
}
if parentImportPath == "" {
continue
}
pkg := &Package{
ImportPath: parentImportPath,
Dir: parentDir,
}
// Some internal package have same parent dir or import path
// Cache all vars by internal parent dir for all child internal counter vars
cacheCover := AddCacheCover(pkg, inPkgCover)
if v, ok := tc.CacheCover[cacheCover.Package.Dir]; ok {
for cVar, val := range v.Vars {
cacheCover.Vars[cVar] = val
}
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
} else {
tc.CacheCover[cacheCover.Package.Dir] = cacheCover
}
// Cache all internal vars to internal parent package
inCover := CacheInternalCover(inPkgCover)
if v, ok := internalPkgCache[cacheCover.Package.Dir]; ok {
v = append(v, inCover)
internalPkgCache[cacheCover.Package.Dir] = v
} else {
var covers []*PackageCover
covers = append(covers, inCover)
internalPkgCache[cacheCover.Package.Dir] = covers
}
seenCache[dep] = cacheCover
continue
}
packageCover, err := AddCounters(depPkg, mode, newGopath)
if err != nil {
log.Fatalf("failed to add counters for pkg %s, err: %v", depPkg.ImportPath, err)
}
tc.DepsCover = append(tc.DepsCover, packageCover)
seen[dep] = packageCover
}
}
if errs := InjectCacheCounters(internalPkgCache, tc.CacheCover); len(errs) > 0 {
log.Fatalf("failed to inject cache counters for package: %s, err: %v", pkg.ImportPath, errs)
}
// inject Http Cover APIs
var httpCoverApis = fmt.Sprintf("%s/http_cover_apis_auto_generated.go", pkg.Dir)
if err := InjectCountersHandlers(tc, httpCoverApis); err != nil {
log.Fatalf("failed to inject counters for package: %s, err: %v", pkg.ImportPath, err)
}
}
}
}
// ListPackages list all packages under specific via go list command
// The argument newgopath is if you need to go list in a different GOPATH
func ListPackages(dir string, args string, newgopath string) map[string]*Package {
@ -167,6 +287,48 @@ func AddCounters(pkg *Package, mode, newgopath string) (*PackageCover, error) {
}, nil
}
func isDirExist(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
// Refer: https://github.com/golang/go/blob/master/src/cmd/go/internal/load/pkg.go#L1334:6
// hasInternalPath looks for the final "internal" path element in the given import path.
// If there isn't one, hasInternalPath returns ok=false.
// Otherwise, hasInternalPath returns ok=true and the index of the "internal".
func hasInternalPath(path string) bool {
// Three cases, depending on internal at start/end of string or not.
// The order matters: we must return the index of the final element,
// because the final one produces the most restrictive requirement
// on the importer.
switch {
case strings.HasSuffix(path, "/internal"):
return true
case strings.Contains(path, "/internal/"):
return true
case path == "internal", strings.HasPrefix(path, "internal/"):
return true
}
return false
}
func getInternalParent(path string) string {
switch {
case strings.HasSuffix(path, "/internal"):
return strings.Split(path, "/internal")[0]
case strings.Contains(path, "/internal/"):
return strings.Split(path, "/internal/")[0]
case path == "internal":
return ""
case strings.HasPrefix(path, "internal/"):
return strings.Split(path, "internal/")[0]
}
return ""
}
func buildCoverCmd(file string, coverVar *FileVar, pkg *Package, mode, newgopath string) *exec.Cmd {
// to construct: go tool cover -mode=atomic -o dest src (note: dest==src)
var newArgs = []string{"tool", "cover"}

View File

@ -18,7 +18,6 @@ package cover
import (
"fmt"
log "github.com/sirupsen/logrus"
"os"
"os/exec"
"path/filepath"
@ -26,6 +25,8 @@ import (
"strings"
"testing"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
@ -225,3 +226,81 @@ func TestDeclareCoverVars(t *testing.T) {
}
}
func TestGetInternalParent(t *testing.T) {
var tcs = []struct {
ImportPath string
expectedParent string
}{
{
ImportPath: "a/internal/b",
expectedParent: "a",
},
{
ImportPath: "internal/b",
expectedParent: "",
},
{
ImportPath: "a/b/internal/b",
expectedParent: "a/b",
},
{
ImportPath: "a/b/internal",
expectedParent: "a/b",
},
{
ImportPath: "a/b/internal/c",
expectedParent: "a/b",
},
{
ImportPath: "a/b/c",
expectedParent: "",
},
{
ImportPath: "",
expectedParent: "",
},
}
for _, tc := range tcs {
actual := getInternalParent(tc.ImportPath)
if actual != tc.expectedParent {
t.Errorf("getInternalParent failed for importPath %s, expected %s, got %s", tc.ImportPath, tc.expectedParent, actual)
}
}
}
func TestFindInternal(t *testing.T) {
var tcs = []struct {
ImportPath string
expectedParent bool
}{
{
ImportPath: "a/internal/b",
expectedParent: true,
},
{
ImportPath: "internal/b",
expectedParent: true,
},
{
ImportPath: "a/b/internal",
expectedParent: true,
},
{
ImportPath: "a/b/c",
expectedParent: false,
},
{
ImportPath: "internal",
expectedParent: true,
},
}
for _, tc := range tcs {
actual := hasInternalPath(tc.ImportPath)
if actual != tc.expectedParent {
t.Errorf("hasInternalPath check failed for importPath %s", tc.ImportPath)
}
}
}

View File

@ -145,9 +145,10 @@ func clearFileCover(counter []uint32) {
func registerHandlers() {
ln, host, err := listen()
if err != nil {
log.Fatalf("profile listen failed, err:%v", err)
log.Fatalf("listen failed, err:%v", err)
}
log.Println("profile listen on", host)
fmt.Printf("[goc] goc agent listened on: %s \n", host)
profileAddr := "http://" + host
if resp, err := registerSelf(profileAddr); err != nil {
log.Fatalf("register address %v failed, err: %v, response: %v", profileAddr, err, string(resp))
@ -220,7 +221,7 @@ func registerSelf(address string) ([]byte, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil && isNetworkError(err) {
log.Printf("[WARN]error occured:%v, try again", err)
log.Printf("[goc][WARN]error occured:%v, try again", err)
resp, err = http.DefaultClient.Do(req)
}
defer resp.Body.Close()

View File

@ -19,7 +19,6 @@ package cover
import (
"bytes"
"fmt"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net"
@ -28,18 +27,19 @@ import (
"os"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
"golang.org/x/tools/cover"
"k8s.io/test-infra/gopherage/pkg/cov"
)
// LocalStore implements the IPersistence interface
var LocalStore Store
// DefaultStore implements the IPersistence interface
var DefaultStore Store
// LogFile a file to save log.
const LogFile = "goc.log"
func init() {
LocalStore = NewStore()
DefaultStore = NewFileStore()
}
// Run starts coverage host center
@ -48,16 +48,18 @@ func Run(port string) {
if err != nil {
log.Fatalf("failed to create log file %s, err: %v", LogFile, err)
}
r := GocServer(f)
// both log to stdout and file by default
mw := io.MultiWriter(f, os.Stdout)
r := GocServer(mw)
log.Fatal(r.Run(port))
}
// GocServer init goc server engine
func GocServer(w io.Writer) *gin.Engine {
if w != nil && w != os.Stdout {
gin.DefaultWriter = io.MultiWriter(w, os.Stdout)
if w != nil {
gin.DefaultWriter = w
}
r := gin.Default()
// api to show the registered services
r.StaticFile(PersistenceFile, "./"+PersistenceFile)
@ -82,7 +84,7 @@ type Service struct {
//listServices list all the registered services
func listServices(c *gin.Context) {
services := LocalStore.GetAll()
services := DefaultStore.GetAll()
c.JSON(http.StatusOK, services)
}
@ -109,7 +111,7 @@ func registerService(c *gin.Context) {
log.Printf("the registed 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)
}
if err := LocalStore.Add(service); err != nil {
if err := DefaultStore.Add(service); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@ -118,7 +120,7 @@ func registerService(c *gin.Context) {
}
func profile(c *gin.Context) {
svrsUnderTest := LocalStore.GetAll()
svrsUnderTest := DefaultStore.GetAll()
var mergedProfiles = make([][]*cover.Profile, len(svrsUnderTest))
for _, addrs := range svrsUnderTest {
for _, addr := range addrs {
@ -154,7 +156,7 @@ func profile(c *gin.Context) {
}
func clear(c *gin.Context) {
svrsUnderTest := LocalStore.GetAll()
svrsUnderTest := DefaultStore.GetAll()
for svc, addrs := range svrsUnderTest {
for _, addr := range addrs {
pp, err := NewWorker(addr).Clear()
@ -168,7 +170,7 @@ func clear(c *gin.Context) {
}
func initSystem(c *gin.Context) {
if err := LocalStore.Init(); err != nil {
if err := DefaultStore.Init(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

View File

@ -19,10 +19,11 @@ package cover
import (
"bufio"
"fmt"
log "github.com/sirupsen/logrus"
"os"
"strings"
"sync"
log "github.com/sirupsen/logrus"
)
// Store persistents the registered service information
@ -38,77 +39,82 @@ type Store interface {
// Init cleanup all the registered service information
Init() error
// Set stores the services information into internal state
Set(services map[string][]string)
}
// PersistenceFile is the file to save services address information
const PersistenceFile = "_svrs_address.txt"
// localStore holds the registered services into memory and persistent to a local file
type localStore struct {
// fileStore holds the registered services into memory and persistent to a local file
type fileStore struct {
mu sync.RWMutex
servicesMap map[string][]string
persistentFile string
memoryStore Store
}
// Add adds the given service to localStore
func (l *localStore) Add(s Service) error {
l.mu.Lock()
defer l.mu.Unlock()
// load to memory
if addrs, ok := l.servicesMap[s.Name]; ok {
for _, addr := range addrs {
if addr == s.Address {
log.Printf("service registered already, name: %s, address: %s", s.Name, s.Address)
return nil
}
}
addrs = append(addrs, s.Address)
l.servicesMap[s.Name] = addrs
} else {
l.servicesMap[s.Name] = []string{s.Address}
// NewFileStore creates a store using local file
func NewFileStore() Store {
l := &fileStore{
persistentFile: PersistenceFile,
memoryStore: NewMemoryStore(),
}
if err := l.load(); err != nil {
log.Fatalf("load failed, file: %s, err: %v", l.persistentFile, err)
}
return l
}
// Add adds the given service to file Store
func (l *fileStore) Add(s Service) error {
l.memoryStore.Add(s)
// persistent to local store
l.mu.Lock()
defer l.mu.Unlock()
return l.appendToFile(s)
}
// Get returns the registered service information with the given name
func (l *localStore) Get(name string) []string {
l.mu.RLock()
defer l.mu.RUnlock()
return l.servicesMap[name]
func (l *fileStore) Get(name string) []string {
return l.memoryStore.Get(name)
}
// Get returns all the registered service information
func (l *localStore) GetAll() map[string][]string {
l.mu.RLock()
defer l.mu.RUnlock()
return l.servicesMap
func (l *fileStore) GetAll() map[string][]string {
return l.memoryStore.GetAll()
}
// Init cleanup all the registered service information
// and the local persistent file
func (l *localStore) Init() error {
func (l *fileStore) Init() error {
if err := l.memoryStore.Init(); err != nil {
return err
}
l.mu.Lock()
defer l.mu.Unlock()
if err := os.Remove(l.persistentFile); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete file %s, err: %v", l.persistentFile, err)
}
l.servicesMap = make(map[string][]string, 0)
return nil
}
// load all registered service from file to memory
func (l *localStore) load() (map[string][]string, error) {
func (l *fileStore) load() error {
var svrsMap = make(map[string][]string, 0)
f, err := os.Open(l.persistentFile)
if err != nil {
if os.IsNotExist(err) {
return svrsMap, nil
return nil
}
return svrsMap, fmt.Errorf("failed to open file, path: %s, err: %v", l.persistentFile, err)
return fmt.Errorf("failed to open file, path: %s, err: %v", l.persistentFile, err)
}
defer f.Close()
@ -129,13 +135,19 @@ func (l *localStore) load() (map[string][]string, error) {
}
if err := ns.Err(); err != nil {
return svrsMap, fmt.Errorf("read file failed, file: %s, err: %v", l.persistentFile, err)
return fmt.Errorf("read file failed, file: %s, err: %v", l.persistentFile, err)
}
return svrsMap, nil
// set information to memory
l.memoryStore.Set(svrsMap)
return nil
}
func (l *localStore) appendToFile(s Service) error {
func (l *fileStore) Set(services map[string][]string) {
panic("TO BE IMPLEMENTED")
}
func (l *fileStore) appendToFile(s Service) error {
f, err := os.OpenFile(l.persistentFile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
@ -159,17 +171,67 @@ func split(r rune) bool {
return r == '&'
}
// NewStore creates a store using local file
func NewStore() Store {
l := &localStore{
persistentFile: PersistenceFile,
// memoryStore holds the registered services only into memory
type memoryStore struct {
mu sync.RWMutex
servicesMap map[string][]string
}
// NewMemoryStore creates a memory store
func NewMemoryStore() Store {
return &memoryStore{
servicesMap: make(map[string][]string, 0),
}
services, err := l.load()
if err != nil {
log.Fatalf("load failed, file: %s, err: %v", l.persistentFile, err)
}
l.servicesMap = services
return l
}
// Add adds the given service to MemoryStore
func (l *memoryStore) Add(s Service) error {
l.mu.Lock()
defer l.mu.Unlock()
// load to memory
if addrs, ok := l.servicesMap[s.Name]; ok {
for _, addr := range addrs {
if addr == s.Address {
log.Printf("service registered already, name: %s, address: %s", s.Name, s.Address)
return nil
}
}
addrs = append(addrs, s.Address)
l.servicesMap[s.Name] = addrs
} else {
l.servicesMap[s.Name] = []string{s.Address}
}
return nil
}
// Get returns the registered service information with the given name
func (l *memoryStore) Get(name string) []string {
l.mu.RLock()
defer l.mu.RUnlock()
return l.servicesMap[name]
}
// Get returns all the registered service information
func (l *memoryStore) GetAll() map[string][]string {
l.mu.RLock()
defer l.mu.RUnlock()
return l.servicesMap
}
// Init cleanup all the registered service information
// and the local persistent file
func (l *memoryStore) Init() error {
l.mu.Lock()
defer l.mu.Unlock()
l.servicesMap = make(map[string][]string, 0)
return nil
}
func (l *memoryStore) Set(services map[string][]string) {
l.mu.Lock()
defer l.mu.Unlock()
l.servicesMap = services
}

View File

@ -21,7 +21,7 @@ import (
)
func TestLocalStore(t *testing.T) {
localStore := NewStore()
localStore := NewFileStore()
var tc1 = Service{
Name: "a",
Address: "http://127.0.0.1",
@ -44,7 +44,7 @@ func TestLocalStore(t *testing.T) {
localStore.Add(tc4)
addrs := localStore.Get(tc1.Name)
if len(addrs) != 2 {
t.Error("unexpect result")
t.Error("unexpected result")
}
for _, addr := range addrs {