Ittt #52
60 changed files with 3053 additions and 646 deletions
|
@ -7,6 +7,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/crypto"
|
"github.com/lbryio/lbry.go/v2/extras/crypto"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/cluster"
|
"github.com/lbryio/reflector.go/cluster"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/lbryio/lbry.go/v2/schema/stake"
|
"github.com/lbryio/lbry.go/v2/schema/stake"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/golang/protobuf/jsonpb"
|
"github.com/gogo/protobuf/jsonpb"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/peer"
|
"github.com/lbryio/reflector.go/server/peer"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
@ -41,7 +41,7 @@ func getStreamCmd(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
var sd stream.SDBlob
|
var sd stream.SDBlob
|
||||||
|
|
||||||
sdb, err := s.Get(sdHash)
|
sdb, _, err := s.Get(sdHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func getStreamCmd(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(sd.BlobInfos)-1; i++ {
|
for i := 0; i < len(sd.BlobInfos)-1; i++ {
|
||||||
b, err := s.Get(hex.EncodeToString(sd.BlobInfos[i].BlobHash))
|
b, _, err := s.Get(hex.EncodeToString(sd.BlobInfos[i].BlobHash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
94
cmd/integrity.go
Normal file
94
cmd/integrity.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/meta"
|
||||||
|
"github.com/lbryio/reflector.go/store/speedwalk"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var threads int
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "check-integrity",
|
||||||
|
Short: "check blobs integrity for a given path",
|
||||||
|
Run: integrityCheckCmd,
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&diskStorePath, "store-path", "", "path of the store where all blobs are cached")
|
||||||
|
cmd.Flags().IntVar(&threads, "threads", runtime.NumCPU()-1, "number of concurrent threads to process blobs")
|
||||||
|
rootCmd.AddCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func integrityCheckCmd(cmd *cobra.Command, args []string) {
|
||||||
|
log.Printf("reflector %s", meta.VersionString())
|
||||||
|
if diskStorePath == "" {
|
||||||
|
log.Fatal("store-path must be defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
blobs, err := speedwalk.AllFiles(diskStorePath, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error while reading blobs from disk %s", errors.FullTrace(err))
|
||||||
|
}
|
||||||
|
tasks := make(chan string, len(blobs))
|
||||||
|
done := make(chan bool)
|
||||||
|
processed := new(int32)
|
||||||
|
go produce(tasks, blobs)
|
||||||
|
cpus := runtime.NumCPU()
|
||||||
|
for i := 0; i < cpus-1; i++ {
|
||||||
|
go consume(i, tasks, done, len(blobs), processed)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func produce(tasks chan<- string, blobs []string) {
|
||||||
|
for _, b := range blobs {
|
||||||
|
tasks <- b
|
||||||
|
}
|
||||||
|
close(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consume(worker int, tasks <-chan string, done chan<- bool, totalTasks int, processed *int32) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for b := range tasks {
|
||||||
|
processedSoFar := atomic.AddInt32(processed, 1)
|
||||||
|
if worker == 0 {
|
||||||
|
remaining := int32(totalTasks) - processedSoFar
|
||||||
|
timePerBlob := time.Since(start).Microseconds() / int64(processedSoFar)
|
||||||
|
remainingTime := time.Duration(int64(remaining)*timePerBlob) * time.Microsecond
|
||||||
|
log.Infof("[T%d] %d/%d blobs processed so far. ETA: %s", worker, processedSoFar, totalTasks, remainingTime.String())
|
||||||
|
}
|
||||||
|
blobPath := path.Join(diskStorePath, b[:2], b)
|
||||||
|
blob, err := ioutil.ReadFile(blobPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Errorf("[Worker %d] Error looking up blob %s: %s", worker, b, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hashBytes := sha512.Sum384(blob)
|
||||||
|
readHash := hex.EncodeToString(hashBytes[:])
|
||||||
|
if readHash != b {
|
||||||
|
log.Infof("[%s] found a broken blob while reading from disk. Actual hash: %s", b, readHash)
|
||||||
|
err := os.Remove(blobPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while deleting broken blob %s: %s", b, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done <- true
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/db"
|
"github.com/lbryio/reflector.go/db"
|
||||||
"github.com/lbryio/reflector.go/peer"
|
"github.com/lbryio/reflector.go/server/peer"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -33,11 +33,13 @@ func peerCmd(cmd *cobra.Command, args []string) {
|
||||||
peerServer := peer.NewServer(s3)
|
peerServer := peer.NewServer(s3)
|
||||||
|
|
||||||
if !peerNoDB {
|
if !peerNoDB {
|
||||||
db := new(db.SQL)
|
db := &db.SQL{
|
||||||
|
LogQueries: log.GetLevel() == log.DebugLevel,
|
||||||
|
}
|
||||||
err = db.Connect(globalConfig.DBConn)
|
err = db.Connect(globalConfig.DBConn)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
combo := store.NewDBBackedStore(s3, db)
|
combo := store.NewDBBackedStore(s3, db, false)
|
||||||
peerServer = peer.NewServer(combo)
|
peerServer = peer.NewServer(combo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
51
cmd/populatedb.go
Normal file
51
cmd/populatedb.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lbryio/reflector.go/db"
|
||||||
|
"github.com/lbryio/reflector.go/meta"
|
||||||
|
"github.com/lbryio/reflector.go/store/speedwalk"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
diskStorePath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var cmd = &cobra.Command{
|
||||||
|
Use: "populate-db",
|
||||||
|
Short: "populate local database with blobs from a disk storage",
|
||||||
|
Run: populateDbCmd,
|
||||||
|
}
|
||||||
|
cmd.Flags().StringVar(&diskStorePath, "store-path", "",
|
||||||
|
"path of the store where all blobs are cached")
|
||||||
|
rootCmd.AddCommand(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateDbCmd(cmd *cobra.Command, args []string) {
|
||||||
|
log.Printf("reflector %s", meta.VersionString())
|
||||||
|
if diskStorePath == "" {
|
||||||
|
log.Fatal("store-path must be defined")
|
||||||
|
}
|
||||||
|
localDb := &db.SQL{
|
||||||
|
SoftDelete: true,
|
||||||
|
TrackAccess: db.TrackAccessBlobs,
|
||||||
|
LogQueries: log.GetLevel() == log.DebugLevel,
|
||||||
|
}
|
||||||
|
err := localDb.Connect("reflector:reflector@tcp(localhost:3306)/reflector")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
blobs, err := speedwalk.AllFiles(diskStorePath, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = localDb.AddBlobs(blobs)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while storing to db: %s", errors.FullTrace(err))
|
||||||
|
}
|
||||||
|
}
|
338
cmd/reflector.go
338
cmd/reflector.go
|
@ -8,34 +8,62 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/util"
|
||||||
"github.com/lbryio/reflector.go/db"
|
"github.com/lbryio/reflector.go/db"
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
"github.com/lbryio/reflector.go/meta"
|
"github.com/lbryio/reflector.go/meta"
|
||||||
"github.com/lbryio/reflector.go/peer"
|
|
||||||
"github.com/lbryio/reflector.go/peer/http3"
|
|
||||||
"github.com/lbryio/reflector.go/reflector"
|
"github.com/lbryio/reflector.go/reflector"
|
||||||
|
"github.com/lbryio/reflector.go/server/http"
|
||||||
|
http32 "github.com/lbryio/reflector.go/server/http3"
|
||||||
|
"github.com/lbryio/reflector.go/server/peer"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
|
||||||
|
"github.com/c2h5oh/datasize"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cast"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
//port configuration
|
||||||
tcpPeerPort int
|
tcpPeerPort int
|
||||||
http3PeerPort int
|
http3PeerPort int
|
||||||
|
httpPeerPort int
|
||||||
receiverPort int
|
receiverPort int
|
||||||
metricsPort int
|
metricsPort int
|
||||||
|
|
||||||
|
//flags configuration
|
||||||
disableUploads bool
|
disableUploads bool
|
||||||
disableBlocklist bool
|
disableBlocklist bool
|
||||||
proxyAddress string
|
|
||||||
proxyPort string
|
|
||||||
proxyProtocol string
|
|
||||||
useDB bool
|
useDB bool
|
||||||
cloudFrontEndpoint string
|
|
||||||
reflectorCmdDiskCache string
|
//upstream configuration
|
||||||
reflectorCmdMemCache int
|
upstreamReflector string
|
||||||
|
upstreamProtocol string
|
||||||
|
|
||||||
|
//downstream configuration
|
||||||
|
requestQueueSize int
|
||||||
|
|
||||||
|
//upstream edge configuration (to "cold" storage)
|
||||||
|
originEndpoint string
|
||||||
|
originEndpointFallback string
|
||||||
|
|
||||||
|
//cache configuration
|
||||||
|
diskCache string
|
||||||
|
secondaryDiskCache string
|
||||||
|
memCache int
|
||||||
)
|
)
|
||||||
|
var cacheManagers = []string{"localdb", "lfu", "arc", "lru", "simple"}
|
||||||
|
|
||||||
|
var cacheMangerToGcache = map[string]store.EvictionStrategy{
|
||||||
|
"lfu": store.LFU,
|
||||||
|
"arc": store.ARC,
|
||||||
|
"lru": store.LRU,
|
||||||
|
"simple": store.SIMPLE,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var cmd = &cobra.Command{
|
var cmd = &cobra.Command{
|
||||||
|
@ -43,20 +71,29 @@ func init() {
|
||||||
Short: "Run reflector server",
|
Short: "Run reflector server",
|
||||||
Run: reflectorCmd,
|
Run: reflectorCmd,
|
||||||
}
|
}
|
||||||
cmd.Flags().StringVar(&proxyAddress, "proxy-address", "", "address of another reflector server where blobs are fetched from")
|
|
||||||
cmd.Flags().StringVar(&proxyPort, "proxy-port", "5567", "port of another reflector server where blobs are fetched from")
|
cmd.Flags().IntVar(&tcpPeerPort, "tcp-peer-port", 5567, "The port reflector will distribute content from for the TCP (LBRY) protocol")
|
||||||
cmd.Flags().StringVar(&proxyProtocol, "proxy-protocol", "http3", "protocol used to fetch blobs from another reflector server (tcp/http3)")
|
|
||||||
cmd.Flags().StringVar(&cloudFrontEndpoint, "cloudfront-endpoint", "", "CloudFront edge endpoint for standard HTTP retrieval")
|
|
||||||
cmd.Flags().IntVar(&tcpPeerPort, "tcp-peer-port", 5567, "The port reflector will distribute content from")
|
|
||||||
cmd.Flags().IntVar(&http3PeerPort, "http3-peer-port", 5568, "The port reflector will distribute content from over HTTP3 protocol")
|
cmd.Flags().IntVar(&http3PeerPort, "http3-peer-port", 5568, "The port reflector will distribute content from over HTTP3 protocol")
|
||||||
|
cmd.Flags().IntVar(&httpPeerPort, "http-peer-port", 5569, "The port reflector will distribute content from over HTTP protocol")
|
||||||
cmd.Flags().IntVar(&receiverPort, "receiver-port", 5566, "The port reflector will receive content from")
|
cmd.Flags().IntVar(&receiverPort, "receiver-port", 5566, "The port reflector will receive content from")
|
||||||
cmd.Flags().IntVar(&metricsPort, "metrics-port", 2112, "The port reflector will use for metrics")
|
cmd.Flags().IntVar(&metricsPort, "metrics-port", 2112, "The port reflector will use for prometheus metrics")
|
||||||
|
|
||||||
cmd.Flags().BoolVar(&disableUploads, "disable-uploads", false, "Disable uploads to this reflector server")
|
cmd.Flags().BoolVar(&disableUploads, "disable-uploads", false, "Disable uploads to this reflector server")
|
||||||
cmd.Flags().BoolVar(&disableBlocklist, "disable-blocklist", false, "Disable blocklist watching/updating")
|
cmd.Flags().BoolVar(&disableBlocklist, "disable-blocklist", false, "Disable blocklist watching/updating")
|
||||||
cmd.Flags().BoolVar(&useDB, "use-db", true, "whether to connect to the reflector db or not")
|
cmd.Flags().BoolVar(&useDB, "use-db", true, "Whether to connect to the reflector db or not")
|
||||||
cmd.Flags().StringVar(&reflectorCmdDiskCache, "disk-cache", "",
|
|
||||||
"enable disk cache, setting max size and path where to store blobs. format is 'MAX_BLOBS:CACHE_PATH'")
|
cmd.Flags().StringVar(&upstreamReflector, "upstream-reflector", "", "host:port of a reflector server where blobs are fetched from")
|
||||||
cmd.Flags().IntVar(&reflectorCmdMemCache, "mem-cache", 0, "enable in-memory cache with a max size of this many blobs")
|
cmd.Flags().StringVar(&upstreamProtocol, "upstream-protocol", "http", "protocol used to fetch blobs from another upstream reflector server (tcp/http3/http)")
|
||||||
|
|
||||||
|
cmd.Flags().IntVar(&requestQueueSize, "request-queue-size", 200, "How many concurrent requests from downstream should be handled at once (the rest will wait)")
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&originEndpoint, "origin-endpoint", "", "HTTP edge endpoint for standard HTTP retrieval")
|
||||||
|
cmd.Flags().StringVar(&originEndpointFallback, "origin-endpoint-fallback", "", "HTTP edge endpoint for standard HTTP retrieval if first origin fails")
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&diskCache, "disk-cache", "100GB:/tmp/downloaded_blobs:localdb", "Where to cache blobs on the file system. format is 'sizeGB:CACHE_PATH:cachemanager' (cachemanagers: localdb/lfu/arc/lru)")
|
||||||
|
cmd.Flags().StringVar(&secondaryDiskCache, "optional-disk-cache", "", "Optional secondary file system cache for blobs. format is 'sizeGB:CACHE_PATH:cachemanager' (cachemanagers: localdb/lfu/arc/lru) (this would get hit before the one specified in disk-cache)")
|
||||||
|
cmd.Flags().IntVar(&memCache, "mem-cache", 0, "enable in-memory cache with a max size of this many blobs")
|
||||||
|
|
||||||
rootCmd.AddCommand(cmd)
|
rootCmd.AddCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,11 +101,11 @@ func reflectorCmd(cmd *cobra.Command, args []string) {
|
||||||
log.Printf("reflector %s", meta.VersionString())
|
log.Printf("reflector %s", meta.VersionString())
|
||||||
|
|
||||||
// the blocklist logic requires the db backed store to be the outer-most store
|
// the blocklist logic requires the db backed store to be the outer-most store
|
||||||
underlyingStore := setupStore()
|
underlyingStore := initStores()
|
||||||
outerStore := wrapWithCache(underlyingStore)
|
underlyingStoreWithCaches, cleanerStopper := initCaches(underlyingStore)
|
||||||
|
|
||||||
if !disableUploads {
|
if !disableUploads {
|
||||||
reflectorServer := reflector.NewServer(underlyingStore)
|
reflectorServer := reflector.NewServer(underlyingStore, underlyingStoreWithCaches)
|
||||||
reflectorServer.Timeout = 3 * time.Minute
|
reflectorServer.Timeout = 3 * time.Minute
|
||||||
reflectorServer.EnableBlocklist = !disableBlocklist
|
reflectorServer.EnableBlocklist = !disableBlocklist
|
||||||
|
|
||||||
|
@ -79,117 +116,264 @@ func reflectorCmd(cmd *cobra.Command, args []string) {
|
||||||
defer reflectorServer.Shutdown()
|
defer reflectorServer.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
peerServer := peer.NewServer(outerStore)
|
peerServer := peer.NewServer(underlyingStoreWithCaches)
|
||||||
err := peerServer.Start(":" + strconv.Itoa(tcpPeerPort))
|
err := peerServer.Start(":" + strconv.Itoa(tcpPeerPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer peerServer.Shutdown()
|
defer peerServer.Shutdown()
|
||||||
|
|
||||||
http3PeerServer := http3.NewServer(outerStore)
|
http3PeerServer := http32.NewServer(underlyingStoreWithCaches, requestQueueSize)
|
||||||
err = http3PeerServer.Start(":" + strconv.Itoa(http3PeerPort))
|
err = http3PeerServer.Start(":" + strconv.Itoa(http3PeerPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer http3PeerServer.Shutdown()
|
defer http3PeerServer.Shutdown()
|
||||||
|
|
||||||
|
httpServer := http.NewServer(underlyingStoreWithCaches, requestQueueSize)
|
||||||
|
err = httpServer.Start(":" + strconv.Itoa(httpPeerPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer httpServer.Shutdown()
|
||||||
|
|
||||||
metricsServer := metrics.NewServer(":"+strconv.Itoa(metricsPort), "/metrics")
|
metricsServer := metrics.NewServer(":"+strconv.Itoa(metricsPort), "/metrics")
|
||||||
metricsServer.Start()
|
metricsServer.Start()
|
||||||
defer metricsServer.Shutdown()
|
defer metricsServer.Shutdown()
|
||||||
|
defer underlyingStoreWithCaches.Shutdown()
|
||||||
|
defer underlyingStore.Shutdown() //do we actually need this? Oo
|
||||||
|
|
||||||
interruptChan := make(chan os.Signal, 1)
|
interruptChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM)
|
||||||
<-interruptChan
|
<-interruptChan
|
||||||
// deferred shutdowns happen now
|
// deferred shutdowns happen now
|
||||||
|
cleanerStopper.StopAndWait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupStore() store.BlobStore {
|
func initUpstreamStore() store.BlobStore {
|
||||||
var s store.BlobStore
|
var s store.BlobStore
|
||||||
|
if upstreamReflector == "" {
|
||||||
if proxyAddress != "" {
|
return nil
|
||||||
switch proxyProtocol {
|
}
|
||||||
|
switch upstreamProtocol {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
s = peer.NewStore(peer.StoreOpts{
|
s = peer.NewStore(peer.StoreOpts{
|
||||||
Address: proxyAddress + ":" + proxyPort,
|
Address: upstreamReflector,
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
})
|
})
|
||||||
case "http3":
|
case "http3":
|
||||||
s = http3.NewStore(http3.StoreOpts{
|
s = http32.NewStore(http32.StoreOpts{
|
||||||
Address: proxyAddress + ":" + proxyPort,
|
Address: upstreamReflector,
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
})
|
})
|
||||||
|
case "http":
|
||||||
|
s = store.NewHttpStore(upstreamReflector)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("protocol is not recognized: %s", proxyProtocol)
|
log.Fatalf("protocol is not recognized: %s", upstreamProtocol)
|
||||||
}
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func initEdgeStore() store.BlobStore {
|
||||||
|
var s3Store *store.S3Store
|
||||||
|
var s store.BlobStore
|
||||||
|
|
||||||
|
if conf != "none" {
|
||||||
|
s3Store = store.NewS3Store(globalConfig.AwsID, globalConfig.AwsSecret, globalConfig.BucketRegion, globalConfig.BucketName)
|
||||||
|
}
|
||||||
|
if originEndpointFallback != "" && originEndpoint != "" {
|
||||||
|
ittt := store.NewITTTStore(store.NewCloudFrontROStore(originEndpoint), store.NewCloudFrontROStore(originEndpointFallback))
|
||||||
|
if s3Store != nil {
|
||||||
|
s = store.NewCloudFrontRWStore(ittt, s3Store)
|
||||||
} else {
|
} else {
|
||||||
s3Store := store.NewS3Store(globalConfig.AwsID, globalConfig.AwsSecret, globalConfig.BucketRegion, globalConfig.BucketName)
|
s = ittt
|
||||||
if cloudFrontEndpoint != "" {
|
}
|
||||||
s = store.NewCloudFrontRWStore(store.NewCloudFrontROStore(cloudFrontEndpoint), s3Store)
|
} else if s3Store != nil {
|
||||||
} else {
|
|
||||||
s = s3Store
|
s = s3Store
|
||||||
|
} else {
|
||||||
|
log.Fatalf("this configuration does not include a valid upstream source")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if useDB {
|
|
||||||
db := new(db.SQL)
|
|
||||||
db.TrackAccessTime = true
|
|
||||||
err := db.Connect(globalConfig.DBConn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s = store.NewDBBackedStore(s, db)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapWithCache(s store.BlobStore) store.BlobStore {
|
func initDBStore(s store.BlobStore) store.BlobStore {
|
||||||
wrapped := s
|
if useDB {
|
||||||
|
dbInst := &db.SQL{
|
||||||
|
TrackAccess: db.TrackAccessStreams,
|
||||||
|
LogQueries: log.GetLevel() == log.DebugLevel,
|
||||||
|
}
|
||||||
|
err := dbInst.Connect(globalConfig.DBConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
s = store.NewDBBackedStore(s, dbInst, false)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
diskCacheMaxSize, diskCachePath := diskCacheParams()
|
func initStores() store.BlobStore {
|
||||||
if diskCacheMaxSize > 0 {
|
s := initUpstreamStore()
|
||||||
|
if s == nil {
|
||||||
|
s = initEdgeStore()
|
||||||
|
}
|
||||||
|
s = initDBStore(s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// initCaches returns a store wrapped with caches and a stop group to execute a clean shutdown
|
||||||
|
func initCaches(s store.BlobStore) (store.BlobStore, *stop.Group) {
|
||||||
|
stopper := stop.New()
|
||||||
|
diskStore := initDiskStore(s, diskCache, stopper)
|
||||||
|
finalStore := initDiskStore(diskStore, secondaryDiskCache, stopper)
|
||||||
|
stop.New()
|
||||||
|
if memCache > 0 {
|
||||||
|
finalStore = store.NewCachingStore(
|
||||||
|
"reflector",
|
||||||
|
finalStore,
|
||||||
|
store.NewGcacheStore("mem", store.NewMemStore(), memCache, store.LRU),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return finalStore, stopper
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDiskStore(upstreamStore store.BlobStore, diskParams string, stopper *stop.Group) store.BlobStore {
|
||||||
|
diskCacheMaxSize, diskCachePath, cacheManager := diskCacheParams(diskParams)
|
||||||
|
//we are tracking blobs in memory with a 1 byte long boolean, which means that for each 2MB (a blob) we need 1Byte
|
||||||
|
// so if the underlying cache holds 10MB, 10MB/2MB=5Bytes which is also the exact count of objects to restore on startup
|
||||||
|
realCacheSize := float64(diskCacheMaxSize) / float64(stream.MaxBlobSize)
|
||||||
|
if diskCacheMaxSize == 0 {
|
||||||
|
return upstreamStore
|
||||||
|
}
|
||||||
err := os.MkdirAll(diskCachePath, os.ModePerm)
|
err := os.MkdirAll(diskCachePath, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
wrapped = store.NewCachingStore(
|
|
||||||
"reflector",
|
diskStore := store.NewDiskStore(diskCachePath, 2)
|
||||||
wrapped,
|
var unwrappedStore store.BlobStore
|
||||||
store.NewLRUStore("peer_server", store.NewDiskStore(diskCachePath, 2), diskCacheMaxSize),
|
cleanerStopper := stop.New(stopper)
|
||||||
)
|
|
||||||
|
if cacheManager == "localdb" {
|
||||||
|
localDb := &db.SQL{
|
||||||
|
SoftDelete: true,
|
||||||
|
TrackAccess: db.TrackAccessBlobs,
|
||||||
|
LogQueries: log.GetLevel() == log.DebugLevel,
|
||||||
|
}
|
||||||
|
err = localDb.Connect("reflector:reflector@tcp(localhost:3306)/reflector")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
unwrappedStore = store.NewDBBackedStore(diskStore, localDb, true)
|
||||||
|
go cleanOldestBlobs(int(realCacheSize), localDb, unwrappedStore, cleanerStopper)
|
||||||
|
} else {
|
||||||
|
unwrappedStore = store.NewGcacheStore("nvme", store.NewDiskStore(diskCachePath, 2), int(realCacheSize), cacheMangerToGcache[cacheManager])
|
||||||
}
|
}
|
||||||
|
|
||||||
if reflectorCmdMemCache > 0 {
|
wrapped := store.NewCachingStore(
|
||||||
wrapped = store.NewCachingStore(
|
|
||||||
"reflector",
|
"reflector",
|
||||||
wrapped,
|
upstreamStore,
|
||||||
store.NewLRUStore("peer_server", store.NewMemStore(), reflectorCmdMemCache),
|
unwrappedStore,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
}
|
}
|
||||||
|
|
||||||
func diskCacheParams() (int, string) {
|
func diskCacheParams(diskParams string) (int, string, string) {
|
||||||
if reflectorCmdDiskCache == "" {
|
if diskParams == "" {
|
||||||
return 0, ""
|
return 0, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := strings.Split(reflectorCmdDiskCache, ":")
|
parts := strings.Split(diskParams, ":")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 3 {
|
||||||
log.Fatalf("--disk-cache must be a number, followed by ':', followed by a string")
|
log.Fatalf("%s does is formatted incorrectly. Expected format: 'sizeGB:CACHE_PATH:cachemanager' for example: '100GB:/tmp/downloaded_blobs:localdb'", diskParams)
|
||||||
}
|
|
||||||
|
|
||||||
maxSize := cast.ToInt(parts[0])
|
|
||||||
if maxSize <= 0 {
|
|
||||||
log.Fatalf("--disk-cache max size must be more than 0")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diskCacheSize := parts[0]
|
||||||
path := parts[1]
|
path := parts[1]
|
||||||
|
cacheManager := parts[2]
|
||||||
|
|
||||||
if len(path) == 0 || path[0] != '/' {
|
if len(path) == 0 || path[0] != '/' {
|
||||||
log.Fatalf("--disk-cache path must start with '/'")
|
log.Fatalf("disk cache paths must start with '/'")
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxSize, path
|
if !util.InSlice(cacheManager, cacheManagers) {
|
||||||
|
log.Fatalf("specified cache manager '%s' is not supported. Use one of the following: %v", cacheManager, cacheManagers)
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxSize datasize.ByteSize
|
||||||
|
err := maxSize.UnmarshalText([]byte(diskCacheSize))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if maxSize <= 0 {
|
||||||
|
log.Fatal("disk cache size must be more than 0")
|
||||||
|
}
|
||||||
|
return int(maxSize), path, cacheManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanOldestBlobs(maxItems int, db *db.SQL, store store.BlobStore, stopper *stop.Group) {
|
||||||
|
// this is so that it runs on startup without having to wait for 10 minutes
|
||||||
|
err := doClean(maxItems, db, store, stopper)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(errors.FullTrace(err))
|
||||||
|
}
|
||||||
|
const cleanupInterval = 10 * time.Minute
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopper.Ch():
|
||||||
|
log.Infoln("stopping self cleanup")
|
||||||
|
return
|
||||||
|
case <-time.After(cleanupInterval):
|
||||||
|
err := doClean(maxItems, db, store, stopper)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(errors.FullTrace(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doClean(maxItems int, db *db.SQL, store store.BlobStore, stopper *stop.Group) error {
|
||||||
|
blobsCount, err := db.Count()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if blobsCount >= maxItems {
|
||||||
|
itemsToDelete := blobsCount / 10
|
||||||
|
blobs, err := db.LeastRecentlyAccessedHashes(itemsToDelete)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
blobsChan := make(chan string, len(blobs))
|
||||||
|
wg := &stop.Group{}
|
||||||
|
go func() {
|
||||||
|
for _, hash := range blobs {
|
||||||
|
select {
|
||||||
|
case <-stopper.Ch():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
blobsChan <- hash
|
||||||
|
}
|
||||||
|
close(blobsChan)
|
||||||
|
}()
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for h := range blobsChan {
|
||||||
|
select {
|
||||||
|
case <-stopper.Ch():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
err = store.Delete(h)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error pruning %s: %s", h, errors.FullTrace(err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
13
cmd/root.go
13
cmd/root.go
|
@ -6,10 +6,11 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/updater"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht"
|
"github.com/lbryio/lbry.go/v2/dht"
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/extras/util"
|
"github.com/lbryio/lbry.go/v2/extras/util"
|
||||||
"github.com/lbryio/reflector.go/updater"
|
|
||||||
|
|
||||||
"github.com/johntdyer/slackrus"
|
"github.com/johntdyer/slackrus"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -164,8 +165,8 @@ func mustGetFlagInt64(cmd *cobra.Command, name string) int64 {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustGetFlagBool(cmd *cobra.Command, name string) bool {
|
//func mustGetFlagBool(cmd *cobra.Command, name string) bool {
|
||||||
v, err := cmd.Flags().GetBool(name)
|
// v, err := cmd.Flags().GetBool(name)
|
||||||
checkErr(err)
|
// checkErr(err)
|
||||||
return v
|
// return v
|
||||||
}
|
//}
|
||||||
|
|
13
cmd/start.go
13
cmd/start.go
|
@ -7,15 +7,16 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht"
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht/bits"
|
|
||||||
"github.com/lbryio/reflector.go/cluster"
|
"github.com/lbryio/reflector.go/cluster"
|
||||||
"github.com/lbryio/reflector.go/db"
|
"github.com/lbryio/reflector.go/db"
|
||||||
"github.com/lbryio/reflector.go/peer"
|
|
||||||
"github.com/lbryio/reflector.go/prism"
|
"github.com/lbryio/reflector.go/prism"
|
||||||
"github.com/lbryio/reflector.go/reflector"
|
"github.com/lbryio/reflector.go/reflector"
|
||||||
|
"github.com/lbryio/reflector.go/server/peer"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/dht"
|
||||||
|
"github.com/lbryio/lbry.go/v2/dht/bits"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -52,11 +53,13 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startCmd(cmd *cobra.Command, args []string) {
|
func startCmd(cmd *cobra.Command, args []string) {
|
||||||
db := new(db.SQL)
|
db := &db.SQL{
|
||||||
|
LogQueries: log.GetLevel() == log.DebugLevel,
|
||||||
|
}
|
||||||
err := db.Connect(globalConfig.DBConn)
|
err := db.Connect(globalConfig.DBConn)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
s3 := store.NewS3Store(globalConfig.AwsID, globalConfig.AwsSecret, globalConfig.BucketRegion, globalConfig.BucketName)
|
s3 := store.NewS3Store(globalConfig.AwsID, globalConfig.AwsSecret, globalConfig.BucketRegion, globalConfig.BucketName)
|
||||||
comboStore := store.NewDBBackedStore(s3, db)
|
comboStore := store.NewDBBackedStore(s3, db, false)
|
||||||
|
|
||||||
conf := prism.DefaultConf()
|
conf := prism.DefaultConf()
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/meta"
|
"github.com/lbryio/reflector.go/meta"
|
||||||
"github.com/lbryio/reflector.go/peer"
|
|
||||||
"github.com/lbryio/reflector.go/reflector"
|
"github.com/lbryio/reflector.go/reflector"
|
||||||
|
"github.com/lbryio/reflector.go/server/peer"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
@ -31,7 +31,7 @@ func testCmd(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
memStore := store.NewMemStore()
|
memStore := store.NewMemStore()
|
||||||
|
|
||||||
reflectorServer := reflector.NewServer(memStore)
|
reflectorServer := reflector.NewServer(memStore, memStore)
|
||||||
reflectorServer.Timeout = 3 * time.Minute
|
reflectorServer.Timeout = 3 * time.Minute
|
||||||
|
|
||||||
err := reflectorServer.Start(":" + strconv.Itoa(reflector.DefaultPort))
|
err := reflectorServer.Start(":" + strconv.Itoa(reflector.DefaultPort))
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/lbryio/reflector.go/reflector"
|
"github.com/lbryio/reflector.go/reflector"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,13 +31,15 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadCmd(cmd *cobra.Command, args []string) {
|
func uploadCmd(cmd *cobra.Command, args []string) {
|
||||||
db := new(db.SQL)
|
db := &db.SQL{
|
||||||
|
LogQueries: log.GetLevel() == log.DebugLevel,
|
||||||
|
}
|
||||||
err := db.Connect(globalConfig.DBConn)
|
err := db.Connect(globalConfig.DBConn)
|
||||||
checkErr(err)
|
checkErr(err)
|
||||||
|
|
||||||
st := store.NewDBBackedStore(
|
st := store.NewDBBackedStore(
|
||||||
store.NewS3Store(globalConfig.AwsID, globalConfig.AwsSecret, globalConfig.BucketRegion, globalConfig.BucketName),
|
store.NewS3Store(globalConfig.AwsID, globalConfig.AwsSecret, globalConfig.BucketRegion, globalConfig.BucketName),
|
||||||
db)
|
db, false)
|
||||||
|
|
||||||
uploader := reflector.NewUploader(db, st, uploadWorkers, uploadSkipExistsCheck, uploadDeleteBlobsAfterUpload)
|
uploader := reflector.NewUploader(db, st, uploadWorkers, uploadSkipExistsCheck, uploadDeleteBlobsAfterUpload)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/meta"
|
"github.com/lbryio/reflector.go/meta"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
305
db/db.go
305
db/db.go
|
@ -3,16 +3,22 @@ package db
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht/bits"
|
"github.com/lbryio/lbry.go/v2/dht/bits"
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
qt "github.com/lbryio/lbry.go/v2/extras/query"
|
qt "github.com/lbryio/lbry.go/v2/extras/query"
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
|
||||||
"github.com/go-sql-driver/mysql"
|
"github.com/go-sql-driver/mysql"
|
||||||
_ "github.com/go-sql-driver/mysql" // blank import for db driver ensures its imported even if its not used
|
_ "github.com/go-sql-driver/mysql" // blank import for db driver ensures its imported even if its not used
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/volatiletech/null"
|
"github.com/volatiletech/null"
|
||||||
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SdBlob is a special blob that contains information on the rest of the blobs in the stream
|
// SdBlob is a special blob that contains information on the rest of the blobs in the stream
|
||||||
|
@ -30,19 +36,38 @@ type SdBlob struct {
|
||||||
StreamHash string `json:"stream_hash"`
|
StreamHash string `json:"stream_hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type trackAccess int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TrackAccessNone trackAccess = iota // Don't track accesses
|
||||||
|
TrackAccessStreams // Track accesses at the stream level
|
||||||
|
TrackAccessBlobs // Track accesses at the blob level
|
||||||
|
)
|
||||||
|
|
||||||
// SQL implements the DB interface
|
// SQL implements the DB interface
|
||||||
type SQL struct {
|
type SQL struct {
|
||||||
conn *sql.DB
|
conn *sql.DB
|
||||||
|
|
||||||
TrackAccessTime bool
|
// Track the approx last time a blob or stream was accessed
|
||||||
|
TrackAccess trackAccess
|
||||||
|
|
||||||
|
// Instead of deleting a blob, marked it as not stored in the db
|
||||||
|
SoftDelete bool
|
||||||
|
|
||||||
|
// Log executed queries. qt.InterpolateParams is cpu-heavy. This avoids that call if not needed.
|
||||||
|
LogQueries bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func logQuery(query string, args ...interface{}) {
|
func (s SQL) logQuery(query string, args ...interface{}) {
|
||||||
s, err := qt.InterpolateParams(query, args...)
|
if !s.LogQueries {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
qStr, err := qt.InterpolateParams(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
} else {
|
} else {
|
||||||
log.Debugln(s)
|
log.Debugln(qStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,16 +98,97 @@ func (s *SQL) AddBlob(hash string, length int, isStored bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//AddBlobs adds blobs to the database.
|
||||||
|
func (s *SQL) AddBlobs(hash []string) error {
|
||||||
|
if s.conn == nil {
|
||||||
|
return errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := 10000
|
||||||
|
totalBlobs := int64(len(hash))
|
||||||
|
work := make(chan []string, 1000)
|
||||||
|
stopper := stop.New()
|
||||||
|
var totalInserted atomic.Int64
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < len(hash); i += batch {
|
||||||
|
j := i + batch
|
||||||
|
if j > len(hash) {
|
||||||
|
j = len(hash)
|
||||||
|
}
|
||||||
|
work <- hash[i:j]
|
||||||
|
}
|
||||||
|
log.Infof("done loading %d hashes in the work queue", len(hash))
|
||||||
|
close(work)
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < runtime.NumCPU(); i++ {
|
||||||
|
stopper.Add(1)
|
||||||
|
go func(worker int) {
|
||||||
|
log.Infof("starting worker %d", worker)
|
||||||
|
defer stopper.Done()
|
||||||
|
for hashes := range work {
|
||||||
|
inserted := totalInserted.Load()
|
||||||
|
remaining := totalBlobs - inserted
|
||||||
|
if inserted > 0 {
|
||||||
|
timePerBlob := time.Since(start).Microseconds() / inserted
|
||||||
|
remainingTime := time.Duration(remaining*timePerBlob) * time.Microsecond
|
||||||
|
log.Infof("[T%d] processing batch of %d items. ETA: %s", worker, len(hashes), remainingTime.String())
|
||||||
|
}
|
||||||
|
err := s.insertBlobs(hashes) // Process the batch.
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error while inserting batch: %s", errors.FullTrace(err))
|
||||||
|
}
|
||||||
|
totalInserted.Add(int64(len(hashes)))
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopper.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) insertBlobs(hashes []string) error {
|
||||||
|
var (
|
||||||
|
q string
|
||||||
|
//args []interface{}
|
||||||
|
)
|
||||||
|
dayAgo := time.Now().AddDate(0, 0, -1).Format("2006-01-02 15:04:05")
|
||||||
|
q = "insert into blob_ (hash, is_stored, length, last_accessed_at) values "
|
||||||
|
for _, hash := range hashes {
|
||||||
|
// prepared statements slow everything down by a lot due to reflection
|
||||||
|
// for this specific instance we'll go ahead and hardcode the query to make it go faster
|
||||||
|
q += fmt.Sprintf("('%s',1,%d,'%s'),", hash, stream.MaxBlobSize, dayAgo)
|
||||||
|
//args = append(args, hash, true, stream.MaxBlobSize, dayAgo)
|
||||||
|
}
|
||||||
|
q = strings.TrimSuffix(q, ",")
|
||||||
|
_, err := s.exec(q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SQL) insertBlob(hash string, length int, isStored bool) (int64, error) {
|
func (s *SQL) insertBlob(hash string, length int, isStored bool) (int64, error) {
|
||||||
if length <= 0 {
|
if length <= 0 {
|
||||||
return 0, errors.Err("length must be positive")
|
return 0, errors.Err("length must be positive")
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []interface{}{hash, isStored, length}
|
var (
|
||||||
blobID, err := s.exec(
|
q string
|
||||||
"INSERT INTO blob_ (hash, is_stored, length) VALUES ("+qt.Qs(len(args))+") ON DUPLICATE KEY UPDATE is_stored = (is_stored or VALUES(is_stored))",
|
args []interface{}
|
||||||
args...,
|
|
||||||
)
|
)
|
||||||
|
if s.TrackAccess == TrackAccessBlobs {
|
||||||
|
args = []interface{}{hash, isStored, length, time.Now()}
|
||||||
|
q = "INSERT INTO blob_ (hash, is_stored, length, last_accessed_at) VALUES (" + qt.Qs(len(args)) + ") ON DUPLICATE KEY UPDATE is_stored = (is_stored or VALUES(is_stored)), last_accessed_at = VALUES(last_accessed_at)"
|
||||||
|
} else {
|
||||||
|
args = []interface{}{hash, isStored, length}
|
||||||
|
q = "INSERT INTO blob_ (hash, is_stored, length) VALUES (" + qt.Qs(len(args)) + ") ON DUPLICATE KEY UPDATE is_stored = (is_stored or VALUES(is_stored))"
|
||||||
|
}
|
||||||
|
|
||||||
|
blobID, err := s.exec(q, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -95,17 +201,33 @@ func (s *SQL) insertBlob(hash string, length int, isStored bool) (int64, error)
|
||||||
if blobID == 0 {
|
if blobID == 0 {
|
||||||
return 0, errors.Err("blob ID is 0 even after INSERTing and SELECTing")
|
return 0, errors.Err("blob ID is 0 even after INSERTing and SELECTing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.TrackAccess == TrackAccessBlobs {
|
||||||
|
err := s.touchBlobs([]uint64{uint64(blobID)})
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return blobID, nil
|
return blobID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQL) insertStream(hash string, sdBlobID int64) (int64, error) {
|
func (s *SQL) insertStream(hash string, sdBlobID int64) (int64, error) {
|
||||||
args := []interface{}{hash, sdBlobID, time.Now()}
|
var (
|
||||||
streamID, err := s.exec(
|
q string
|
||||||
"INSERT IGNORE INTO stream (hash, sd_blob_id, last_accessed_at) VALUES ("+qt.Qs(len(args))+")",
|
args []interface{}
|
||||||
args...,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if s.TrackAccess == TrackAccessStreams {
|
||||||
|
args = []interface{}{hash, sdBlobID, time.Now()}
|
||||||
|
q = "INSERT IGNORE INTO stream (hash, sd_blob_id, last_accessed_at) VALUES (" + qt.Qs(len(args)) + ")"
|
||||||
|
} else {
|
||||||
|
args = []interface{}{hash, sdBlobID}
|
||||||
|
q = "INSERT IGNORE INTO stream (hash, sd_blob_id) VALUES (" + qt.Qs(len(args)) + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
streamID, err := s.exec(q, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.Err(err)
|
return 0, errors.Err(err)
|
||||||
}
|
}
|
||||||
|
@ -119,8 +241,8 @@ func (s *SQL) insertStream(hash string, sdBlobID int64) (int64, error) {
|
||||||
return 0, errors.Err("stream ID is 0 even after INSERTing and SELECTing")
|
return 0, errors.Err("stream ID is 0 even after INSERTing and SELECTing")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TrackAccessTime {
|
if s.TrackAccess == TrackAccessStreams {
|
||||||
err := s.touch([]uint64{uint64(streamID)})
|
err := s.touchStreams([]uint64{uint64(streamID)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, errors.Err(err)
|
return 0, errors.Err(err)
|
||||||
}
|
}
|
||||||
|
@ -130,8 +252,8 @@ func (s *SQL) insertStream(hash string, sdBlobID int64) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasBlob checks if the database contains the blob information.
|
// HasBlob checks if the database contains the blob information.
|
||||||
func (s *SQL) HasBlob(hash string) (bool, error) {
|
func (s *SQL) HasBlob(hash string, touch bool) (bool, error) {
|
||||||
exists, err := s.HasBlobs([]string{hash})
|
exists, err := s.HasBlobs([]string{hash}, touch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -139,13 +261,39 @@ func (s *SQL) HasBlob(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasBlobs checks if the database contains the set of blobs and returns a bool map.
|
// HasBlobs checks if the database contains the set of blobs and returns a bool map.
|
||||||
func (s *SQL) HasBlobs(hashes []string) (map[string]bool, error) {
|
func (s *SQL) HasBlobs(hashes []string, touch bool) (map[string]bool, error) {
|
||||||
exists, streamsNeedingTouch, err := s.hasBlobs(hashes)
|
exists, idsNeedingTouch, err := s.hasBlobs(hashes)
|
||||||
s.touch(streamsNeedingTouch)
|
|
||||||
|
if touch {
|
||||||
|
if s.TrackAccess == TrackAccessBlobs {
|
||||||
|
_ = s.touchBlobs(idsNeedingTouch)
|
||||||
|
} else if s.TrackAccess == TrackAccessStreams {
|
||||||
|
_ = s.touchStreams(idsNeedingTouch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return exists, err
|
return exists, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQL) touch(streamIDs []uint64) error {
|
func (s *SQL) touchBlobs(blobIDs []uint64) error {
|
||||||
|
if len(blobIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "UPDATE blob_ SET last_accessed_at = ? WHERE id IN (" + qt.Qs(len(blobIDs)) + ")"
|
||||||
|
args := make([]interface{}, len(blobIDs)+1)
|
||||||
|
args[0] = time.Now()
|
||||||
|
for i := range blobIDs {
|
||||||
|
args[i+1] = blobIDs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
_, err := s.exec(query, args...)
|
||||||
|
log.Debugf("touched %d blobs and took %s", len(blobIDs), time.Since(startTime))
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) touchStreams(streamIDs []uint64) error {
|
||||||
if len(streamIDs) == 0 {
|
if len(streamIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -159,7 +307,7 @@ func (s *SQL) touch(streamIDs []uint64) error {
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
_, err := s.exec(query, args...)
|
_, err := s.exec(query, args...)
|
||||||
log.Debugf("stream access query touched %d streams and took %s", len(streamIDs), time.Since(startTime))
|
log.Debugf("touched %d streams and took %s", len(streamIDs), time.Since(startTime))
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,14 +318,15 @@ func (s *SQL) hasBlobs(hashes []string) (map[string]bool, []uint64, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hash string
|
hash string
|
||||||
streamID uint64
|
blobID uint64
|
||||||
|
streamID null.Uint64
|
||||||
lastAccessedAt null.Time
|
lastAccessedAt null.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
var needsTouch []uint64
|
var needsTouch []uint64
|
||||||
exists := make(map[string]bool)
|
exists := make(map[string]bool)
|
||||||
|
|
||||||
touchDeadline := time.Now().AddDate(0, 0, -1) // touch blob if last accessed before this time
|
touchDeadline := time.Now().Add(-6 * time.Hour) // touch blob if last accessed before this time
|
||||||
maxBatchSize := 10000
|
maxBatchSize := 10000
|
||||||
doneIndex := 0
|
doneIndex := 0
|
||||||
|
|
||||||
|
@ -189,20 +338,29 @@ func (s *SQL) hasBlobs(hashes []string) (map[string]bool, []uint64, error) {
|
||||||
log.Debugf("getting hashes[%d:%d] of %d", doneIndex, sliceEnd, len(hashes))
|
log.Debugf("getting hashes[%d:%d] of %d", doneIndex, sliceEnd, len(hashes))
|
||||||
batch := hashes[doneIndex:sliceEnd]
|
batch := hashes[doneIndex:sliceEnd]
|
||||||
|
|
||||||
// TODO: this query doesn't work for SD blobs, which are not in the stream_blob table
|
var query string
|
||||||
|
if s.TrackAccess == TrackAccessBlobs {
|
||||||
query := `SELECT b.hash, s.id, s.last_accessed_at
|
query = `SELECT b.hash, b.id, NULL, b.last_accessed_at
|
||||||
|
FROM blob_ b
|
||||||
|
WHERE b.is_stored = 1 and b.hash IN (` + qt.Qs(len(batch)) + `)`
|
||||||
|
} else if s.TrackAccess == TrackAccessStreams {
|
||||||
|
query = `SELECT b.hash, b.id, s.id, s.last_accessed_at
|
||||||
FROM blob_ b
|
FROM blob_ b
|
||||||
LEFT JOIN stream_blob sb ON b.id = sb.blob_id
|
LEFT JOIN stream_blob sb ON b.id = sb.blob_id
|
||||||
INNER JOIN stream s on (sb.stream_id = s.id or s.sd_blob_id = b.id)
|
INNER JOIN stream s on (sb.stream_id = s.id or s.sd_blob_id = b.id)
|
||||||
WHERE b.is_stored = ? and b.hash IN (` + qt.Qs(len(batch)) + `)`
|
WHERE b.is_stored = 1 and b.hash IN (` + qt.Qs(len(batch)) + `)`
|
||||||
args := make([]interface{}, len(batch)+1)
|
} else {
|
||||||
args[0] = true
|
query = `SELECT b.hash, b.id, NULL, NULL
|
||||||
for i := range batch {
|
FROM blob_ b
|
||||||
args[i+1] = batch[i]
|
WHERE b.is_stored = 1 and b.hash IN (` + qt.Qs(len(batch)) + `)`
|
||||||
}
|
}
|
||||||
|
|
||||||
logQuery(query, args...)
|
args := make([]interface{}, len(batch))
|
||||||
|
for i := range batch {
|
||||||
|
args[i] = batch[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logQuery(query, args...)
|
||||||
|
|
||||||
err := func() error {
|
err := func() error {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
@ -214,13 +372,17 @@ WHERE b.is_stored = ? and b.hash IN (` + qt.Qs(len(batch)) + `)`
|
||||||
defer closeRows(rows)
|
defer closeRows(rows)
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
err := rows.Scan(&hash, &streamID, &lastAccessedAt)
|
err := rows.Scan(&hash, &blobID, &streamID, &lastAccessedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
exists[hash] = true
|
exists[hash] = true
|
||||||
if s.TrackAccessTime && (!lastAccessedAt.Valid || lastAccessedAt.Time.Before(touchDeadline)) {
|
if !lastAccessedAt.Valid || lastAccessedAt.Time.Before(touchDeadline) {
|
||||||
needsTouch = append(needsTouch, streamID)
|
if s.TrackAccess == TrackAccessBlobs {
|
||||||
|
needsTouch = append(needsTouch, blobID)
|
||||||
|
} else if s.TrackAccess == TrackAccessStreams && !streamID.IsZero() {
|
||||||
|
needsTouch = append(needsTouch, streamID.Uint64)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,8 +402,14 @@ WHERE b.is_stored = ? and b.hash IN (` + qt.Qs(len(batch)) + `)`
|
||||||
return exists, needsTouch, nil
|
return exists, needsTouch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete will remove the blob from the db
|
// Delete will remove (or soft-delete) the blob from the db
|
||||||
|
// NOTE: If SoftDelete is enabled, streams will never be deleted
|
||||||
func (s *SQL) Delete(hash string) error {
|
func (s *SQL) Delete(hash string) error {
|
||||||
|
if s.SoftDelete {
|
||||||
|
_, err := s.exec("UPDATE blob_ SET is_stored = 0 WHERE hash = ?", hash)
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
_, err := s.exec("DELETE FROM stream WHERE sd_blob_id = (SELECT id FROM blob_ WHERE hash = ?)", hash)
|
_, err := s.exec("DELETE FROM stream WHERE sd_blob_id = (SELECT id FROM blob_ WHERE hash = ?)", hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
|
@ -251,11 +419,59 @@ func (s *SQL) Delete(hash string) error {
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//LeastRecentlyAccessedHashes gets the least recently accessed blobs
|
||||||
|
func (s *SQL) LeastRecentlyAccessedHashes(maxBlobs int) ([]string, error) {
|
||||||
|
if s.conn == nil {
|
||||||
|
return nil, errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.TrackAccess != TrackAccessBlobs {
|
||||||
|
return nil, errors.Err("blob access tracking is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "SELECT hash from blob_ where is_stored = 1 order by last_accessed_at limit ?"
|
||||||
|
s.logQuery(query, maxBlobs)
|
||||||
|
|
||||||
|
rows, err := s.conn.Query(query, maxBlobs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Err(err)
|
||||||
|
}
|
||||||
|
defer closeRows(rows)
|
||||||
|
|
||||||
|
blobs := make([]string, 0, maxBlobs)
|
||||||
|
for rows.Next() {
|
||||||
|
var hash string
|
||||||
|
err := rows.Scan(&hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Err(err)
|
||||||
|
}
|
||||||
|
blobs = append(blobs, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return blobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) Count() (int, error) {
|
||||||
|
if s.conn == nil {
|
||||||
|
return 0, errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "SELECT count(id) from blob_"
|
||||||
|
if s.SoftDelete {
|
||||||
|
query += " where is_stored = 1"
|
||||||
|
}
|
||||||
|
s.logQuery(query)
|
||||||
|
|
||||||
|
var count int
|
||||||
|
err := s.conn.QueryRow(query).Scan(&count)
|
||||||
|
return count, errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Block will mark a blob as blocked
|
// Block will mark a blob as blocked
|
||||||
func (s *SQL) Block(hash string) error {
|
func (s *SQL) Block(hash string) error {
|
||||||
query := "INSERT IGNORE INTO blocked SET hash = ?"
|
query := "INSERT IGNORE INTO blocked SET hash = ?"
|
||||||
args := []interface{}{hash}
|
args := []interface{}{hash}
|
||||||
logQuery(query, args...)
|
s.logQuery(query, args...)
|
||||||
_, err := s.conn.Exec(query, args...)
|
_, err := s.conn.Exec(query, args...)
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
|
@ -263,7 +479,7 @@ func (s *SQL) Block(hash string) error {
|
||||||
// GetBlocked will return a list of blocked hashes
|
// GetBlocked will return a list of blocked hashes
|
||||||
func (s *SQL) GetBlocked() (map[string]bool, error) {
|
func (s *SQL) GetBlocked() (map[string]bool, error) {
|
||||||
query := "SELECT hash FROM blocked"
|
query := "SELECT hash FROM blocked"
|
||||||
logQuery(query)
|
s.logQuery(query)
|
||||||
rows, err := s.conn.Query(query)
|
rows, err := s.conn.Query(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, errors.Err(err)
|
||||||
|
@ -306,7 +522,7 @@ func (s *SQL) MissingBlobsForKnownStream(sdHash string) ([]string, error) {
|
||||||
`
|
`
|
||||||
args := []interface{}{sdHash}
|
args := []interface{}{sdHash}
|
||||||
|
|
||||||
logQuery(query, args...)
|
s.logQuery(query, args...)
|
||||||
|
|
||||||
rows, err := s.conn.Query(query, args...)
|
rows, err := s.conn.Query(query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -385,7 +601,7 @@ func (s *SQL) GetHashRange() (string, string, error) {
|
||||||
|
|
||||||
query := "SELECT MIN(hash), MAX(hash) from blob_"
|
query := "SELECT MIN(hash), MAX(hash) from blob_"
|
||||||
|
|
||||||
logQuery(query)
|
s.logQuery(query)
|
||||||
|
|
||||||
err := s.conn.QueryRow(query).Scan(&min, &max)
|
err := s.conn.QueryRow(query).Scan(&min, &max)
|
||||||
return min, max, err
|
return min, max, err
|
||||||
|
@ -409,7 +625,7 @@ func (s *SQL) GetStoredHashesInRange(ctx context.Context, start, end bits.Bitmap
|
||||||
query := "SELECT hash FROM blob_ WHERE hash >= ? AND hash <= ? AND is_stored = 1"
|
query := "SELECT hash FROM blob_ WHERE hash >= ? AND hash <= ? AND is_stored = 1"
|
||||||
args := []interface{}{start.Hex(), end.Hex()}
|
args := []interface{}{start.Hex(), end.Hex()}
|
||||||
|
|
||||||
logQuery(query, args...)
|
s.logQuery(query, args...)
|
||||||
|
|
||||||
rows, err := s.conn.Query(query, args...)
|
rows, err := s.conn.Query(query, args...)
|
||||||
defer closeRows(rows)
|
defer closeRows(rows)
|
||||||
|
@ -490,7 +706,7 @@ func closeRows(rows *sql.Rows) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQL) exec(query string, args ...interface{}) (int64, error) {
|
func (s *SQL) exec(query string, args ...interface{}) (int64, error) {
|
||||||
logQuery(query, args...)
|
s.logQuery(query, args...)
|
||||||
attempt, maxAttempts := 0, 3
|
attempt, maxAttempts := 0, 3
|
||||||
Retry:
|
Retry:
|
||||||
attempt++
|
attempt++
|
||||||
|
@ -528,8 +744,11 @@ CREATE TABLE blob_ (
|
||||||
hash char(96) NOT NULL,
|
hash char(96) NOT NULL,
|
||||||
is_stored TINYINT(1) NOT NULL DEFAULT 0,
|
is_stored TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
length bigint(20) unsigned DEFAULT NULL,
|
length bigint(20) unsigned DEFAULT NULL,
|
||||||
|
last_accessed_at TIMESTAMP NULL DEFAULT NULL,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
UNIQUE KEY blob_hash_idx (hash)
|
UNIQUE KEY blob_hash_idx (hash),
|
||||||
|
KEY `blob_last_accessed_idx` (`last_accessed_at`),
|
||||||
|
KEY `is_stored_idx` (`is_stored`)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE stream (
|
CREATE TABLE stream (
|
||||||
|
|
55
go.mod
55
go.mod
|
@ -2,45 +2,50 @@ module github.com/lbryio/reflector.go
|
||||||
|
|
||||||
replace github.com/btcsuite/btcd => github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19
|
replace github.com/btcsuite/btcd => github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19
|
||||||
|
|
||||||
//replace github.com/lbryio/lbry.go/v2 => ../lbry.go
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
|
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.16.11
|
github.com/aws/aws-sdk-go v1.27.0
|
||||||
|
github.com/bluele/gcache v0.0.2
|
||||||
|
github.com/brk0v/directio v0.0.0-20190225130936-69406e757cf7
|
||||||
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3
|
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
|
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
|
||||||
|
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/go-sql-driver/mysql v1.4.1
|
github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db
|
||||||
github.com/golang/protobuf v1.4.2
|
github.com/gin-gonic/gin v1.7.1
|
||||||
github.com/google/btree v1.0.0 // indirect
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/google/gops v0.3.7
|
github.com/gogo/protobuf v1.2.1
|
||||||
github.com/gorilla/mux v1.7.4
|
github.com/golang/protobuf v1.5.2
|
||||||
|
github.com/google/go-cmp v0.5.6 // indirect
|
||||||
|
github.com/google/gops v0.3.18
|
||||||
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/hashicorp/go-msgpack v0.5.5 // indirect
|
github.com/hashicorp/go-msgpack v0.5.5 // indirect
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/hashicorp/memberlist v0.1.4 // indirect
|
github.com/hashicorp/serf v0.9.5
|
||||||
github.com/hashicorp/serf v0.8.2
|
|
||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
|
||||||
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07
|
github.com/johntdyer/slackrus v0.0.0-20210521205746-42486fb4c48c
|
||||||
github.com/karrick/godirwalk v1.16.1
|
github.com/karrick/godirwalk v1.16.1
|
||||||
github.com/lbryio/chainquery v1.9.0
|
github.com/lbryio/chainquery v1.9.0
|
||||||
github.com/lbryio/lbry.go v1.1.2 // indirect
|
github.com/lbryio/lbry.go/v2 v2.7.2-0.20210416195322-6516df1418e3
|
||||||
github.com/lbryio/lbry.go/v2 v2.7.2-0.20210402181800-ec4d2cb37ce9
|
|
||||||
github.com/lbryio/types v0.0.0-20201019032447-f0b4476ef386
|
github.com/lbryio/types v0.0.0-20201019032447-f0b4476ef386
|
||||||
github.com/lucas-clemente/quic-go v0.18.1
|
github.com/lucas-clemente/quic-go v0.20.1
|
||||||
github.com/phayes/freeport v0.0.0-20171002185219-e27662a4a9d6
|
github.com/phayes/freeport v0.0.0-20171002185219-e27662a4a9d6
|
||||||
github.com/prometheus/client_golang v0.9.2
|
github.com/prometheus/client_golang v1.10.0
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sergi/go-diff v1.2.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/afero v1.4.1 // indirect
|
github.com/spf13/afero v1.4.1 // indirect
|
||||||
github.com/spf13/cast v1.3.0
|
github.com/spf13/cast v1.3.0
|
||||||
github.com/spf13/cobra v0.0.3
|
github.com/spf13/cobra v1.1.3
|
||||||
github.com/spf13/pflag v1.0.3 // indirect
|
github.com/spf13/viper v1.7.1 // indirect
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/volatiletech/null v8.0.0+incompatible
|
github.com/volatiletech/null v8.0.0+incompatible
|
||||||
go.uber.org/atomic v1.5.1
|
go.uber.org/atomic v1.7.0
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4 // indirect
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
||||||
google.golang.org/appengine v1.6.2 // indirect
|
golang.org/x/text v0.3.6 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||||
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.15
|
go 1.16
|
||||||
|
|
533
go.sum
533
go.sum
|
@ -2,30 +2,66 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
github.com/StackExchange/wmi v0.0.0-20170410192909-ea383cf3ba6e/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
|
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
|
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
|
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
||||||
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/aws/aws-sdk-go v1.16.11 h1:g/c7gJeVyHoXCxM2fddS85bPGVkBF8s2q8t3fyElegc=
|
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||||
github.com/aws/aws-sdk-go v1.16.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||||
|
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||||
|
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||||
|
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
|
github.com/brk0v/directio v0.0.0-20190225130936-69406e757cf7 h1:7gNKWnX6OF+ERiXVw4I9RsHhZ52aumXdFE07nEx5v20=
|
||||||
|
github.com/brk0v/directio v0.0.0-20190225130936-69406e757cf7/go.mod h1:M/KA3XJG5PJaApPiv4gWNsgcSJquOQTqumZNLyYE0KM=
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||||
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||||
|
@ -39,77 +75,156 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3
|
||||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
|
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2 h1:t8KYCwSKsOEZBFELI4Pn/phbp38iJ1RRAkDFNin1aak=
|
||||||
|
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||||
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||||
|
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||||
|
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||||
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
|
github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db h1:oZ4U9IqO8NS+61OmGTBi8vopzqTRxwQeogyBHdrhjbc=
|
||||||
|
github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db/go.mod h1:Pk7/9x6tyChFTkahDvLBQMlvdsWvfC+yU8HTT5VD314=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
|
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||||
|
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.7.1 h1:qC89GU3p8TvKWMAVhEpmpB2CIb1hnqt2UdKZaP93mS8=
|
||||||
|
github.com/gin-gonic/gin v1.7.1/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
|
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
|
||||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-ini/ini v1.48.0 h1:TvO60hO/2xgaaTWp2P0wUe4CFxwdMzfbkv3+343Xzqw=
|
github.com/go-ini/ini v1.48.0 h1:TvO60hO/2xgaaTWp2P0wUe4CFxwdMzfbkv3+343Xzqw=
|
||||||
github.com/go-ini/ini v1.48.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.48.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||||
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
||||||
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU=
|
||||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
|
||||||
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/gops v0.3.7 h1:KtVAagOM0FIq+02DiQrKBTnLhYpWBMowaufcj+W1Exw=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gops v0.3.7/go.mod h1:bj0cwMmX1X4XIJFTjR99R5sCxNssNJ8HebFNvoQlmgY=
|
github.com/google/gops v0.3.18 h1:my259V+172PVFmduS2RAsq4FKH+HjKqdh7pLr17Ot8c=
|
||||||
|
github.com/google/gops v0.3.18/go.mod h1:Pfp8hWGIFdV/7rY9/O/U5WgdjYQXf/GiEK4NVuVd2ZE=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f h1:TyqzGm2z1h3AGhjOoRYyeLcW4WlW81MDQkWa+rx/000=
|
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f h1:TyqzGm2z1h3AGhjOoRYyeLcW4WlW81MDQkWa+rx/000=
|
||||||
|
@ -117,52 +232,73 @@ github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f/go.mod h1:wJfORR
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/rpc v1.1.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
|
github.com/gorilla/rpc v1.1.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
|
||||||
github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
|
github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
|
||||||
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
|
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
|
||||||
github.com/gorilla/schema v1.0.2/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
github.com/gorilla/schema v1.0.2/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||||
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
|
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||||
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
|
|
||||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs=
|
github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g=
|
||||||
github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||||
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
|
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM=
|
||||||
|
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
|
||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||||
|
@ -171,26 +307,35 @@ github.com/jmoiron/sqlx v0.0.0-20170430194603-d9bd385d68c0/go.mod h1:IiEW3SEiiEr
|
||||||
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3w6+IPyMit07RE42MtTWNd77sN2cHngQ=
|
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22 h1:jKUP9TQ0c7X3w6+IPyMit07RE42MtTWNd77sN2cHngQ=
|
||||||
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22/go.mod h1:u0Jo4f2dNlTJeeOywkM6bLwxq6gC3pZ9rEFHn3AhTdk=
|
github.com/johntdyer/slack-go v0.0.0-20180213144715-95fac1160b22/go.mod h1:u0Jo4f2dNlTJeeOywkM6bLwxq6gC3pZ9rEFHn3AhTdk=
|
||||||
github.com/johntdyer/slackrus v0.0.0-20170926115001-3992f319fd0a/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
|
github.com/johntdyer/slackrus v0.0.0-20170926115001-3992f319fd0a/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
|
||||||
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07 h1:+kBG/8rjCa6vxJZbUjAiE4MQmBEBYc8nLEb51frnvBY=
|
github.com/johntdyer/slackrus v0.0.0-20210521205746-42486fb4c48c h1:4eD0DM7IEOQI5egyzKdPXncs61BxMtYwwzJPCRQFU0Y=
|
||||||
github.com/johntdyer/slackrus v0.0.0-20180518184837-f7aae3243a07/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
|
github.com/johntdyer/slackrus v0.0.0-20210521205746-42486fb4c48c/go.mod h1:j1kV/8f3jowErEq4XyeypkCdvg5EeHkf0YCKCcq5Ybo=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
|
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||||
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
|
github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
|
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
|
||||||
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
|
||||||
github.com/keybase/go-ps v0.0.0-20161005175911-668c8856d999/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
|
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
@ -200,13 +345,10 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lbryio/chainquery v1.9.0 h1:NfBZ3eKYwD3PqXU/vt+2tF3ox3WUWoW4J5YdEQ0rxw0=
|
github.com/lbryio/chainquery v1.9.0 h1:NfBZ3eKYwD3PqXU/vt+2tF3ox3WUWoW4J5YdEQ0rxw0=
|
||||||
github.com/lbryio/chainquery v1.9.0/go.mod h1:7G8l7jNtANS1I7fQOvtzbiHsv6qKVmN4codXHc3C4kk=
|
github.com/lbryio/chainquery v1.9.0/go.mod h1:7G8l7jNtANS1I7fQOvtzbiHsv6qKVmN4codXHc3C4kk=
|
||||||
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c/go.mod h1:muH7wpUqE8hRA3OrYYosw9+Sl681BF9cwcjzE+OCNK8=
|
github.com/lbryio/errors.go v0.0.0-20180223142025-ad03d3cc6a5c/go.mod h1:muH7wpUqE8hRA3OrYYosw9+Sl681BF9cwcjzE+OCNK8=
|
||||||
|
github.com/lbryio/lbry.go v1.1.1-0.20190825202001-8fa28d3d656f h1:ovd2wPXzkT80vdP/FX5xcQeXu0i9RAo80SQ6qIsrAjM=
|
||||||
github.com/lbryio/lbry.go v1.1.1-0.20190825202001-8fa28d3d656f/go.mod h1:JtyI30bU51rm0LZ/po3mQuzf++14OWb6kR/6mMRAmKU=
|
github.com/lbryio/lbry.go v1.1.1-0.20190825202001-8fa28d3d656f/go.mod h1:JtyI30bU51rm0LZ/po3mQuzf++14OWb6kR/6mMRAmKU=
|
||||||
github.com/lbryio/lbry.go v1.1.2 h1:Dyxc+glT/rVWJwHfIf7vjlPYYbjzrQz5ARmJd5Hp69c=
|
github.com/lbryio/lbry.go/v2 v2.7.2-0.20210416195322-6516df1418e3 h1:hkVViG8qbOKJMf/M6mN+Hrtns4j55IU2dRavpJZWbxw=
|
||||||
github.com/lbryio/lbry.go v1.1.2/go.mod h1:JtyI30bU51rm0LZ/po3mQuzf++14OWb6kR/6mMRAmKU=
|
github.com/lbryio/lbry.go/v2 v2.7.2-0.20210416195322-6516df1418e3/go.mod h1:I1q8W9fwU+t0IWNiprPgE1SorWQwcO6ser0nzP3L5Pk=
|
||||||
github.com/lbryio/lbry.go/v2 v2.6.1-0.20200901175808-73382bb02128 h1:VL209c+AGKixMFpxT+TsOAPzBPuoUzyjXf47iNe7OzY=
|
|
||||||
github.com/lbryio/lbry.go/v2 v2.6.1-0.20200901175808-73382bb02128/go.mod h1:RqOv4V5eWY/JGmduCPcQVdN19SEYnNY3SuF+arTKIU4=
|
|
||||||
github.com/lbryio/lbry.go/v2 v2.7.2-0.20210402181800-ec4d2cb37ce9 h1:jjIfkL4LdLL/dngZixJTT5NneZbE1LPoWTpDNryuW9Q=
|
|
||||||
github.com/lbryio/lbry.go/v2 v2.7.2-0.20210402181800-ec4d2cb37ce9/go.mod h1:I1q8W9fwU+t0IWNiprPgE1SorWQwcO6ser0nzP3L5Pk=
|
|
||||||
github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19 h1:/zWD8dVIl7bV1TdJWqPqy9tpqixzX2Qxgit48h3hQcY=
|
github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19 h1:/zWD8dVIl7bV1TdJWqPqy9tpqixzX2Qxgit48h3hQcY=
|
||||||
github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
github.com/lbryio/lbrycrd.go v0.0.0-20200203050410-e1076f12bf19/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
github.com/lbryio/lbryschema.go v0.0.0-20190428231007-c54836bca002/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
|
github.com/lbryio/lbryschema.go v0.0.0-20190428231007-c54836bca002/go.mod h1:dAzPCBj3CKKWBGYBZxK6tKBP5SCgY2tqd9SnQd/OyKo=
|
||||||
|
@ -215,40 +357,72 @@ github.com/lbryio/ozzo-validation v0.0.0-20170323141101-d1008ad1fd04/go.mod h1:f
|
||||||
github.com/lbryio/types v0.0.0-20190422033210-321fb2abda9c/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE=
|
github.com/lbryio/types v0.0.0-20190422033210-321fb2abda9c/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE=
|
||||||
github.com/lbryio/types v0.0.0-20201019032447-f0b4476ef386 h1:JOQkGpeCM9FWkEHRx+kRPqySPCXElNW1em1++7tVS4M=
|
github.com/lbryio/types v0.0.0-20201019032447-f0b4476ef386 h1:JOQkGpeCM9FWkEHRx+kRPqySPCXElNW1em1++7tVS4M=
|
||||||
github.com/lbryio/types v0.0.0-20201019032447-f0b4476ef386/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE=
|
github.com/lbryio/types v0.0.0-20201019032447-f0b4476ef386/go.mod h1:CG3wsDv5BiVYQd5i1Jp7wGsaVyjZTJshqXeWMVKsISE=
|
||||||
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys=
|
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||||
github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg=
|
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||||
|
github.com/lucas-clemente/quic-go v0.20.1 h1:hb5m76V8QS/8Nw/suHvXqo3BMHAozvIkcnzpJdpanSk=
|
||||||
|
github.com/lucas-clemente/quic-go v0.20.1/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
|
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
|
||||||
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
|
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
|
||||||
|
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||||
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5 h1:mG83tLXWSRdcXMWfkoumVwhcCbf3jHF9QKv/m37BkM0=
|
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5 h1:mG83tLXWSRdcXMWfkoumVwhcCbf3jHF9QKv/m37BkM0=
|
||||||
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5/go.mod h1:H0aPCWffGOaDcjkw1iB7W9DVLp6GXmfcJY/7YZCWPA4=
|
github.com/lyoshenka/bencode v0.0.0-20180323155644-b7abd7672df5/go.mod h1:H0aPCWffGOaDcjkw1iB7W9DVLp6GXmfcJY/7YZCWPA4=
|
||||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||||
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/marten-seemann/qpack v0.2.0 h1:/r1rhZoOmgxVKBqPNnYilZBDEyw+6OUHCbBzA5jc2y0=
|
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
||||||
github.com/marten-seemann/qpack v0.2.0/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||||
github.com/marten-seemann/qtls v0.10.0 h1:ECsuYUKalRL240rRD4Ri33ISb7kAQ3qGDlrrl55b2pc=
|
github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A=
|
||||||
github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs=
|
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.0 h1:i/YPXVxz8q9umso/5y474CNcHmTpA+5DH+mFPjx6PZg=
|
github.com/marten-seemann/qtls-go1-16 v0.1.3 h1:XEZ1xGorVy9u+lJq+WXNE+hiqRYLNvJGYmwfwKQN2gU=
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.0/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
|
||||||
|
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||||
|
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||||
|
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||||
|
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||||
|
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
|
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM=
|
github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM=
|
||||||
|
@ -256,6 +430,10 @@ github.com/nlopes/slack v0.6.0 h1:jt0jxVQGhssx1Ib7naAOZEZcGdtIhTzkP0nopK0AsRA=
|
||||||
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
|
github.com/nlopes/slack v0.6.0/go.mod h1:JzQ9m3PMAqcpeCam7UaHSuBuupz7CmpjehYMayT6YOk=
|
||||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||||
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
@ -267,46 +445,95 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
|
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||||
|
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||||
|
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
|
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
|
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||||
|
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||||
github.com/phayes/freeport v0.0.0-20171002185219-e27662a4a9d6 h1:2bae6N0SZjgzk+Zg8mzTsfmpwHXY9VBNp9UdjhaElA0=
|
github.com/phayes/freeport v0.0.0-20171002185219-e27662a4a9d6 h1:2bae6N0SZjgzk+Zg8mzTsfmpwHXY9VBNp9UdjhaElA0=
|
||||||
github.com/phayes/freeport v0.0.0-20171002185219-e27662a4a9d6/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
github.com/phayes/freeport v0.0.0-20171002185219-e27662a4a9d6/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||||
|
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
|
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||||
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
|
github.com/prometheus/client_golang v1.10.0 h1:/o0BDeWzLWXNZ+4q5gXltUvaMpJqckTa+jTNoB+z4cg=
|
||||||
|
github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||||
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||||
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
|
github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y=
|
||||||
|
github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
|
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||||
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rubenv/sql-migrate v0.0.0-20170330050058-38004e7a77f2/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY=
|
github.com/rubenv/sql-migrate v0.0.0-20170330050058-38004e7a77f2/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561/go.mod h1:lvjGftC8oe7XPtyrOidaMi0rp5B9+XY/ZRUynGnuaxQ=
|
github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561/go.mod h1:lvjGftC8oe7XPtyrOidaMi0rp5B9+XY/ZRUynGnuaxQ=
|
||||||
github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77 h1:Msb6XRY62jQOueNNlB5LGin1rljK7c49NLniGwJG2bg=
|
github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77 h1:Msb6XRY62jQOueNNlB5LGin1rljK7c49NLniGwJG2bg=
|
||||||
github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
|
github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
|
||||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||||
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/sfreiberg/gotwilio v0.0.0-20180612161623-8fb7259ba8bf/go.mod h1:60PiR0SAnAcYSiwrXB6BaxeqHdXMf172toCosHfV+Yk=
|
github.com/sfreiberg/gotwilio v0.0.0-20180612161623-8fb7259ba8bf/go.mod h1:60PiR0SAnAcYSiwrXB6BaxeqHdXMf172toCosHfV+Yk=
|
||||||
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
|
||||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
|
||||||
github.com/shopspring/decimal v0.0.0-20180607144847-19e3cb6c2930/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180607144847-19e3cb6c2930/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 h1:Gojs/hac/DoYEM7WEICT45+hNWczIeuL5D21e5/HPAw=
|
github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 h1:Gojs/hac/DoYEM7WEICT45+hNWczIeuL5D21e5/HPAw=
|
||||||
|
@ -331,44 +558,73 @@ github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b
|
||||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
|
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
|
||||||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
|
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
|
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/afero v1.4.1 h1:asw9sl74539yqavKaglDM5hFpdJVK0Y5Dr/JOgQ89nQ=
|
github.com/spf13/afero v1.4.1 h1:asw9sl74539yqavKaglDM5hFpdJVK0Y5Dr/JOgQ89nQ=
|
||||||
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig=
|
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
||||||
|
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||||
|
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
|
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
||||||
|
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||||
|
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
|
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||||
|
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
|
||||||
|
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||||
|
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d h1:gI4/tqP6lCY5k6Sg+4k9qSoBXmPwG+xXgMpK7jivD4M=
|
github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d h1:gI4/tqP6lCY5k6Sg+4k9qSoBXmPwG+xXgMpK7jivD4M=
|
||||||
|
@ -377,15 +633,29 @@ github.com/volatiletech/null v8.0.0+incompatible h1:7wP8m5d/gZ6kW/9GnrLtMCRre2dl
|
||||||
github.com/volatiletech/null v8.0.0+incompatible/go.mod h1:0wD98JzdqB+rLyZ70fN05VDbXbafIb0KU0MdVhCzmOQ=
|
github.com/volatiletech/null v8.0.0+incompatible/go.mod h1:0wD98JzdqB+rLyZ70fN05VDbXbafIb0KU0MdVhCzmOQ=
|
||||||
github.com/volatiletech/sqlboiler v3.4.0+incompatible h1:saQ6WxZ9wEJp33q3w/DHs7an7SYi1H7Yzf4/moxCbJU=
|
github.com/volatiletech/sqlboiler v3.4.0+incompatible h1:saQ6WxZ9wEJp33q3w/DHs7an7SYi1H7Yzf4/moxCbJU=
|
||||||
github.com/volatiletech/sqlboiler v3.4.0+incompatible/go.mod h1:jLfDkkHWPbS2cWRLkyC20vQWaIQsASEY7gM7zSo11Yw=
|
github.com/volatiletech/sqlboiler v3.4.0+incompatible/go.mod h1:jLfDkkHWPbS2cWRLkyC20vQWaIQsASEY7gM7zSo11Yw=
|
||||||
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
|
github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg=
|
||||||
github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
|
github.com/ybbus/jsonrpc v0.0.0-20180411222309-2a548b7d822d/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
@ -394,23 +664,37 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -419,130 +703,214 @@ golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73r
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191009170203-06d7bd2c5f4f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4 h1:Toz2IK7k8rbltAXwNAxKcn9OzqyNfMUhUNjz3sL0NMk=
|
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
|
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.2 h1:j8RI1yW0SkI+paT6uGwMlrMI/6zwYA6/CFil8rxOzGI=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20181004005441-af9cb2a35e7f/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||||
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||||
gopkg.in/gorp.v1 v1.7.1/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
gopkg.in/gorp.v1 v1.7.1/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.48.0 h1:URjZc+8ugRY5mL5uUeQH/a63JcHwdX9xZaWvmNWD7z8=
|
|
||||||
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||||
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 h1:FpCr9V8wuOei4BAen+93HtVJ+XSi+KPbaPKm0Vj5R64=
|
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79 h1:FpCr9V8wuOei4BAen+93HtVJ+XSi+KPbaPKm0Vj5R64=
|
||||||
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79/go.mod h1:gWkaRU7CoXpezCBWfWjm3999QqS+1pYPXGbqQCTMzo8=
|
gopkg.in/nullbio/null.v6 v6.0.0-20161116030900-40264a2e6b79/go.mod h1:gWkaRU7CoXpezCBWfWjm3999QqS+1pYPXGbqQCTMzo8=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
|
@ -551,9 +919,12 @@ grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJd
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
rsc.io/goversion v1.0.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
|
||||||
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
|
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
|
|
|
@ -60,6 +60,7 @@ func (s *Server) Shutdown() {
|
||||||
const (
|
const (
|
||||||
ns = "reflector"
|
ns = "reflector"
|
||||||
subsystemCache = "cache"
|
subsystemCache = "cache"
|
||||||
|
subsystemITTT = "ittt"
|
||||||
|
|
||||||
labelDirection = "direction"
|
labelDirection = "direction"
|
||||||
labelErrorType = "error_type"
|
labelErrorType = "error_type"
|
||||||
|
@ -117,6 +118,11 @@ var (
|
||||||
Name: "http3_blob_download_total",
|
Name: "http3_blob_download_total",
|
||||||
Help: "Total number of blobs downloaded from reflector through QUIC protocol",
|
Help: "Total number of blobs downloaded from reflector through QUIC protocol",
|
||||||
})
|
})
|
||||||
|
HttpDownloadCount = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Name: "http_blob_download_total",
|
||||||
|
Help: "Total number of blobs downloaded from reflector through HTTP protocol",
|
||||||
|
})
|
||||||
|
|
||||||
CacheHitCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
CacheHitCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
|
@ -124,6 +130,18 @@ var (
|
||||||
Name: "hit_total",
|
Name: "hit_total",
|
||||||
Help: "Total number of blobs retrieved from the cache storage",
|
Help: "Total number of blobs retrieved from the cache storage",
|
||||||
}, []string{LabelCacheType, LabelComponent})
|
}, []string{LabelCacheType, LabelComponent})
|
||||||
|
ThisHitCount = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Subsystem: subsystemITTT,
|
||||||
|
Name: "this_hit_total",
|
||||||
|
Help: "Total number of blobs retrieved from the this storage",
|
||||||
|
})
|
||||||
|
ThatHitCount = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Subsystem: subsystemITTT,
|
||||||
|
Name: "that_hit_total",
|
||||||
|
Help: "Total number of blobs retrieved from the that storage",
|
||||||
|
})
|
||||||
CacheMissCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
CacheMissCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Subsystem: subsystemCache,
|
Subsystem: subsystemCache,
|
||||||
|
@ -181,11 +199,21 @@ var (
|
||||||
Name: "udp_in_bytes",
|
Name: "udp_in_bytes",
|
||||||
Help: "Total number of bytes downloaded through UDP",
|
Help: "Total number of bytes downloaded through UDP",
|
||||||
})
|
})
|
||||||
|
MtrInBytesHttp = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Name: "http_in_bytes",
|
||||||
|
Help: "Total number of bytes downloaded through HTTP",
|
||||||
|
})
|
||||||
MtrOutBytesUdp = promauto.NewCounter(prometheus.CounterOpts{
|
MtrOutBytesUdp = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Name: "udp_out_bytes",
|
Name: "udp_out_bytes",
|
||||||
Help: "Total number of bytes streamed out through UDP",
|
Help: "Total number of bytes streamed out through UDP",
|
||||||
})
|
})
|
||||||
|
MtrOutBytesHttp = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Name: "http_out_bytes",
|
||||||
|
Help: "Total number of bytes streamed out through UDP",
|
||||||
|
})
|
||||||
MtrInBytesReflector = promauto.NewCounter(prometheus.CounterOpts{
|
MtrInBytesReflector = promauto.NewCounter(prometheus.CounterOpts{
|
||||||
Namespace: ns,
|
Namespace: ns,
|
||||||
Name: "reflector_in_bytes",
|
Name: "reflector_in_bytes",
|
||||||
|
@ -201,6 +229,21 @@ var (
|
||||||
Name: "s3_in_bytes",
|
Name: "s3_in_bytes",
|
||||||
Help: "Total number of incoming bytes (from S3-CF)",
|
Help: "Total number of incoming bytes (from S3-CF)",
|
||||||
})
|
})
|
||||||
|
Http3BlobReqQueue = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Name: "http3_blob_request_queue_size",
|
||||||
|
Help: "Blob requests of https queue size",
|
||||||
|
})
|
||||||
|
HttpBlobReqQueue = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Name: "http_blob_request_queue_size",
|
||||||
|
Help: "Blob requests queue size of the HTTP protocol",
|
||||||
|
})
|
||||||
|
RoutinesQueue = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Namespace: ns,
|
||||||
|
Name: "routines",
|
||||||
|
Help: "routines running by type",
|
||||||
|
}, []string{"package", "kind"})
|
||||||
)
|
)
|
||||||
|
|
||||||
func CacheLabels(name, component string) prometheus.Labels {
|
func CacheLabels(name, component string) prometheus.Labels {
|
||||||
|
|
348
lite_db/db.go
Normal file
348
lite_db/db.go
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
package lite_db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
qt "github.com/lbryio/lbry.go/v2/extras/query"
|
||||||
|
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
|
_ "github.com/go-sql-driver/mysql" // blank import for db driver ensures its imported even if its not used
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/volatiletech/null"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SdBlob is a special blob that contains information on the rest of the blobs in the stream
|
||||||
|
type SdBlob struct {
|
||||||
|
StreamName string `json:"stream_name"`
|
||||||
|
Blobs []struct {
|
||||||
|
Length int `json:"length"`
|
||||||
|
BlobNum int `json:"blob_num"`
|
||||||
|
BlobHash string `json:"blob_hash,omitempty"`
|
||||||
|
IV string `json:"iv"`
|
||||||
|
} `json:"blobs"`
|
||||||
|
StreamType string `json:"stream_type"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
SuggestedFileName string `json:"suggested_file_name"`
|
||||||
|
StreamHash string `json:"stream_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQL implements the DB interface
|
||||||
|
type SQL struct {
|
||||||
|
conn *sql.DB
|
||||||
|
|
||||||
|
TrackAccessTime bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func logQuery(query string, args ...interface{}) {
|
||||||
|
s, err := qt.InterpolateParams(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln(err)
|
||||||
|
} else {
|
||||||
|
log.Debugln(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect will create a connection to the database
|
||||||
|
func (s *SQL) Connect(dsn string) error {
|
||||||
|
var err error
|
||||||
|
// interpolateParams is necessary. otherwise uploading a stream with thousands of blobs
|
||||||
|
// will hit MySQL's max_prepared_stmt_count limit because the prepared statements are all
|
||||||
|
// opened inside a transaction. closing them manually doesn't seem to help
|
||||||
|
dsn += "?parseTime=1&collation=utf8mb4_unicode_ci&interpolateParams=1"
|
||||||
|
s.conn, err = sql.Open("mysql", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.conn.SetMaxIdleConns(12)
|
||||||
|
|
||||||
|
return errors.Err(s.conn.Ping())
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBlob adds a blob to the database.
|
||||||
|
func (s *SQL) AddBlob(hash string, length int) error {
|
||||||
|
if s.conn == nil {
|
||||||
|
return errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.insertBlob(hash, length)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) insertBlob(hash string, length int) (int64, error) {
|
||||||
|
if length <= 0 {
|
||||||
|
return 0, errors.Err("length must be positive")
|
||||||
|
}
|
||||||
|
const isStored = true
|
||||||
|
now := time.Now()
|
||||||
|
args := []interface{}{hash, isStored, length, now}
|
||||||
|
blobID, err := s.exec(
|
||||||
|
"INSERT INTO blob_ (hash, is_stored, length, last_accessed_at) VALUES ("+qt.Qs(len(args))+") ON DUPLICATE KEY UPDATE is_stored = (is_stored or VALUES(is_stored)), last_accessed_at=VALUES(last_accessed_at)",
|
||||||
|
args...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if blobID == 0 {
|
||||||
|
err = s.conn.QueryRow("SELECT id FROM blob_ WHERE hash = ?", hash).Scan(&blobID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Err(err)
|
||||||
|
}
|
||||||
|
if blobID == 0 {
|
||||||
|
return 0, errors.Err("blob ID is 0 even after INSERTing and SELECTing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blobID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasBlob checks if the database contains the blob information.
|
||||||
|
func (s *SQL) HasBlob(hash string) (bool, error) {
|
||||||
|
exists, err := s.HasBlobs([]string{hash})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return exists[hash], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasBlobs checks if the database contains the set of blobs and returns a bool map.
|
||||||
|
func (s *SQL) HasBlobs(hashes []string) (map[string]bool, error) {
|
||||||
|
exists, streamsNeedingTouch, err := s.hasBlobs(hashes)
|
||||||
|
s.touch(streamsNeedingTouch)
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) touch(blobIDs []uint64) error {
|
||||||
|
if len(blobIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "UPDATE blob_ SET last_accessed_at = ? WHERE id IN (" + qt.Qs(len(blobIDs)) + ")"
|
||||||
|
args := make([]interface{}, len(blobIDs)+1)
|
||||||
|
args[0] = time.Now()
|
||||||
|
for i := range blobIDs {
|
||||||
|
args[i+1] = blobIDs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
_, err := s.exec(query, args...)
|
||||||
|
log.Debugf("blobs access query touched %d blobs and took %s", len(blobIDs), time.Since(startTime))
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) hasBlobs(hashes []string) (map[string]bool, []uint64, error) {
|
||||||
|
if s.conn == nil {
|
||||||
|
return nil, nil, errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
hash string
|
||||||
|
blobID uint64
|
||||||
|
lastAccessedAt null.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
var needsTouch []uint64
|
||||||
|
exists := make(map[string]bool)
|
||||||
|
|
||||||
|
touchDeadline := time.Now().AddDate(0, 0, -1) // touch blob if last accessed before this time
|
||||||
|
maxBatchSize := 10000
|
||||||
|
doneIndex := 0
|
||||||
|
|
||||||
|
for len(hashes) > doneIndex {
|
||||||
|
sliceEnd := doneIndex + maxBatchSize
|
||||||
|
if sliceEnd > len(hashes) {
|
||||||
|
sliceEnd = len(hashes)
|
||||||
|
}
|
||||||
|
log.Debugf("getting hashes[%d:%d] of %d", doneIndex, sliceEnd, len(hashes))
|
||||||
|
batch := hashes[doneIndex:sliceEnd]
|
||||||
|
|
||||||
|
// TODO: this query doesn't work for SD blobs, which are not in the stream_blob table
|
||||||
|
|
||||||
|
query := `SELECT hash, id, last_accessed_at
|
||||||
|
FROM blob_
|
||||||
|
WHERE is_stored = ? and hash IN (` + qt.Qs(len(batch)) + `)`
|
||||||
|
args := make([]interface{}, len(batch)+1)
|
||||||
|
args[0] = true
|
||||||
|
for i := range batch {
|
||||||
|
args[i+1] = batch[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
logQuery(query, args...)
|
||||||
|
|
||||||
|
err := func() error {
|
||||||
|
startTime := time.Now()
|
||||||
|
rows, err := s.conn.Query(query, args...)
|
||||||
|
log.Debugf("hashes query took %s", time.Since(startTime))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
defer closeRows(rows)
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
err := rows.Scan(&hash, &blobID, &lastAccessedAt)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
exists[hash] = true
|
||||||
|
if s.TrackAccessTime && (!lastAccessedAt.Valid || lastAccessedAt.Time.Before(touchDeadline)) {
|
||||||
|
needsTouch = append(needsTouch, blobID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rows.Err()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doneIndex += len(batch)
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exists, needsTouch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete will remove the blob from the db
|
||||||
|
func (s *SQL) Delete(hash string) error {
|
||||||
|
_, err := s.exec("UPDATE blob_ set is_stored = ? WHERE hash = ?", 0, hash)
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSDBlob insert the SD blob and all the content blobs. The content blobs are marked as "not stored",
|
||||||
|
// but they are tracked so reflector knows what it is missing.
|
||||||
|
func (s *SQL) AddSDBlob(sdHash string, sdBlobLength int) error {
|
||||||
|
if s.conn == nil {
|
||||||
|
return errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.insertBlob(sdHash, sdBlobLength)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHashRange gets the smallest and biggest hashes in the db
|
||||||
|
func (s *SQL) GetLRUBlobs(maxBlobs int) ([]string, error) {
|
||||||
|
if s.conn == nil {
|
||||||
|
return nil, errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "SELECT hash from blob_ where is_stored = ? order by last_accessed_at limit ?"
|
||||||
|
const isStored = true
|
||||||
|
logQuery(query, isStored, maxBlobs)
|
||||||
|
rows, err := s.conn.Query(query, isStored, maxBlobs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Err(err)
|
||||||
|
}
|
||||||
|
defer closeRows(rows)
|
||||||
|
blobs := make([]string, 0, maxBlobs)
|
||||||
|
for rows.Next() {
|
||||||
|
var hash string
|
||||||
|
err := rows.Scan(&hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Err(err)
|
||||||
|
}
|
||||||
|
blobs = append(blobs, hash)
|
||||||
|
}
|
||||||
|
return blobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) AllBlobs() ([]string, error) {
|
||||||
|
if s.conn == nil {
|
||||||
|
return nil, errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "SELECT hash from blob_ where is_stored = ?" //TODO: maybe sorting them makes more sense?
|
||||||
|
const isStored = true
|
||||||
|
logQuery(query, isStored)
|
||||||
|
rows, err := s.conn.Query(query, isStored)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Err(err)
|
||||||
|
}
|
||||||
|
defer closeRows(rows)
|
||||||
|
totalBlobs, err := s.BlobsCount()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blobs := make([]string, 0, totalBlobs)
|
||||||
|
for rows.Next() {
|
||||||
|
var hash string
|
||||||
|
err := rows.Scan(&hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Err(err)
|
||||||
|
}
|
||||||
|
blobs = append(blobs, hash)
|
||||||
|
}
|
||||||
|
return blobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) BlobsCount() (int, error) {
|
||||||
|
if s.conn == nil {
|
||||||
|
return 0, errors.Err("not connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := "SELECT count(id) from blob_ where is_stored = ?" //TODO: maybe sorting them makes more sense?
|
||||||
|
const isStored = true
|
||||||
|
logQuery(query, isStored)
|
||||||
|
var count int
|
||||||
|
err := s.conn.QueryRow(query, isStored).Scan(&count)
|
||||||
|
return count, errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeRows(rows *sql.Rows) {
|
||||||
|
if rows != nil {
|
||||||
|
err := rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error closing rows: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQL) exec(query string, args ...interface{}) (int64, error) {
|
||||||
|
logQuery(query, args...)
|
||||||
|
attempt, maxAttempts := 0, 3
|
||||||
|
Retry:
|
||||||
|
attempt++
|
||||||
|
result, err := s.conn.Exec(query, args...)
|
||||||
|
if isLockTimeoutError(err) {
|
||||||
|
if attempt <= maxAttempts {
|
||||||
|
//Error 1205: Lock wait timeout exceeded; try restarting transaction
|
||||||
|
goto Retry
|
||||||
|
}
|
||||||
|
err = errors.Prefix("Lock timeout for query "+query, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastID, err := result.LastInsertId()
|
||||||
|
return lastID, errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLockTimeoutError(err error) bool {
|
||||||
|
e, ok := err.(*mysql.MySQLError)
|
||||||
|
return ok && e != nil && e.Number == 1205
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SQL schema
|
||||||
|
|
||||||
|
in prod make sure you use latin1 or utf8 charset, NOT utf8mb4. that's a waste of space.
|
||||||
|
|
||||||
|
CREATE TABLE `blob_` (
|
||||||
|
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`hash` char(96) NOT NULL,
|
||||||
|
`is_stored` tinyint(1) NOT NULL DEFAULT '0',
|
||||||
|
`length` bigint unsigned DEFAULT NULL,
|
||||||
|
`last_accessed_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `id` (`id`),
|
||||||
|
UNIQUE KEY `blob_hash_idx` (`hash`),
|
||||||
|
KEY `blob_last_accessed_idx` (`last_accessed_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
||||||
|
|
||||||
|
*/
|
|
@ -5,14 +5,14 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht"
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht/bits"
|
|
||||||
"github.com/lbryio/reflector.go/cluster"
|
"github.com/lbryio/reflector.go/cluster"
|
||||||
"github.com/lbryio/reflector.go/db"
|
"github.com/lbryio/reflector.go/db"
|
||||||
"github.com/lbryio/reflector.go/peer"
|
|
||||||
"github.com/lbryio/reflector.go/reflector"
|
"github.com/lbryio/reflector.go/reflector"
|
||||||
|
"github.com/lbryio/reflector.go/server/peer"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/dht"
|
||||||
|
"github.com/lbryio/lbry.go/v2/dht/bits"
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/extras/stop"
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func New(conf *Config) *Prism {
|
||||||
dht: d,
|
dht: d,
|
||||||
cluster: c,
|
cluster: c,
|
||||||
peer: peer.NewServer(conf.Blobs),
|
peer: peer.NewServer(conf.Blobs),
|
||||||
reflector: reflector.NewServer(conf.Blobs),
|
reflector: reflector.NewServer(conf.Blobs, conf.Blobs),
|
||||||
|
|
||||||
grp: stop.New(),
|
grp: stop.New(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht/bits"
|
"github.com/lbryio/lbry.go/v2/dht/bits"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAnnounceRange(t *testing.T) {
|
func TestAnnounceRange(t *testing.T) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import (
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TODO = `
|
/* TODO:
|
||||||
import cert from wallet
|
import cert from wallet
|
||||||
get all utxos from chainquery
|
get all utxos from chainquery
|
||||||
create transaction
|
create transaction
|
||||||
|
@ -38,8 +38,7 @@ var TODO = `
|
||||||
"too-long-mempool-chain",
|
"too-long-mempool-chain",
|
||||||
"Missing inputs",
|
"Missing inputs",
|
||||||
"Not enough funds to cover this transaction",
|
"Not enough funds to cover this transaction",
|
||||||
}
|
*/
|
||||||
`
|
|
||||||
|
|
||||||
type Details struct {
|
type Details struct {
|
||||||
Title string
|
Title string
|
||||||
|
|
98
readme.md
98
readme.md
|
@ -1,19 +1,104 @@
|
||||||
# Reflector
|
# Reflector
|
||||||
|
|
||||||
A reflector cluster to accept LBRY content for hosting en masse, rehost the content, and make money on data fees (TODO).
|
Reflector is a central piece of software that providers LBRY with the following features:
|
||||||
This code includes Go implementations of the LBRY peer protocol, reflector protocol, and DHT.
|
- Blobs reflection: when something is published, we capture the data and store it on our servers for quicker retrieval
|
||||||
|
- Blobs distribution: when a piece of content is requested and the LBRY network doesn't have it, reflector will retrieve it from its storage and distribute it
|
||||||
|
- Blobs caching: reflectors can be chained together in multiple regions or servers to form a chain of cached content. We call those "blobcaches". They are layered so that content distribution is favorable in all the regions we deploy it to
|
||||||
|
|
||||||
|
There are a few other features embedded in reflector.go including publishing streams from Go, downloading or upload blobs, resolving content and more unfinished tools.
|
||||||
|
|
||||||
|
This code includes a Go implementations of the LBRY peer protocol, reflector protocol, and DHT.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
coming soon
|
- Install mysql 8 (5.7 might work too)
|
||||||
|
- add a reflector user and database with password `reflector` with localhost access only
|
||||||
|
- Create the tables as described [here](https://github.com/lbryio/reflector.go/blob/ittt/db/db.go#L735) (the link might not update as the code does so just look for the schema in that file)
|
||||||
|
|
||||||
|
#### We do not support running reflector.go as a blob receiver, however if you want to run it as a private blobcache you may compile it yourself and run it as following:
|
||||||
|
```bash
|
||||||
|
./prism-bin reflector \
|
||||||
|
--conf="none" \
|
||||||
|
--disable-uploads=true \
|
||||||
|
--use-db=false \
|
||||||
|
--upstream-reflector="reflector.lbry.com" \
|
||||||
|
--upstream-protocol="http" \
|
||||||
|
--request-queue-size=200 \
|
||||||
|
--disk-cache="2GB:/path/to/your/storage/:localdb" \
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a systemd script if you want to run it automatically on startup or as a service.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
coming soon
|
Usage as reflector/blobcache:
|
||||||
|
```bash
|
||||||
|
Run reflector server
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
prism reflector [flags]
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--disable-blocklist Disable blocklist watching/updating
|
||||||
|
--disable-uploads Disable uploads to this reflector server
|
||||||
|
--disk-cache string Where to cache blobs on the file system. format is 'sizeGB:CACHE_PATH:cachemanager' (cachemanagers: localdb/lfuda/lru) (default "100GB:/tmp/downloaded_blobs:localdb")
|
||||||
|
-h, --help help for reflector
|
||||||
|
--http-peer-port int The port reflector will distribute content from over HTTP protocol (default 5569)
|
||||||
|
--http3-peer-port int The port reflector will distribute content from over HTTP3 protocol (default 5568)
|
||||||
|
--mem-cache int enable in-memory cache with a max size of this many blobs
|
||||||
|
--metrics-port int The port reflector will use for prometheus metrics (default 2112)
|
||||||
|
--optional-disk-cache string Optional secondary file system cache for blobs. format is 'sizeGB:CACHE_PATH:cachemanager' (cachemanagers: localdb/lfuda/lru) (this would get hit before the one specified in disk-cache)
|
||||||
|
--origin-endpoint string HTTP edge endpoint for standard HTTP retrieval
|
||||||
|
--origin-endpoint-fallback string HTTP edge endpoint for standard HTTP retrieval if first origin fails
|
||||||
|
--receiver-port int The port reflector will receive content from (default 5566)
|
||||||
|
--request-queue-size int How many concurrent requests from downstream should be handled at once (the rest will wait) (default 200)
|
||||||
|
--tcp-peer-port int The port reflector will distribute content from for the TCP (LBRY) protocol (default 5567)
|
||||||
|
--upstream-protocol string protocol used to fetch blobs from another upstream reflector server (tcp/http3/http) (default "http")
|
||||||
|
--upstream-reflector string host:port of a reflector server where blobs are fetched from
|
||||||
|
--use-db Whether to connect to the reflector db or not (default true)
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
--conf string Path to config. Use 'none' to disable (default "config.json")
|
||||||
|
-v, --verbose strings Verbose logging for specific components
|
||||||
|
```
|
||||||
|
|
||||||
|
Other uses:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Prism is a single entry point application with multiple sub modules which can be leveraged individually or together
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
prism [command]
|
||||||
|
|
||||||
|
Available Commands:
|
||||||
|
check-integrity check blobs integrity for a given path
|
||||||
|
cluster Start(join) to or Start a new cluster
|
||||||
|
decode Decode a claim value
|
||||||
|
dht Run dht node
|
||||||
|
getstream Get a stream from a reflector server
|
||||||
|
help Help about any command
|
||||||
|
peer Run peer server
|
||||||
|
populate-db populate local database with blobs from a disk storage
|
||||||
|
publish Publish a file
|
||||||
|
reflector Run reflector server
|
||||||
|
resolve Resolve a URL
|
||||||
|
send Send a file to a reflector
|
||||||
|
sendblob Send a random blob to a reflector server
|
||||||
|
start Runs full prism application with cluster, dht, peer server, and reflector server.
|
||||||
|
test Test things
|
||||||
|
upload Upload blobs to S3
|
||||||
|
version Print the version
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
--conf string Path to config. Use 'none' to disable (default "config.json")
|
||||||
|
-h, --help help for prism
|
||||||
|
-v, --verbose strings Verbose logging for specific components
|
||||||
|
```
|
||||||
## Running from Source
|
## Running from Source
|
||||||
|
|
||||||
This project requires [Go v1.11](https://golang.org/doc/install) or higher because it uses Go modules.
|
This project requires [Go v1.16](https://golang.org/doc/install).
|
||||||
|
|
||||||
|
On Ubuntu you can install it with `sudo snap install go --classic`
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone git@github.com:lbryio/reflector.go.git
|
git clone git@github.com:lbryio/reflector.go.git
|
||||||
|
@ -36,5 +121,4 @@ We take security seriously. Please contact security@lbry.com regarding any secur
|
||||||
Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
|
Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
The primary contact for this project is [@Nikooo777](https://github.com/Nikooo777) (niko-at-lbry.com)
|
||||||
The primary contact for this project is [@lyoshenka](https://github.com/lyoshenka) (grin@lbry.com)
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
"github.com/lbryio/reflector.go/wallet"
|
"github.com/lbryio/reflector.go/wallet"
|
||||||
|
|
||||||
|
@ -109,8 +110,9 @@ func sdHashesForOutpoints(walletServers, outpoints []string, stopper stop.Chan)
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "sdhashesforoutput").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "sdhashesforoutput").Dec()
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
case <-stopper:
|
case <-stopper:
|
||||||
|
|
|
@ -40,15 +40,17 @@ type Server struct {
|
||||||
|
|
||||||
EnableBlocklist bool // if true, blocklist checking and blob deletion will be enabled
|
EnableBlocklist bool // if true, blocklist checking and blob deletion will be enabled
|
||||||
|
|
||||||
store store.BlobStore
|
underlyingStore store.BlobStore
|
||||||
|
outerStore store.BlobStore
|
||||||
grp *stop.Group
|
grp *stop.Group
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns an initialized reflector server pointer.
|
// NewServer returns an initialized reflector server pointer.
|
||||||
func NewServer(store store.BlobStore) *Server {
|
func NewServer(underlying store.BlobStore, outer store.BlobStore) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
Timeout: DefaultTimeout,
|
Timeout: DefaultTimeout,
|
||||||
store: store,
|
underlyingStore: underlying,
|
||||||
|
outerStore: outer,
|
||||||
grp: stop.New(),
|
grp: stop.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,9 +69,10 @@ func (s *Server) Start(address string) error {
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
log.Println("reflector listening on " + address)
|
log.Println("reflector listening on " + address)
|
||||||
|
|
||||||
s.grp.Add(1)
|
s.grp.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "listener").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "listener").Dec()
|
||||||
<-s.grp.Ch()
|
<-s.grp.Ch()
|
||||||
err := l.Close()
|
err := l.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -79,15 +82,19 @@ func (s *Server) Start(address string) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.grp.Add(1)
|
s.grp.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "start").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "start").Dec()
|
||||||
s.listenAndServe(l)
|
s.listenAndServe(l)
|
||||||
s.grp.Done()
|
s.grp.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if s.EnableBlocklist {
|
if s.EnableBlocklist {
|
||||||
if b, ok := s.store.(store.Blocklister); ok {
|
if b, ok := s.underlyingStore.(store.Blocklister); ok {
|
||||||
s.grp.Add(1)
|
s.grp.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "enableblocklist").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "enableblocklist").Dec()
|
||||||
s.enableBlocklist(b)
|
s.enableBlocklist(b)
|
||||||
s.grp.Done()
|
s.grp.Done()
|
||||||
}()
|
}()
|
||||||
|
@ -110,7 +117,9 @@ func (s *Server) listenAndServe(listener net.Listener) {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
} else {
|
} else {
|
||||||
s.grp.Add(1)
|
s.grp.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "server-listenandserve").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "server-listenandserve").Dec()
|
||||||
s.handleConn(conn)
|
s.handleConn(conn)
|
||||||
s.grp.Done()
|
s.grp.Done()
|
||||||
}()
|
}()
|
||||||
|
@ -125,7 +134,9 @@ func (s *Server) handleConn(conn net.Conn) {
|
||||||
close(connNeedsClosing)
|
close(connNeedsClosing)
|
||||||
}()
|
}()
|
||||||
s.grp.Add(1)
|
s.grp.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "server-handleconn").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "server-handleconn").Dec()
|
||||||
defer s.grp.Done()
|
defer s.grp.Done()
|
||||||
select {
|
select {
|
||||||
case <-connNeedsClosing:
|
case <-connNeedsClosing:
|
||||||
|
@ -190,13 +201,13 @@ func (s *Server) receiveBlob(conn net.Conn) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var wantsBlob bool
|
var wantsBlob bool
|
||||||
if bl, ok := s.store.(store.Blocklister); ok {
|
if bl, ok := s.underlyingStore.(store.Blocklister); ok {
|
||||||
wantsBlob, err = bl.Wants(blobHash)
|
wantsBlob, err = bl.Wants(blobHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
blobExists, err := s.store.Has(blobHash)
|
blobExists, err := s.underlyingStore.Has(blobHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -206,7 +217,7 @@ func (s *Server) receiveBlob(conn net.Conn) error {
|
||||||
var neededBlobs []string
|
var neededBlobs []string
|
||||||
|
|
||||||
if isSdBlob && !wantsBlob {
|
if isSdBlob && !wantsBlob {
|
||||||
if nbc, ok := s.store.(neededBlobChecker); ok {
|
if nbc, ok := s.underlyingStore.(neededBlobChecker); ok {
|
||||||
neededBlobs, err = nbc.MissingBlobsForKnownStream(blobHash)
|
neededBlobs, err = nbc.MissingBlobsForKnownStream(blobHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -249,9 +260,9 @@ func (s *Server) receiveBlob(conn net.Conn) error {
|
||||||
log.Debugln("Got blob " + blobHash[:8])
|
log.Debugln("Got blob " + blobHash[:8])
|
||||||
|
|
||||||
if isSdBlob {
|
if isSdBlob {
|
||||||
err = s.store.PutSD(blobHash, blob)
|
err = s.outerStore.PutSD(blobHash, blob)
|
||||||
} else {
|
} else {
|
||||||
err = s.store.Put(blobHash, blob)
|
err = s.outerStore.Put(blobHash, blob)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -9,9 +9,10 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/dht/bits"
|
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/dht/bits"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/phayes/freeport"
|
"github.com/phayes/freeport"
|
||||||
)
|
)
|
||||||
|
@ -22,7 +23,7 @@ func startServerOnRandomPort(t *testing.T) (*Server, int) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := NewServer(store.NewMemStore())
|
srv := NewServer(store.NewMemStore(), store.NewMemStore())
|
||||||
err = srv.Start("127.0.0.1:" + strconv.Itoa(port))
|
err = srv.Start("127.0.0.1:" + strconv.Itoa(port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -119,7 +120,7 @@ func TestServer_Timeout(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := NewServer(store.NewMemStore())
|
srv := NewServer(store.NewMemStore(), store.NewMemStore())
|
||||||
srv.Timeout = testTimeout
|
srv.Timeout = testTimeout
|
||||||
err = srv.Start("127.0.0.1:" + strconv.Itoa(port))
|
err = srv.Start("127.0.0.1:" + strconv.Itoa(port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -190,7 +191,7 @@ func TestServer_PartialUpload(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := NewServer(st)
|
srv := NewServer(st, st)
|
||||||
err = srv.Start("127.0.0.1:" + strconv.Itoa(port))
|
err = srv.Start("127.0.0.1:" + strconv.Itoa(port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/db"
|
"github.com/lbryio/reflector.go/db"
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
@ -74,7 +75,7 @@ func (u *Uploader) Upload(dirOrFilePath string) error {
|
||||||
|
|
||||||
var exists map[string]bool
|
var exists map[string]bool
|
||||||
if !u.skipExistsCheck {
|
if !u.skipExistsCheck {
|
||||||
exists, err = u.db.HasBlobs(hashes)
|
exists, err = u.db.HasBlobs(hashes, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,9 @@ func (u *Uploader) Upload(dirOrFilePath string) error {
|
||||||
|
|
||||||
for i := 0; i < u.workers; i++ {
|
for i := 0; i < u.workers; i++ {
|
||||||
workerWG.Add(1)
|
workerWG.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "upload").Inc()
|
||||||
go func(i int) {
|
go func(i int) {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "upload").Dec()
|
||||||
defer workerWG.Done()
|
defer workerWG.Done()
|
||||||
defer func(i int) { log.Debugf("worker %d quitting", i) }(i)
|
defer func(i int) { log.Debugf("worker %d quitting", i) }(i)
|
||||||
u.worker(pathChan)
|
u.worker(pathChan)
|
||||||
|
@ -97,7 +100,9 @@ func (u *Uploader) Upload(dirOrFilePath string) error {
|
||||||
|
|
||||||
countWG := sync.WaitGroup{}
|
countWG := sync.WaitGroup{}
|
||||||
countWG.Add(1)
|
countWG.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("reflector", "uploader").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("reflector", "uploader").Dec()
|
||||||
defer countWG.Done()
|
defer countWG.Done()
|
||||||
u.counter()
|
u.counter()
|
||||||
}()
|
}()
|
||||||
|
|
92
server/http/routes.go
Normal file
92
server/http/routes.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) getBlob(c *gin.Context) {
|
||||||
|
waiter := &sync.WaitGroup{}
|
||||||
|
waiter.Add(1)
|
||||||
|
enqueue(&blobRequest{c: c, finished: waiter})
|
||||||
|
waiter.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleGetBlob(c *gin.Context) {
|
||||||
|
start := time.Now()
|
||||||
|
hash := c.Query("hash")
|
||||||
|
|
||||||
|
if s.missesCache.Has(hash) {
|
||||||
|
serialized, err := shared.NewBlobTrace(time.Since(start), "http").Serialize()
|
||||||
|
c.Header("Via", serialized)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(errors.Err(err))
|
||||||
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
blob, trace, err := s.store.Get(hash)
|
||||||
|
if err != nil {
|
||||||
|
serialized, serializeErr := trace.Serialize()
|
||||||
|
if serializeErr != nil {
|
||||||
|
_ = c.Error(errors.Prefix(serializeErr.Error(), err))
|
||||||
|
c.String(http.StatusInternalServerError, errors.Prefix(serializeErr.Error(), err).Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Header("Via", serialized)
|
||||||
|
|
||||||
|
if errors.Is(err, store.ErrBlobNotFound) {
|
||||||
|
_ = s.missesCache.Set(hash, true)
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = c.Error(err)
|
||||||
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serialized, err := trace.Serialize()
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metrics.MtrOutBytesHttp.Add(float64(len(blob)))
|
||||||
|
metrics.BlobDownloadCount.Inc()
|
||||||
|
metrics.HttpDownloadCount.Inc()
|
||||||
|
c.Header("Via", serialized)
|
||||||
|
c.Header("Content-Disposition", "filename="+hash)
|
||||||
|
c.Data(http.StatusOK, "application/octet-stream", blob)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) hasBlob(c *gin.Context) {
|
||||||
|
hash := c.Query("hash")
|
||||||
|
has, err := s.store.Has(hash)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(err)
|
||||||
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if has {
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Status(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) recoveryHandler(c *gin.Context, err interface{}) {
|
||||||
|
c.JSON(500, gin.H{
|
||||||
|
"title": "Error",
|
||||||
|
"err": err,
|
||||||
|
})
|
||||||
|
}
|
79
server/http/server.go
Normal file
79
server/http/server.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
|
|
||||||
|
"github.com/bluele/gcache"
|
||||||
|
nice "github.com/ekyoung/gin-nice-recovery"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is an instance of a peer server that houses the listener and store.
|
||||||
|
type Server struct {
|
||||||
|
store store.BlobStore
|
||||||
|
grp *stop.Group
|
||||||
|
concurrentRequests int
|
||||||
|
missesCache gcache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer returns an initialized Server pointer.
|
||||||
|
func NewServer(store store.BlobStore, requestQueueSize int) *Server {
|
||||||
|
return &Server{
|
||||||
|
store: store,
|
||||||
|
grp: stop.New(),
|
||||||
|
concurrentRequests: requestQueueSize,
|
||||||
|
missesCache: gcache.New(2000).Expiration(5 * time.Minute).ARC().Build(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully shuts down the peer server.
|
||||||
|
func (s *Server) Shutdown() {
|
||||||
|
log.Debug("shutting down HTTP server")
|
||||||
|
s.grp.StopAndWait()
|
||||||
|
log.Debug("HTTP server stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the server listener to handle connections.
|
||||||
|
func (s *Server) Start(address string) error {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
router := gin.Default()
|
||||||
|
router.GET("/blob", s.getBlob)
|
||||||
|
router.HEAD("/blob", s.hasBlob)
|
||||||
|
// Install nice.Recovery, passing the handler to call after recovery
|
||||||
|
router.Use(nice.Recovery(s.recoveryHandler))
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: address,
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
go s.listenForShutdown(srv)
|
||||||
|
go InitWorkers(s, s.concurrentRequests)
|
||||||
|
// Initializing the server in a goroutine so that
|
||||||
|
// it won't block the graceful shutdown handling below
|
||||||
|
s.grp.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer s.grp.Done()
|
||||||
|
log.Println("HTTP server listening on " + address)
|
||||||
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Fatalf("listen: %s\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) listenForShutdown(listener *http.Server) {
|
||||||
|
<-s.grp.Ch()
|
||||||
|
// The context is used to inform the server it has 5 seconds to finish
|
||||||
|
// the request it is currently handling
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := listener.Shutdown(ctx); err != nil {
|
||||||
|
log.Fatal("Server forced to shutdown:", err)
|
||||||
|
}
|
||||||
|
}
|
46
server/http/worker.go
Normal file
46
server/http/worker.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type blobRequest struct {
|
||||||
|
c *gin.Context
|
||||||
|
finished *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
var getReqCh = make(chan *blobRequest, 20000)
|
||||||
|
|
||||||
|
func InitWorkers(server *Server, workers int) {
|
||||||
|
stopper := stop.New(server.grp)
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("http", "worker").Inc()
|
||||||
|
go func(worker int) {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("http", "worker").Dec()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopper.Ch():
|
||||||
|
case r := <-getReqCh:
|
||||||
|
process(server, r)
|
||||||
|
metrics.HttpBlobReqQueue.Dec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enqueue(b *blobRequest) {
|
||||||
|
metrics.HttpBlobReqQueue.Inc()
|
||||||
|
getReqCh <- b
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(server *Server, r *blobRequest) {
|
||||||
|
server.HandleGetBlob(r.c)
|
||||||
|
r.finished.Done()
|
||||||
|
}
|
|
@ -9,12 +9,15 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
|
||||||
"github.com/lucas-clemente/quic-go/http3"
|
"github.com/lucas-clemente/quic-go/http3"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is an instance of a client connected to a server.
|
// Client is an instance of a client connected to a server.
|
||||||
|
@ -35,7 +38,7 @@ func (c *Client) Close() error {
|
||||||
func (c *Client) GetStream(sdHash string, blobCache store.BlobStore) (stream.Stream, error) {
|
func (c *Client) GetStream(sdHash string, blobCache store.BlobStore) (stream.Stream, error) {
|
||||||
var sd stream.SDBlob
|
var sd stream.SDBlob
|
||||||
|
|
||||||
b, err := c.GetBlob(sdHash)
|
b, _, err := c.GetBlob(sdHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -49,10 +52,12 @@ func (c *Client) GetStream(sdHash string, blobCache store.BlobStore) (stream.Str
|
||||||
s[0] = b
|
s[0] = b
|
||||||
|
|
||||||
for i := 0; i < len(sd.BlobInfos)-1; i++ {
|
for i := 0; i < len(sd.BlobInfos)-1; i++ {
|
||||||
s[i+1], err = c.GetBlob(hex.EncodeToString(sd.BlobInfos[i].BlobHash))
|
var trace shared.BlobTrace
|
||||||
|
s[i+1], trace, err = c.GetBlob(hex.EncodeToString(sd.BlobInfos[i].BlobHash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debug(trace.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -75,26 +80,35 @@ func (c *Client) HasBlob(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlob gets a blob
|
// GetBlob gets a blob
|
||||||
func (c *Client) GetBlob(hash string) (stream.Blob, error) {
|
func (c *Client) GetBlob(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
resp, err := c.conn.Get(fmt.Sprintf("https://%s/get/%s", c.ServerAddr, hash))
|
start := time.Now()
|
||||||
|
resp, err := c.conn.Get(fmt.Sprintf("https://%s/get/%s?trace=true", c.ServerAddr, hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, shared.NewBlobTrace(time.Since(start), "http3"), errors.Err(err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
fmt.Printf("%s blob not found %d\n", hash, resp.StatusCode)
|
fmt.Printf("%s blob not found %d\n", hash, resp.StatusCode)
|
||||||
return nil, errors.Err(store.ErrBlobNotFound)
|
return nil, shared.NewBlobTrace(time.Since(start), "http3"), errors.Err(store.ErrBlobNotFound)
|
||||||
} else if resp.StatusCode != http.StatusOK {
|
} else if resp.StatusCode != http.StatusOK {
|
||||||
return nil, errors.Err("non 200 status code returned: %d", resp.StatusCode)
|
return nil, shared.NewBlobTrace(time.Since(start), "http3"), errors.Err("non 200 status code returned: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := getBuffer()
|
tmp := getBuffer()
|
||||||
defer putBuffer(tmp)
|
defer putBuffer(tmp)
|
||||||
|
serialized := resp.Header.Get("Via")
|
||||||
|
trace := shared.NewBlobTrace(time.Since(start), "http3")
|
||||||
|
if serialized != "" {
|
||||||
|
parsedTrace, err := shared.Deserialize(serialized)
|
||||||
|
if err != nil {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), "http3"), err
|
||||||
|
}
|
||||||
|
trace = *parsedTrace
|
||||||
|
}
|
||||||
written, err := io.Copy(tmp, resp.Body)
|
written, err := io.Copy(tmp, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, trace.Stack(time.Since(start), "http3"), errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blob := make([]byte, written)
|
blob := make([]byte, written)
|
||||||
|
@ -102,7 +116,7 @@ func (c *Client) GetBlob(hash string) (stream.Blob, error) {
|
||||||
|
|
||||||
metrics.MtrInBytesUdp.Add(float64(len(blob)))
|
metrics.MtrInBytesUdp.Add(float64(len(blob)))
|
||||||
|
|
||||||
return blob, nil
|
return blob, trace.Stack(time.Since(start), "http3"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buffer pool to reduce GC
|
// buffer pool to reduce GC
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
@ -28,13 +30,15 @@ import (
|
||||||
type Server struct {
|
type Server struct {
|
||||||
store store.BlobStore
|
store store.BlobStore
|
||||||
grp *stop.Group
|
grp *stop.Group
|
||||||
|
concurrentRequests int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns an initialized Server pointer.
|
// NewServer returns an initialized Server pointer.
|
||||||
func NewServer(store store.BlobStore) *Server {
|
func NewServer(store store.BlobStore, requestQueueSize int) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
store: store,
|
store: store,
|
||||||
grp: stop.New(),
|
grp: stop.New(),
|
||||||
|
concurrentRequests: requestQueueSize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,33 +67,21 @@ type availabilityResponse struct {
|
||||||
// Start starts the server listener to handle connections.
|
// Start starts the server listener to handle connections.
|
||||||
func (s *Server) Start(address string) error {
|
func (s *Server) Start(address string) error {
|
||||||
log.Println("HTTP3 peer listening on " + address)
|
log.Println("HTTP3 peer listening on " + address)
|
||||||
|
window500M := 500 * 1 << 20
|
||||||
|
|
||||||
quicConf := &quic.Config{
|
quicConf := &quic.Config{
|
||||||
HandshakeTimeout: 4 * time.Second,
|
MaxStreamReceiveWindow: uint64(window500M),
|
||||||
MaxIdleTimeout: 10 * time.Second,
|
MaxConnectionReceiveWindow: uint64(window500M),
|
||||||
|
EnableDatagrams: true,
|
||||||
|
HandshakeIdleTimeout: 4 * time.Second,
|
||||||
|
MaxIdleTimeout: 20 * time.Second,
|
||||||
}
|
}
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/get/{hash}", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/get/{hash}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
waiter := &sync.WaitGroup{}
|
||||||
requestedBlob := vars["hash"]
|
waiter.Add(1)
|
||||||
blob, err := s.store.Get(requestedBlob)
|
enqueue(&blobRequest{request: r, reply: w, finished: waiter})
|
||||||
if err != nil {
|
waiter.Wait()
|
||||||
if errors.Is(err, store.ErrBlobNotFound) {
|
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("%s: %s", requestedBlob, errors.FullTrace(err))
|
|
||||||
s.logError(err)
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = w.Write(blob)
|
|
||||||
if err != nil {
|
|
||||||
s.logError(err)
|
|
||||||
}
|
|
||||||
metrics.MtrOutBytesUdp.Add(float64(len(blob)))
|
|
||||||
metrics.BlobDownloadCount.Inc()
|
|
||||||
metrics.Http3DownloadCount.Inc()
|
|
||||||
})
|
})
|
||||||
r.HandleFunc("/has/{hash}", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/has/{hash}", func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
@ -127,7 +119,7 @@ func (s *Server) Start(address string) error {
|
||||||
},
|
},
|
||||||
QuicConfig: quicConf,
|
QuicConfig: quicConf,
|
||||||
}
|
}
|
||||||
|
go InitWorkers(s, s.concurrentRequests)
|
||||||
go s.listenForShutdown(&server)
|
go s.listenForShutdown(&server)
|
||||||
s.grp.Add(1)
|
s.grp.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -176,3 +168,47 @@ func (s *Server) listenForShutdown(listener *http3.Server) {
|
||||||
log.Error("error closing listener for peer server - ", err)
|
log.Error("error closing listener for peer server - ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleGetBlob(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
requestedBlob := vars["hash"]
|
||||||
|
traceParam := r.URL.Query().Get("trace")
|
||||||
|
var err error
|
||||||
|
wantsTrace := false
|
||||||
|
if traceParam != "" {
|
||||||
|
wantsTrace, err = strconv.ParseBool(traceParam)
|
||||||
|
if err != nil {
|
||||||
|
wantsTrace = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blob, trace, err := s.store.Get(requestedBlob)
|
||||||
|
|
||||||
|
if wantsTrace {
|
||||||
|
serialized, err := trace.Serialize()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Add("Via", serialized)
|
||||||
|
log.Debug(trace.String())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, store.ErrBlobNotFound) {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: %s", requestedBlob, errors.FullTrace(err))
|
||||||
|
s.logError(err)
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(blob)
|
||||||
|
if err != nil {
|
||||||
|
s.logError(err)
|
||||||
|
}
|
||||||
|
metrics.MtrOutBytesUdp.Add(float64(len(blob)))
|
||||||
|
metrics.BlobDownloadCount.Inc()
|
||||||
|
metrics.Http3DownloadCount.Inc()
|
||||||
|
}
|
|
@ -4,10 +4,16 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
|
||||||
"github.com/lucas-clemente/quic-go"
|
"github.com/lucas-clemente/quic-go"
|
||||||
"github.com/lucas-clemente/quic-go/http3"
|
"github.com/lucas-clemente/quic-go/http3"
|
||||||
)
|
)
|
||||||
|
@ -16,6 +22,7 @@ import (
|
||||||
// It satisfies the store.BlobStore interface but cannot put or delete blobs.
|
// It satisfies the store.BlobStore interface but cannot put or delete blobs.
|
||||||
type Store struct {
|
type Store struct {
|
||||||
opts StoreOpts
|
opts StoreOpts
|
||||||
|
NotFoundCache *sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
// StoreOpts allows to set options for a new Store.
|
// StoreOpts allows to set options for a new Store.
|
||||||
|
@ -26,13 +33,17 @@ type StoreOpts struct {
|
||||||
|
|
||||||
// NewStore makes a new peer store.
|
// NewStore makes a new peer store.
|
||||||
func NewStore(opts StoreOpts) *Store {
|
func NewStore(opts StoreOpts) *Store {
|
||||||
return &Store{opts: opts}
|
return &Store{opts: opts, NotFoundCache: &sync.Map{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Store) getClient() (*Client, error) {
|
func (p *Store) getClient() (*Client, error) {
|
||||||
var qconf quic.Config
|
var qconf quic.Config
|
||||||
qconf.HandshakeTimeout = 4 * time.Second
|
window500M := 500 * 1 << 20
|
||||||
qconf.MaxIdleTimeout = 10 * time.Second
|
qconf.MaxStreamReceiveWindow = uint64(window500M)
|
||||||
|
qconf.MaxConnectionReceiveWindow = uint64(window500M)
|
||||||
|
qconf.EnableDatagrams = true
|
||||||
|
qconf.HandshakeIdleTimeout = 4 * time.Second
|
||||||
|
qconf.MaxIdleTimeout = 20 * time.Second
|
||||||
pool, err := x509.SystemCertPool()
|
pool, err := x509.SystemCertPool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -68,10 +79,19 @@ func (p *Store) Has(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get downloads the blob from the peer
|
// Get downloads the blob from the peer
|
||||||
func (p *Store) Get(hash string) (stream.Blob, error) {
|
func (p *Store) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
|
if lastChecked, ok := p.NotFoundCache.Load(hash); ok {
|
||||||
|
if lastChecked.(time.Time).After(time.Now().Add(-5 * time.Minute)) {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), p.Name()+"-notfoundcache"), store.ErrBlobNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
c, err := p.getClient()
|
c, err := p.getClient()
|
||||||
|
if err != nil && strings.Contains(err.Error(), "blob not found") {
|
||||||
|
p.NotFoundCache.Store(hash, time.Now())
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), p.Name()), err
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
return c.GetBlob(hash)
|
return c.GetBlob(hash)
|
||||||
|
@ -79,15 +99,19 @@ func (p *Store) Get(hash string) (stream.Blob, error) {
|
||||||
|
|
||||||
// Put is not supported
|
// Put is not supported
|
||||||
func (p *Store) Put(hash string, blob stream.Blob) error {
|
func (p *Store) Put(hash string, blob stream.Blob) error {
|
||||||
panic("http3Store cannot put or delete blobs")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutSD is not supported
|
// PutSD is not supported
|
||||||
func (p *Store) PutSD(hash string, blob stream.Blob) error {
|
func (p *Store) PutSD(hash string, blob stream.Blob) error {
|
||||||
panic("http3Store cannot put or delete blobs")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is not supported
|
// Delete is not supported
|
||||||
func (p *Store) Delete(hash string) error {
|
func (p *Store) Delete(hash string) error {
|
||||||
panic("http3Store cannot put or delete blobs")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown is not supported
|
||||||
|
func (p *Store) Shutdown() {
|
||||||
}
|
}
|
46
server/http3/worker.go
Normal file
46
server/http3/worker.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package http3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/stop"
|
||||||
|
)
|
||||||
|
|
||||||
|
type blobRequest struct {
|
||||||
|
request *http.Request
|
||||||
|
reply http.ResponseWriter
|
||||||
|
finished *sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
var getReqCh = make(chan *blobRequest, 20000)
|
||||||
|
|
||||||
|
func InitWorkers(server *Server, workers int) {
|
||||||
|
stopper := stop.New(server.grp)
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("http3", "worker").Inc()
|
||||||
|
go func(worker int) {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("http3", "worker").Dec()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopper.Ch():
|
||||||
|
case r := <-getReqCh:
|
||||||
|
metrics.Http3BlobReqQueue.Dec()
|
||||||
|
process(server, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enqueue(b *blobRequest) {
|
||||||
|
metrics.Http3BlobReqQueue.Inc()
|
||||||
|
getReqCh <- b
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(server *Server, r *blobRequest) {
|
||||||
|
server.HandleGetBlob(r.reply, r.request)
|
||||||
|
r.finished.Done()
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
@ -17,9 +18,6 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrBlobExists is a default error for when a blob already exists on the reflector server.
|
|
||||||
var ErrBlobExists = errors.Base("blob exists on server")
|
|
||||||
|
|
||||||
// Client is an instance of a client connected to a server.
|
// Client is an instance of a client connected to a server.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
|
@ -57,10 +55,11 @@ func (c *Client) GetStream(sdHash string, blobCache store.BlobStore) (stream.Str
|
||||||
|
|
||||||
var sd stream.SDBlob
|
var sd stream.SDBlob
|
||||||
|
|
||||||
b, err := c.GetBlob(sdHash)
|
b, trace, err := c.GetBlob(sdHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debug(trace.String())
|
||||||
|
|
||||||
err = sd.FromBlob(b)
|
err = sd.FromBlob(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,10 +70,11 @@ func (c *Client) GetStream(sdHash string, blobCache store.BlobStore) (stream.Str
|
||||||
s[0] = b
|
s[0] = b
|
||||||
|
|
||||||
for i := 0; i < len(sd.BlobInfos)-1; i++ {
|
for i := 0; i < len(sd.BlobInfos)-1; i++ {
|
||||||
s[i+1], err = c.GetBlob(hex.EncodeToString(sd.BlobInfos[i].BlobHash))
|
s[i+1], trace, err = c.GetBlob(hex.EncodeToString(sd.BlobInfos[i].BlobHash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debug(trace.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -114,47 +114,52 @@ func (c *Client) HasBlob(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlob gets a blob
|
// GetBlob gets a blob
|
||||||
func (c *Client) GetBlob(hash string) (stream.Blob, error) {
|
func (c *Client) GetBlob(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
if !c.connected {
|
if !c.connected {
|
||||||
return nil, errors.Err("not connected")
|
return nil, shared.NewBlobTrace(time.Since(start), "tcp"), errors.Err("not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRequest, err := json.Marshal(blobRequest{
|
sendRequest, err := json.Marshal(blobRequest{
|
||||||
RequestedBlob: hash,
|
RequestedBlob: hash,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), "tcp"), err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.write(sendRequest)
|
err = c.write(sendRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), "tcp"), err
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp blobResponse
|
var resp blobResponse
|
||||||
err = c.read(&resp)
|
err = c.read(&resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), "tcp"), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace := shared.NewBlobTrace(time.Since(start), "tcp")
|
||||||
|
if resp.RequestTrace != nil {
|
||||||
|
trace = *resp.RequestTrace
|
||||||
|
}
|
||||||
if resp.IncomingBlob.Error != "" {
|
if resp.IncomingBlob.Error != "" {
|
||||||
return nil, errors.Prefix(hash[:8], resp.IncomingBlob.Error)
|
return nil, trace, errors.Prefix(hash[:8], resp.IncomingBlob.Error)
|
||||||
}
|
}
|
||||||
if resp.IncomingBlob.BlobHash != hash {
|
if resp.IncomingBlob.BlobHash != hash {
|
||||||
return nil, errors.Prefix(hash[:8], "blob hash in response does not match requested hash")
|
return nil, trace.Stack(time.Since(start), "tcp"), errors.Prefix(hash[:8], "blob hash in response does not match requested hash")
|
||||||
}
|
}
|
||||||
if resp.IncomingBlob.Length <= 0 {
|
if resp.IncomingBlob.Length <= 0 {
|
||||||
return nil, errors.Prefix(hash[:8], "length reported as <= 0")
|
return nil, trace, errors.Prefix(hash[:8], "length reported as <= 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("receiving blob %s from %s", hash[:8], c.conn.RemoteAddr())
|
log.Debugf("receiving blob %s from %s", hash[:8], c.conn.RemoteAddr())
|
||||||
|
|
||||||
blob, err := c.readRawBlob(resp.IncomingBlob.Length)
|
blob, err := c.readRawBlob(resp.IncomingBlob.Length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, (*resp.RequestTrace).Stack(time.Since(start), "tcp"), err
|
||||||
}
|
}
|
||||||
metrics.MtrInBytesTcp.Add(float64(len(blob)))
|
metrics.MtrInBytesTcp.Add(float64(len(blob)))
|
||||||
return blob, nil
|
return blob, trace.Stack(time.Since(start), "tcp"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) read(v interface{}) error {
|
func (c *Client) read(v interface{}) error {
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
"github.com/lbryio/reflector.go/reflector"
|
"github.com/lbryio/reflector.go/reflector"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
"github.com/lbryio/reflector.go/store"
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
@ -88,7 +89,9 @@ func (s *Server) listenAndServe(listener net.Listener) {
|
||||||
log.Error(errors.Prefix("accepting conn", err))
|
log.Error(errors.Prefix("accepting conn", err))
|
||||||
} else {
|
} else {
|
||||||
s.grp.Add(1)
|
s.grp.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("peer", "server-handleconn").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
|
defer metrics.RoutinesQueue.WithLabelValues("peer", "server-handleconn").Dec()
|
||||||
s.handleConnection(conn)
|
s.handleConnection(conn)
|
||||||
s.grp.Done()
|
s.grp.Done()
|
||||||
}()
|
}()
|
||||||
|
@ -253,6 +256,7 @@ func (s *Server) handleCompositeRequest(data []byte) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var blob []byte
|
var blob []byte
|
||||||
|
var trace shared.BlobTrace
|
||||||
if request.RequestedBlob != "" {
|
if request.RequestedBlob != "" {
|
||||||
if len(request.RequestedBlob) != stream.BlobHashHexLength {
|
if len(request.RequestedBlob) != stream.BlobHashHexLength {
|
||||||
return nil, errors.Err("Invalid blob hash length")
|
return nil, errors.Err("Invalid blob hash length")
|
||||||
|
@ -260,7 +264,8 @@ func (s *Server) handleCompositeRequest(data []byte) ([]byte, error) {
|
||||||
|
|
||||||
log.Debugln("Sending blob " + request.RequestedBlob[:8])
|
log.Debugln("Sending blob " + request.RequestedBlob[:8])
|
||||||
|
|
||||||
blob, err = s.store.Get(request.RequestedBlob)
|
blob, trace, err = s.store.Get(request.RequestedBlob)
|
||||||
|
log.Debug(trace.String())
|
||||||
if errors.Is(err, store.ErrBlobNotFound) {
|
if errors.Is(err, store.ErrBlobNotFound) {
|
||||||
response.IncomingBlob = incomingBlob{
|
response.IncomingBlob = incomingBlob{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
|
@ -382,6 +387,7 @@ type incomingBlob struct {
|
||||||
}
|
}
|
||||||
type blobResponse struct {
|
type blobResponse struct {
|
||||||
IncomingBlob incomingBlob `json:"incoming_blob"`
|
IncomingBlob incomingBlob `json:"incoming_blob"`
|
||||||
|
RequestTrace *shared.BlobTrace
|
||||||
}
|
}
|
||||||
|
|
||||||
type compositeRequest struct {
|
type compositeRequest struct {
|
|
@ -1,8 +1,12 @@
|
||||||
package peer
|
package peer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
"github.com/lbryio/reflector.go/store"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
)
|
)
|
||||||
|
@ -43,26 +47,36 @@ func (p *Store) Has(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get downloads the blob from the peer
|
// Get downloads the blob from the peer
|
||||||
func (p *Store) Get(hash string) (stream.Blob, error) {
|
func (p *Store) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
c, err := p.getClient()
|
c, err := p.getClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), p.Name()), err
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
return c.GetBlob(hash)
|
blob, trace, err := c.GetBlob(hash)
|
||||||
|
if err != nil && strings.Contains(err.Error(), "blob not found") {
|
||||||
|
return nil, trace, store.ErrBlobNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob, trace, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put is not supported
|
// Put is not supported
|
||||||
func (p *Store) Put(hash string, blob stream.Blob) error {
|
func (p *Store) Put(hash string, blob stream.Blob) error {
|
||||||
panic("PeerStore cannot put or delete blobs")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutSD is not supported
|
// PutSD is not supported
|
||||||
func (p *Store) PutSD(hash string, blob stream.Blob) error {
|
func (p *Store) PutSD(hash string, blob stream.Blob) error {
|
||||||
panic("PeerStore cannot put or delete blobs")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete is not supported
|
// Delete is not supported
|
||||||
func (p *Store) Delete(hash string) error {
|
func (p *Store) Delete(hash string) error {
|
||||||
panic("PeerStore cannot put or delete blobs")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown is not supported
|
||||||
|
func (p *Store) Shutdown() {
|
||||||
}
|
}
|
6
shared/errors.go
Normal file
6
shared/errors.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import "github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
|
||||||
|
//ErrNotImplemented is a standard error when a store that implements the store interface does not implement a method
|
||||||
|
var ErrNotImplemented = errors.Base("this store does not implement this method")
|
82
shared/shared.go
Normal file
82
shared/shared.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BlobStack struct {
|
||||||
|
Timing time.Duration `json:"timing"`
|
||||||
|
OriginName string `json:"origin_name"`
|
||||||
|
HostName string `json:"host_name"`
|
||||||
|
}
|
||||||
|
type BlobTrace struct {
|
||||||
|
Stacks []BlobStack `json:"stacks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostName *string
|
||||||
|
|
||||||
|
func getHostName() string {
|
||||||
|
if hostName == nil {
|
||||||
|
hn, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
hn = "unknown"
|
||||||
|
}
|
||||||
|
hostName = &hn
|
||||||
|
}
|
||||||
|
return *hostName
|
||||||
|
}
|
||||||
|
func (b *BlobTrace) Stack(timing time.Duration, originName string) BlobTrace {
|
||||||
|
b.Stacks = append(b.Stacks, BlobStack{
|
||||||
|
Timing: timing,
|
||||||
|
OriginName: originName,
|
||||||
|
HostName: getHostName(),
|
||||||
|
})
|
||||||
|
return *b
|
||||||
|
}
|
||||||
|
func (b *BlobTrace) Merge(otherTrance BlobTrace) BlobTrace {
|
||||||
|
b.Stacks = append(b.Stacks, otherTrance.Stacks...)
|
||||||
|
return *b
|
||||||
|
}
|
||||||
|
func NewBlobTrace(timing time.Duration, originName string) BlobTrace {
|
||||||
|
b := BlobTrace{}
|
||||||
|
b.Stacks = append(b.Stacks, BlobStack{
|
||||||
|
Timing: timing,
|
||||||
|
OriginName: originName,
|
||||||
|
HostName: getHostName(),
|
||||||
|
})
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BlobTrace) String() string {
|
||||||
|
var fullTrace string
|
||||||
|
for i, stack := range b.Stacks {
|
||||||
|
delta := time.Duration(0)
|
||||||
|
if i > 0 {
|
||||||
|
delta = stack.Timing - b.Stacks[i-1].Timing
|
||||||
|
}
|
||||||
|
fullTrace += fmt.Sprintf("[%d](%s) origin: %s - timing: %s - delta: %s\n", i, stack.HostName, stack.OriginName, stack.Timing.String(), delta.String())
|
||||||
|
}
|
||||||
|
return fullTrace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BlobTrace) Serialize() (string, error) {
|
||||||
|
t, err := json.Marshal(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Err(err)
|
||||||
|
}
|
||||||
|
return string(t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Deserialize(serializedData string) (*BlobTrace, error) {
|
||||||
|
var trace BlobTrace
|
||||||
|
err := json.Unmarshal([]byte(serializedData), &trace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Err(err)
|
||||||
|
}
|
||||||
|
return &trace, nil
|
||||||
|
}
|
36
shared/shared_test.go
Normal file
36
shared/shared_test.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/util"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBlobTrace_Serialize(t *testing.T) {
|
||||||
|
hostName = util.PtrToString("test_machine")
|
||||||
|
stack := NewBlobTrace(10*time.Second, "test")
|
||||||
|
stack.Stack(20*time.Second, "test2")
|
||||||
|
stack.Stack(30*time.Second, "test3")
|
||||||
|
serialized, err := stack.Serialize()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
t.Log(serialized)
|
||||||
|
expected := "{\"stacks\":[{\"timing\":10000000000,\"origin_name\":\"test\",\"host_name\":\"test_machine\"},{\"timing\":20000000000,\"origin_name\":\"test2\",\"host_name\":\"test_machine\"},{\"timing\":30000000000,\"origin_name\":\"test3\",\"host_name\":\"test_machine\"}]}"
|
||||||
|
assert.Equal(t, expected, serialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlobTrace_Deserialize(t *testing.T) {
|
||||||
|
hostName = util.PtrToString("test_machine")
|
||||||
|
serialized := "{\"stacks\":[{\"timing\":10000000000,\"origin_name\":\"test\"},{\"timing\":20000000000,\"origin_name\":\"test2\"},{\"timing\":30000000000,\"origin_name\":\"test3\"}]}"
|
||||||
|
stack, err := Deserialize(serialized)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, stack.Stacks, 3)
|
||||||
|
assert.Equal(t, stack.Stacks[0].Timing, 10*time.Second)
|
||||||
|
assert.Equal(t, stack.Stacks[1].Timing, 20*time.Second)
|
||||||
|
assert.Equal(t, stack.Stacks[2].Timing, 30*time.Second)
|
||||||
|
assert.Equal(t, stack.Stacks[0].OriginName, "test")
|
||||||
|
assert.Equal(t, stack.Stacks[1].OriginName, "test2")
|
||||||
|
assert.Equal(t, stack.Stacks[2].OriginName, "test3")
|
||||||
|
}
|
|
@ -3,10 +3,13 @@ package store
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CachingStore combines two stores, typically a local and a remote store, to improve performance.
|
// CachingStore combines two stores, typically a local and a remote store, to improve performance.
|
||||||
|
@ -22,7 +25,7 @@ func NewCachingStore(component string, origin, cache BlobStore) *CachingStore {
|
||||||
return &CachingStore{
|
return &CachingStore{
|
||||||
component: component,
|
component: component,
|
||||||
origin: WithSingleFlight(component, origin),
|
origin: WithSingleFlight(component, origin),
|
||||||
cache: cache,
|
cache: WithSingleFlight(component, cache),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,9 +45,9 @@ func (c *CachingStore) Has(hash string) (bool, error) {
|
||||||
|
|
||||||
// Get tries to get the blob from the cache first, falling back to the origin. If the blob comes
|
// Get tries to get the blob from the cache first, falling back to the origin. If the blob comes
|
||||||
// from the origin, it is also stored in the cache.
|
// from the origin, it is also stored in the cache.
|
||||||
func (c *CachingStore) Get(hash string) (stream.Blob, error) {
|
func (c *CachingStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
blob, err := c.cache.Get(hash)
|
blob, trace, err := c.cache.Get(hash)
|
||||||
if err == nil || !errors.Is(err, ErrBlobNotFound) {
|
if err == nil || !errors.Is(err, ErrBlobNotFound) {
|
||||||
metrics.CacheHitCount.With(metrics.CacheLabels(c.cache.Name(), c.component)).Inc()
|
metrics.CacheHitCount.With(metrics.CacheLabels(c.cache.Name(), c.component)).Inc()
|
||||||
rate := float64(len(blob)) / 1024 / 1024 / time.Since(start).Seconds()
|
rate := float64(len(blob)) / 1024 / 1024 / time.Since(start).Seconds()
|
||||||
|
@ -53,18 +56,21 @@ func (c *CachingStore) Get(hash string) (stream.Blob, error) {
|
||||||
metrics.LabelComponent: c.component,
|
metrics.LabelComponent: c.component,
|
||||||
metrics.LabelSource: "cache",
|
metrics.LabelSource: "cache",
|
||||||
}).Set(rate)
|
}).Set(rate)
|
||||||
return blob, err
|
return blob, trace.Stack(time.Since(start), c.Name()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics.CacheMissCount.With(metrics.CacheLabels(c.cache.Name(), c.component)).Inc()
|
metrics.CacheMissCount.With(metrics.CacheLabels(c.cache.Name(), c.component)).Inc()
|
||||||
|
|
||||||
blob, err = c.origin.Get(hash)
|
blob, trace, err = c.origin.Get(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, trace.Stack(time.Since(start), c.Name()), err
|
||||||
}
|
}
|
||||||
|
// do not do this async unless you're prepared to deal with mayhem
|
||||||
err = c.cache.Put(hash, blob)
|
err = c.cache.Put(hash, blob)
|
||||||
return blob, err
|
if err != nil {
|
||||||
|
log.Errorf("error saving blob to underlying cache: %s", errors.FullTrace(err))
|
||||||
|
}
|
||||||
|
return blob, trace.Stack(time.Since(start), c.Name()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores the blob in the origin and the cache
|
// Put stores the blob in the origin and the cache
|
||||||
|
@ -93,3 +99,9 @@ func (c *CachingStore) Delete(hash string) error {
|
||||||
}
|
}
|
||||||
return c.cache.Delete(hash)
|
return c.cache.Delete(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (c *CachingStore) Shutdown() {
|
||||||
|
c.origin.Shutdown()
|
||||||
|
c.cache.Shutdown()
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,13 +53,14 @@ func TestCachingStore_CacheMiss(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.Get(hash)
|
res, stack, err := s.Get(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(b, res) {
|
if !bytes.Equal(b, res) {
|
||||||
t.Errorf("expected Get() to return %s, got %s", string(b), string(res))
|
t.Errorf("expected Get() to return %s, got %s", string(b), string(res))
|
||||||
}
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond) //storing to cache is done async so let's give it some time
|
||||||
|
|
||||||
has, err := cache.Has(hash)
|
has, err := cache.Has(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -66,14 +69,16 @@ func TestCachingStore_CacheMiss(t *testing.T) {
|
||||||
if !has {
|
if !has {
|
||||||
t.Errorf("Get() did not copy blob to cache")
|
t.Errorf("Get() did not copy blob to cache")
|
||||||
}
|
}
|
||||||
|
t.Logf("stack: %s", stack.String())
|
||||||
|
|
||||||
res, err = cache.Get(hash)
|
res, stack, err = cache.Get(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(b, res) {
|
if !bytes.Equal(b, res) {
|
||||||
t.Errorf("expected cached Get() to return %s, got %s", string(b), string(res))
|
t.Errorf("expected cached Get() to return %s, got %s", string(b), string(res))
|
||||||
}
|
}
|
||||||
|
t.Logf("stack: %s", stack.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCachingStore_ThunderingHerd(t *testing.T) {
|
func TestCachingStore_ThunderingHerd(t *testing.T) {
|
||||||
|
@ -92,7 +97,7 @@ func TestCachingStore_ThunderingHerd(t *testing.T) {
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
|
|
||||||
getNoErr := func() {
|
getNoErr := func() {
|
||||||
res, err := s.Get(hash)
|
res, _, err := s.Get(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -148,7 +153,7 @@ func (s *SlowBlobStore) Has(hash string) (bool, error) {
|
||||||
return s.mem.Has(hash)
|
return s.mem.Has(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SlowBlobStore) Get(hash string) (stream.Blob, error) {
|
func (s *SlowBlobStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
time.Sleep(s.delay)
|
time.Sleep(s.delay)
|
||||||
return s.mem.Get(hash)
|
return s.mem.Get(hash)
|
||||||
}
|
}
|
||||||
|
@ -167,3 +172,7 @@ func (s *SlowBlobStore) Delete(hash string) error {
|
||||||
time.Sleep(s.delay)
|
time.Sleep(s.delay)
|
||||||
return s.mem.Delete(hash)
|
return s.mem.Delete(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SlowBlobStore) Shutdown() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
"github.com/lbryio/reflector.go/meta"
|
"github.com/lbryio/reflector.go/meta"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
@ -49,30 +50,30 @@ func (c *CloudFrontROStore) Has(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the blob from Cloudfront.
|
// Get gets the blob from Cloudfront.
|
||||||
func (c *CloudFrontROStore) Get(hash string) (stream.Blob, error) {
|
func (c *CloudFrontROStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
log.Debugf("Getting %s from S3", hash[:8])
|
log.Debugf("Getting %s from S3", hash[:8])
|
||||||
|
start := time.Now()
|
||||||
defer func(t time.Time) {
|
defer func(t time.Time) {
|
||||||
log.Debugf("Getting %s from S3 took %s", hash[:8], time.Since(t).String())
|
log.Debugf("Getting %s from S3 took %s", hash[:8], time.Since(t).String())
|
||||||
}(time.Now())
|
}(start)
|
||||||
|
|
||||||
status, body, err := c.cfRequest(http.MethodGet, hash)
|
status, body, err := c.cfRequest(http.MethodGet, hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), c.Name()), err
|
||||||
}
|
}
|
||||||
defer body.Close()
|
defer body.Close()
|
||||||
|
|
||||||
switch status {
|
switch status {
|
||||||
case http.StatusNotFound, http.StatusForbidden:
|
case http.StatusNotFound, http.StatusForbidden:
|
||||||
return nil, errors.Err(ErrBlobNotFound)
|
return nil, shared.NewBlobTrace(time.Since(start), c.Name()), errors.Err(ErrBlobNotFound)
|
||||||
case http.StatusOK:
|
case http.StatusOK:
|
||||||
b, err := ioutil.ReadAll(body)
|
b, err := ioutil.ReadAll(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Err(err)
|
return nil, shared.NewBlobTrace(time.Since(start), c.Name()), errors.Err(err)
|
||||||
}
|
}
|
||||||
metrics.MtrInBytesS3.Add(float64(len(b)))
|
metrics.MtrInBytesS3.Add(float64(len(b)))
|
||||||
return b, nil
|
return b, shared.NewBlobTrace(time.Since(start), c.Name()), nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.Err("unexpected status %d", status)
|
return nil, shared.NewBlobTrace(time.Since(start), c.Name()), errors.Err("unexpected status %d", status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,13 +94,17 @@ func (c *CloudFrontROStore) cfRequest(method, hash string) (int, io.ReadCloser,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CloudFrontROStore) Put(_ string, _ stream.Blob) error {
|
func (c *CloudFrontROStore) Put(_ string, _ stream.Blob) error {
|
||||||
panic("CloudFrontROStore cannot do writes. Use CloudFrontRWStore")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CloudFrontROStore) PutSD(_ string, _ stream.Blob) error {
|
func (c *CloudFrontROStore) PutSD(_ string, _ stream.Blob) error {
|
||||||
panic("CloudFrontROStore cannot do writes. Use CloudFrontRWStore")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CloudFrontROStore) Delete(_ string) error {
|
func (c *CloudFrontROStore) Delete(_ string) error {
|
||||||
panic("CloudFrontROStore cannot do writes. Use CloudFrontRWStore")
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (c *CloudFrontROStore) Shutdown() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CloudFrontRWStore combines a Cloudfront and an S3 store. Reads go to Cloudfront, writes go to S3.
|
// CloudFrontRWStore combines a Cloudfront and an S3 store. Reads go to Cloudfront/Wasabi, writes go to S3.
|
||||||
type CloudFrontRWStore struct {
|
type CloudFrontRWStore struct {
|
||||||
cf *CloudFrontROStore
|
cf *ITTTStore
|
||||||
s3 *S3Store
|
s3 *S3Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCloudFrontRWStore returns an initialized CloudFrontRWStore store pointer.
|
// NewCloudFrontRWStore returns an initialized CloudFrontRWStore store pointer.
|
||||||
// NOTE: It panics if either argument is nil.
|
// NOTE: It panics if either argument is nil.
|
||||||
func NewCloudFrontRWStore(cf *CloudFrontROStore, s3 *S3Store) *CloudFrontRWStore {
|
func NewCloudFrontRWStore(cf *ITTTStore, s3 *S3Store) *CloudFrontRWStore {
|
||||||
if cf == nil || s3 == nil {
|
if cf == nil || s3 == nil {
|
||||||
panic("both stores must be set")
|
panic("both stores must be set")
|
||||||
}
|
}
|
||||||
|
@ -30,8 +34,10 @@ func (c *CloudFrontRWStore) Has(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the blob from Cloudfront.
|
// Get gets the blob from Cloudfront.
|
||||||
func (c *CloudFrontRWStore) Get(hash string) (stream.Blob, error) {
|
func (c *CloudFrontRWStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
return c.cf.Get(hash)
|
start := time.Now()
|
||||||
|
blob, trace, err := c.cf.Get(hash)
|
||||||
|
return blob, trace.Stack(time.Since(start), c.Name()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores the blob on S3
|
// Put stores the blob on S3
|
||||||
|
@ -48,3 +54,9 @@ func (c *CloudFrontRWStore) PutSD(hash string, blob stream.Blob) error {
|
||||||
func (c *CloudFrontRWStore) Delete(hash string) error {
|
func (c *CloudFrontRWStore) Delete(hash string) error {
|
||||||
return c.s3.Delete(hash)
|
return c.s3.Delete(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (c *CloudFrontRWStore) Shutdown() {
|
||||||
|
c.s3.Shutdown()
|
||||||
|
c.cf.Shutdown()
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package store
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/db"
|
"github.com/lbryio/reflector.go/db"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
@ -18,11 +20,12 @@ type DBBackedStore struct {
|
||||||
db *db.SQL
|
db *db.SQL
|
||||||
blockedMu sync.RWMutex
|
blockedMu sync.RWMutex
|
||||||
blocked map[string]bool
|
blocked map[string]bool
|
||||||
|
deleteOnMiss bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDBBackedStore returns an initialized store pointer.
|
// NewDBBackedStore returns an initialized store pointer.
|
||||||
func NewDBBackedStore(blobs BlobStore, db *db.SQL) *DBBackedStore {
|
func NewDBBackedStore(blobs BlobStore, db *db.SQL, deleteOnMiss bool) *DBBackedStore {
|
||||||
return &DBBackedStore{blobs: blobs, db: db}
|
return &DBBackedStore{blobs: blobs, db: db, deleteOnMiss: deleteOnMiss}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameDBBacked = "db-backed"
|
const nameDBBacked = "db-backed"
|
||||||
|
@ -32,20 +35,29 @@ func (d *DBBackedStore) Name() string { return nameDBBacked }
|
||||||
|
|
||||||
// Has returns true if the blob is in the store
|
// Has returns true if the blob is in the store
|
||||||
func (d *DBBackedStore) Has(hash string) (bool, error) {
|
func (d *DBBackedStore) Has(hash string) (bool, error) {
|
||||||
return d.db.HasBlob(hash)
|
return d.db.HasBlob(hash, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets the blob
|
// Get gets the blob
|
||||||
func (d *DBBackedStore) Get(hash string) (stream.Blob, error) {
|
func (d *DBBackedStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
has, err := d.db.HasBlob(hash)
|
start := time.Now()
|
||||||
|
has, err := d.db.HasBlob(hash, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), err
|
||||||
}
|
}
|
||||||
if !has {
|
if !has {
|
||||||
return nil, ErrBlobNotFound
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), ErrBlobNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.blobs.Get(hash)
|
b, stack, err := d.blobs.Get(hash)
|
||||||
|
if d.deleteOnMiss && errors.Is(err, ErrBlobNotFound) {
|
||||||
|
e2 := d.Delete(hash)
|
||||||
|
if e2 != nil {
|
||||||
|
log.Errorf("error while deleting blob from db: %s", errors.FullTrace(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, stack.Stack(time.Since(start), d.Name()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores the blob in the S3 store and stores the blob information in the DB.
|
// Put stores the blob in the S3 store and stores the blob information in the DB.
|
||||||
|
@ -100,7 +112,7 @@ func (d *DBBackedStore) Block(hash string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
has, err := d.db.HasBlob(hash)
|
has, err := d.db.HasBlob(hash, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -182,3 +194,8 @@ func (d *DBBackedStore) initBlocked() error {
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (d *DBBackedStore) Shutdown() {
|
||||||
|
d.blobs.Shutdown()
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
"github.com/lbryio/reflector.go/store/speedwalk"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
"github.com/lbryio/reflector.go/store/speedwalk"
|
|
||||||
|
"github.com/brk0v/directio"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DiskStore stores blobs on a local disk
|
// DiskStore stores blobs on a local disk
|
||||||
|
@ -19,8 +31,12 @@ type DiskStore struct {
|
||||||
|
|
||||||
// true if initOnce ran, false otherwise
|
// true if initOnce ran, false otherwise
|
||||||
initialized bool
|
initialized bool
|
||||||
|
|
||||||
|
concurrentChecks atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxConcurrentChecks = 30
|
||||||
|
|
||||||
// NewDiskStore returns an initialized file disk store pointer.
|
// NewDiskStore returns an initialized file disk store pointer.
|
||||||
func NewDiskStore(dir string, prefixLength int) *DiskStore {
|
func NewDiskStore(dir string, prefixLength int) *DiskStore {
|
||||||
return &DiskStore{
|
return &DiskStore{
|
||||||
|
@ -52,21 +68,40 @@ func (d *DiskStore) Has(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the blob or an error if the blob doesn't exist.
|
// Get returns the blob or an error if the blob doesn't exist.
|
||||||
func (d *DiskStore) Get(hash string) (stream.Blob, error) {
|
func (d *DiskStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
err := d.initOnce()
|
err := d.initOnce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
blob, err := ioutil.ReadFile(d.path(hash))
|
blob, err := ioutil.ReadFile(d.path(hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, errors.Err(ErrBlobNotFound)
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), errors.Err(ErrBlobNotFound)
|
||||||
}
|
}
|
||||||
return nil, errors.Err(err)
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return blob, nil
|
// this is a rather poor yet effective way of throttling how many blobs can be checked concurrently
|
||||||
|
// poor because there is a possible race condition between the check and the actual +1
|
||||||
|
if d.concurrentChecks.Load() < maxConcurrentChecks {
|
||||||
|
d.concurrentChecks.Add(1)
|
||||||
|
defer d.concurrentChecks.Sub(1)
|
||||||
|
hashBytes := sha512.Sum384(blob)
|
||||||
|
readHash := hex.EncodeToString(hashBytes[:])
|
||||||
|
if hash != readHash {
|
||||||
|
message := fmt.Sprintf("[%s] found a broken blob while reading from disk. Actual hash: %s", hash, readHash)
|
||||||
|
log.Errorf("%s", message)
|
||||||
|
err := d.Delete(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), err
|
||||||
|
}
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), d.Name()), errors.Err(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob, shared.NewBlobTrace(time.Since(start), d.Name()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores the blob on disk
|
// Put stores the blob on disk
|
||||||
|
@ -81,7 +116,25 @@ func (d *DiskStore) Put(hash string, blob stream.Blob) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(d.path(hash), blob, 0644)
|
// Open file with O_DIRECT
|
||||||
|
f, err := os.OpenFile(d.tmpPath(hash), openFileFlags, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Use directio writer
|
||||||
|
dio, err := directio.New(f)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
defer dio.Flush()
|
||||||
|
// Write the body to file
|
||||||
|
_, err = io.Copy(dio, bytes.NewReader(blob))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Err(err)
|
||||||
|
}
|
||||||
|
err = os.Rename(d.tmpPath(hash), d.path(hash))
|
||||||
return errors.Err(err)
|
return errors.Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,11 +178,15 @@ func (d *DiskStore) dir(hash string) string {
|
||||||
}
|
}
|
||||||
return path.Join(d.blobDir, hash[:d.prefixLength])
|
return path.Join(d.blobDir, hash[:d.prefixLength])
|
||||||
}
|
}
|
||||||
|
func (d *DiskStore) tmpDir(hash string) string {
|
||||||
|
return path.Join(d.blobDir, "tmp")
|
||||||
|
}
|
||||||
func (d *DiskStore) path(hash string) string {
|
func (d *DiskStore) path(hash string) string {
|
||||||
return path.Join(d.dir(hash), hash)
|
return path.Join(d.dir(hash), hash)
|
||||||
}
|
}
|
||||||
|
func (d *DiskStore) tmpPath(hash string) string {
|
||||||
|
return path.Join(d.tmpDir(hash), hash)
|
||||||
|
}
|
||||||
func (d *DiskStore) ensureDirExists(dir string) error {
|
func (d *DiskStore) ensureDirExists(dir string) error {
|
||||||
return errors.Err(os.MkdirAll(dir, 0755))
|
return errors.Err(os.MkdirAll(dir, 0755))
|
||||||
}
|
}
|
||||||
|
@ -143,7 +200,14 @@ func (d *DiskStore) initOnce() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = d.ensureDirExists(path.Join(d.blobDir, "tmp"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
d.initialized = true
|
d.initialized = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (d *DiskStore) Shutdown() {
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestDiskStore_Get(t *testing.T) {
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
d := NewDiskStore(tmpDir, 2)
|
d := NewDiskStore(tmpDir, 2)
|
||||||
|
|
||||||
hash := "1234567890"
|
hash := "f428b8265d65dad7f8ffa52922bba836404cbd62f3ecfe10adba6b444f8f658938e54f5981ac4de39644d5b93d89a94b"
|
||||||
data := []byte("oyuntyausntoyaunpdoyruoyduanrstjwfjyuwf")
|
data := []byte("oyuntyausntoyaunpdoyruoyduanrstjwfjyuwf")
|
||||||
|
|
||||||
expectedPath := path.Join(tmpDir, hash[:2], hash)
|
expectedPath := path.Join(tmpDir, hash[:2], hash)
|
||||||
|
@ -28,7 +28,7 @@ func TestDiskStore_Get(t *testing.T) {
|
||||||
err = ioutil.WriteFile(expectedPath, data, os.ModePerm)
|
err = ioutil.WriteFile(expectedPath, data, os.ModePerm)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
blob, err := d.Get(hash)
|
blob, _, err := d.Get(hash)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.EqualValues(t, data, blob)
|
assert.EqualValues(t, data, blob)
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ func TestDiskStore_GetNonexistentBlob(t *testing.T) {
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
d := NewDiskStore(tmpDir, 2)
|
d := NewDiskStore(tmpDir, 2)
|
||||||
|
|
||||||
blob, err := d.Get("nonexistent")
|
blob, _, err := d.Get("nonexistent")
|
||||||
assert.Nil(t, blob)
|
assert.Nil(t, blob)
|
||||||
assert.True(t, errors.Is(err, ErrBlobNotFound))
|
assert.True(t, errors.Is(err, ErrBlobNotFound))
|
||||||
}
|
}
|
||||||
|
|
9
store/flags_darwin.go
Normal file
9
store/flags_darwin.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var openFileFlags = os.O_WRONLY | os.O_CREATE
|
10
store/flags_linux.go
Normal file
10
store/flags_linux.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var openFileFlags = os.O_WRONLY | os.O_CREATE | syscall.O_DIRECT
|
159
store/gcache.go
Normal file
159
store/gcache.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
|
||||||
|
"github.com/bluele/gcache"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GcacheStore adds a max cache size and Greedy-Dual-Size-Frequency cache eviction strategy to a BlobStore
|
||||||
|
type GcacheStore struct {
|
||||||
|
// underlying store
|
||||||
|
store BlobStore
|
||||||
|
// cache implementation
|
||||||
|
cache gcache.Cache
|
||||||
|
}
|
||||||
|
type EvictionStrategy int
|
||||||
|
|
||||||
|
const (
|
||||||
|
//LFU Discards the least frequently used items first.
|
||||||
|
LFU EvictionStrategy = iota
|
||||||
|
//ARC Constantly balances between LRU and LFU, to improve the combined result.
|
||||||
|
ARC
|
||||||
|
//LRU Discards the least recently used items first.
|
||||||
|
LRU
|
||||||
|
//SIMPLE has no clear priority for evict cache. It depends on key-value map order.
|
||||||
|
SIMPLE
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGcacheStore initialize a new LRUStore
|
||||||
|
func NewGcacheStore(component string, store BlobStore, maxSize int, strategy EvictionStrategy) *GcacheStore {
|
||||||
|
cacheBuilder := gcache.New(maxSize)
|
||||||
|
var cache gcache.Cache
|
||||||
|
evictFunc := func(key interface{}, value interface{}) {
|
||||||
|
logrus.Infof("evicting %s", key)
|
||||||
|
metrics.CacheLRUEvictCount.With(metrics.CacheLabels(store.Name(), component)).Inc()
|
||||||
|
_ = store.Delete(key.(string)) // TODO: log this error. may happen if underlying entry is gone but cache entry still there
|
||||||
|
}
|
||||||
|
switch strategy {
|
||||||
|
case LFU:
|
||||||
|
cache = cacheBuilder.LFU().EvictedFunc(evictFunc).Build()
|
||||||
|
case ARC:
|
||||||
|
cache = cacheBuilder.ARC().EvictedFunc(evictFunc).Build()
|
||||||
|
case LRU:
|
||||||
|
cache = cacheBuilder.LRU().EvictedFunc(evictFunc).Build()
|
||||||
|
case SIMPLE:
|
||||||
|
cache = cacheBuilder.Simple().EvictedFunc(evictFunc).Build()
|
||||||
|
|
||||||
|
}
|
||||||
|
l := &GcacheStore{
|
||||||
|
store: store,
|
||||||
|
cache: cache,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
if lstr, ok := store.(lister); ok {
|
||||||
|
err := l.loadExisting(lstr, int(maxSize))
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // TODO: what should happen here? panic? return nil? just keep going?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameGcache = "gcache"
|
||||||
|
|
||||||
|
// Name is the cache type name
|
||||||
|
func (l *GcacheStore) Name() string { return nameGcache }
|
||||||
|
|
||||||
|
// Has returns whether the blob is in the store, without updating the recent-ness.
|
||||||
|
func (l *GcacheStore) Has(hash string) (bool, error) {
|
||||||
|
return l.cache.Has(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the blob or an error if the blob doesn't exist.
|
||||||
|
func (l *GcacheStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
|
_, err := l.cache.Get(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), l.Name()), errors.Err(ErrBlobNotFound)
|
||||||
|
}
|
||||||
|
blob, stack, err := l.store.Get(hash)
|
||||||
|
if errors.Is(err, ErrBlobNotFound) {
|
||||||
|
// Blob disappeared from underlying store
|
||||||
|
l.cache.Remove(hash)
|
||||||
|
}
|
||||||
|
return blob, stack.Stack(time.Since(start), l.Name()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put stores the blob. Following LFUDA rules it's not guaranteed that a SET will store the value!!!
|
||||||
|
func (l *GcacheStore) Put(hash string, blob stream.Blob) error {
|
||||||
|
_ = l.cache.Set(hash, true)
|
||||||
|
has, _ := l.Has(hash)
|
||||||
|
if has {
|
||||||
|
err := l.store.Put(hash, blob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutSD stores the sd blob. Following LFUDA rules it's not guaranteed that a SET will store the value!!!
|
||||||
|
func (l *GcacheStore) PutSD(hash string, blob stream.Blob) error {
|
||||||
|
_ = l.cache.Set(hash, true)
|
||||||
|
has, _ := l.Has(hash)
|
||||||
|
if has {
|
||||||
|
err := l.store.PutSD(hash, blob)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the blob from the store
|
||||||
|
func (l *GcacheStore) Delete(hash string) error {
|
||||||
|
err := l.store.Delete(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This must come after store.Delete()
|
||||||
|
// Remove triggers onEvict function, which also tries to delete blob from store
|
||||||
|
// We need to delete it manually first so any errors can be propagated up
|
||||||
|
l.cache.Remove(hash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadExisting imports existing blobs from the underlying store into the LRU cache
|
||||||
|
func (l *GcacheStore) loadExisting(store lister, maxItems int) error {
|
||||||
|
logrus.Infof("loading at most %d items", maxItems)
|
||||||
|
existing, err := store.list()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logrus.Infof("read %d files from underlying store", len(existing))
|
||||||
|
|
||||||
|
added := 0
|
||||||
|
for _, h := range existing {
|
||||||
|
_ = l.cache.Set(h, true)
|
||||||
|
added++
|
||||||
|
if maxItems > 0 && added >= maxItems { // underlying cache is bigger than the cache
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (l *GcacheStore) Shutdown() {
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
|
||||||
|
@ -12,93 +14,80 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cacheMaxBlobs = 3
|
const cacheMaxSize = 3
|
||||||
|
|
||||||
func getTestLRUStore() (*LRUStore, *MemStore) {
|
func getTestGcacheStore() (*GcacheStore, *MemStore) {
|
||||||
m := NewMemStore()
|
m := NewMemStore()
|
||||||
return NewLRUStore("test", m, 3), m
|
return NewGcacheStore("test", m, cacheMaxSize, LFU), m
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUStore_Eviction(t *testing.T) {
|
func TestGcacheStore_Eviction(t *testing.T) {
|
||||||
lru, mem := getTestLRUStore()
|
lfu, mem := getTestGcacheStore()
|
||||||
b := []byte("x")
|
b := []byte("x")
|
||||||
err := lru.Put("one", b)
|
for i := 0; i < 3; i++ {
|
||||||
|
err := lfu.Put(fmt.Sprintf("%d", i), b)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = lru.Put("two", b)
|
for j := 0; j < 3-i; j++ {
|
||||||
|
_, _, err = lfu.Get(fmt.Sprintf("%d", i))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
err = lru.Put("three", b)
|
}
|
||||||
require.NoError(t, err)
|
}
|
||||||
err = lru.Put("four", b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = lru.Put("five", b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, cacheMaxBlobs, len(mem.Debug()))
|
|
||||||
|
|
||||||
for k, v := range map[string]bool{
|
for k, v := range map[string]bool{
|
||||||
"one": false,
|
"0": true,
|
||||||
"two": false,
|
"1": true,
|
||||||
"three": true,
|
"2": true,
|
||||||
"four": true,
|
|
||||||
"five": true,
|
|
||||||
"six": false,
|
|
||||||
} {
|
} {
|
||||||
has, err := lru.Has(k)
|
has, err := lfu.Has(k)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, v, has)
|
assert.Equal(t, v, has)
|
||||||
}
|
}
|
||||||
|
err := lfu.Put("3", b)
|
||||||
lru.Get("three") // touch so it stays in cache
|
require.NoError(t, err)
|
||||||
lru.Put("six", b)
|
|
||||||
|
|
||||||
assert.Equal(t, cacheMaxBlobs, len(mem.Debug()))
|
|
||||||
|
|
||||||
for k, v := range map[string]bool{
|
for k, v := range map[string]bool{
|
||||||
"one": false,
|
"0": true,
|
||||||
"two": false,
|
"1": true,
|
||||||
"three": true,
|
"2": false,
|
||||||
"four": false,
|
"3": true,
|
||||||
"five": true,
|
|
||||||
"six": true,
|
|
||||||
} {
|
} {
|
||||||
has, err := lru.Has(k)
|
has, err := lfu.Has(k)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, v, has)
|
assert.Equal(t, v, has)
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, cacheMaxSize, len(mem.Debug()))
|
||||||
|
|
||||||
err = lru.Delete("three")
|
err = lfu.Delete("0")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = lru.Delete("five")
|
err = lfu.Delete("1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = lru.Delete("six")
|
err = lfu.Delete("3")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 0, len(mem.Debug()))
|
assert.Equal(t, 0, len(mem.Debug()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUStore_UnderlyingBlobMissing(t *testing.T) {
|
func TestGcacheStore_UnderlyingBlobMissing(t *testing.T) {
|
||||||
lru, mem := getTestLRUStore()
|
lfu, mem := getTestGcacheStore()
|
||||||
hash := "hash"
|
hash := "hash"
|
||||||
b := []byte("this is a blob of stuff")
|
b := []byte("this is a blob of stuff")
|
||||||
err := lru.Put(hash, b)
|
err := lfu.Put(hash, b)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = mem.Delete(hash)
|
err = mem.Delete(hash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// hash still exists in lru
|
// hash still exists in lru
|
||||||
assert.True(t, lru.lru.Contains(hash))
|
assert.True(t, lfu.cache.Has(hash))
|
||||||
|
|
||||||
blob, err := lru.Get(hash)
|
blob, _, err := lfu.Get(hash)
|
||||||
assert.Nil(t, blob)
|
assert.Nil(t, blob)
|
||||||
assert.True(t, errors.Is(err, ErrBlobNotFound), "expected (%s) %s, got (%s) %s",
|
assert.True(t, errors.Is(err, ErrBlobNotFound), "expected (%s) %s, got (%s) %s",
|
||||||
reflect.TypeOf(ErrBlobNotFound).String(), ErrBlobNotFound.Error(),
|
reflect.TypeOf(ErrBlobNotFound).String(), ErrBlobNotFound.Error(),
|
||||||
reflect.TypeOf(err).String(), err.Error())
|
reflect.TypeOf(err).String(), err.Error())
|
||||||
|
|
||||||
// lru.Get() removes hash if underlying store doesn't have it
|
// lru.Get() removes hash if underlying store doesn't have it
|
||||||
assert.False(t, lru.lru.Contains(hash))
|
assert.False(t, lfu.cache.Has(hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUStore_loadExisting(t *testing.T) {
|
func TestGcacheStore_loadExisting(t *testing.T) {
|
||||||
tmpDir, err := ioutil.TempDir("", "reflector_test_*")
|
tmpDir, err := ioutil.TempDir("", "reflector_test_*")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(tmpDir)
|
defer os.RemoveAll(tmpDir)
|
||||||
|
@ -114,8 +103,9 @@ func TestLRUStore_loadExisting(t *testing.T) {
|
||||||
require.Equal(t, 1, len(existing), "blob should exist in cache")
|
require.Equal(t, 1, len(existing), "blob should exist in cache")
|
||||||
assert.Equal(t, hash, existing[0])
|
assert.Equal(t, hash, existing[0])
|
||||||
|
|
||||||
lru := NewLRUStore("test", d, 3) // lru should load existing blobs when it's created
|
lfu := NewGcacheStore("test", d, 3, LFU) // lru should load existing blobs when it's created
|
||||||
has, err := lru.Has(hash)
|
time.Sleep(100 * time.Millisecond) // async load so let's wait...
|
||||||
|
has, err := lfu.Has(hash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, has, "hash should be loaded from disk store but it's not")
|
assert.True(t, has, "hash should be loaded from disk store but it's not")
|
||||||
}
|
}
|
160
store/http.go
Normal file
160
store/http.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HttpStore is a store that works on top of the HTTP protocol
|
||||||
|
type HttpStore struct {
|
||||||
|
upstream string
|
||||||
|
httpClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHttpStore(upstream string) *HttpStore {
|
||||||
|
return &HttpStore{
|
||||||
|
upstream: "http://" + upstream,
|
||||||
|
httpClient: getClient(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameHttp = "http"
|
||||||
|
|
||||||
|
func (n *HttpStore) Name() string { return nameHttp }
|
||||||
|
func (n *HttpStore) Has(hash string) (bool, error) {
|
||||||
|
url := n.upstream + "/blob?hash=" + hash
|
||||||
|
|
||||||
|
req, err := http.NewRequest("HEAD", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := n.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Err(err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if res.StatusCode == http.StatusNoContent {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
var body []byte
|
||||||
|
if res.Body != nil {
|
||||||
|
body, _ = ioutil.ReadAll(res.Body)
|
||||||
|
}
|
||||||
|
return false, errors.Err("upstream error. Status code: %d (%s)", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *HttpStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
|
url := n.upstream + "/blob?hash=" + hash
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), n.Name()), errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := n.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), n.Name()), errors.Err(err)
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
tmp := getBuffer()
|
||||||
|
defer putBuffer(tmp)
|
||||||
|
serialized := res.Header.Get("Via")
|
||||||
|
trace := shared.NewBlobTrace(time.Since(start), n.Name())
|
||||||
|
if serialized != "" {
|
||||||
|
parsedTrace, err := shared.Deserialize(serialized)
|
||||||
|
if err != nil {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), n.Name()), err
|
||||||
|
}
|
||||||
|
trace = *parsedTrace
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
return nil, trace.Stack(time.Since(start), n.Name()), ErrBlobNotFound
|
||||||
|
}
|
||||||
|
if res.StatusCode == http.StatusOK {
|
||||||
|
written, err := io.Copy(tmp, res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, trace.Stack(time.Since(start), n.Name()), errors.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blob := make([]byte, written)
|
||||||
|
copy(blob, tmp.Bytes())
|
||||||
|
metrics.MtrInBytesHttp.Add(float64(len(blob)))
|
||||||
|
return blob, trace.Stack(time.Since(start), n.Name()), nil
|
||||||
|
}
|
||||||
|
var body []byte
|
||||||
|
if res.Body != nil {
|
||||||
|
body, _ = ioutil.ReadAll(res.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, trace.Stack(time.Since(start), n.Name()), errors.Err("upstream error. Status code: %d (%s)", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *HttpStore) Put(string, stream.Blob) error {
|
||||||
|
return shared.ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (n *HttpStore) PutSD(string, stream.Blob) error {
|
||||||
|
return shared.ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (n *HttpStore) Delete(string) error {
|
||||||
|
return shared.ErrNotImplemented
|
||||||
|
}
|
||||||
|
func (n *HttpStore) Shutdown() {}
|
||||||
|
|
||||||
|
// buffer pool to reduce GC
|
||||||
|
// https://www.captaincodeman.com/2017/06/02/golang-buffer-pool-gotcha
|
||||||
|
var buffers = sync.Pool{
|
||||||
|
// New is called when a new instance is needed
|
||||||
|
New: func() interface{} {
|
||||||
|
buf := make([]byte, 0, stream.MaxBlobSize)
|
||||||
|
return bytes.NewBuffer(buf)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBuffer fetches a buffer from the pool
|
||||||
|
func getBuffer() *bytes.Buffer {
|
||||||
|
return buffers.Get().(*bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// putBuffer returns a buffer to the pool
|
||||||
|
func putBuffer(buf *bytes.Buffer) {
|
||||||
|
buf.Reset()
|
||||||
|
buffers.Put(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getClient gets an http client that's customized to be more performant when dealing with blobs of 2MB in size (most of our blobs)
|
||||||
|
func getClient() *http.Client {
|
||||||
|
// Customize the Transport to have larger connection pool
|
||||||
|
defaultTransport := &http.Transport{
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
DisableCompression: true,
|
||||||
|
MaxIdleConnsPerHost: 100,
|
||||||
|
ReadBufferSize: stream.MaxBlobSize + 1024*10, //add an extra few KBs to make sure it fits the extra information
|
||||||
|
}
|
||||||
|
|
||||||
|
return &http.Client{Transport: defaultTransport}
|
||||||
|
}
|
73
store/ittt.go
Normal file
73
store/ittt.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ITTTStore performs an operation on this storage, if this fails, it attempts to run it on that
|
||||||
|
type ITTTStore struct {
|
||||||
|
this, that BlobStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewITTTStore returns a new instance of the IF THIS THAN THAT store
|
||||||
|
func NewITTTStore(this, that BlobStore) *ITTTStore {
|
||||||
|
return &ITTTStore{
|
||||||
|
this: this,
|
||||||
|
that: that,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameIttt = "ittt"
|
||||||
|
|
||||||
|
// Name is the cache type name
|
||||||
|
func (c *ITTTStore) Name() string { return nameIttt }
|
||||||
|
|
||||||
|
// Has checks in this for a hash, if it fails it checks in that. It returns true if either store has it.
|
||||||
|
func (c *ITTTStore) Has(hash string) (bool, error) {
|
||||||
|
has, err := c.this.Has(hash)
|
||||||
|
if err != nil || !has {
|
||||||
|
has, err = c.that.Has(hash)
|
||||||
|
}
|
||||||
|
return has, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tries to get the blob from this first, falling back to that.
|
||||||
|
func (c *ITTTStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
|
blob, trace, err := c.this.Get(hash)
|
||||||
|
if err == nil {
|
||||||
|
metrics.ThisHitCount.Inc()
|
||||||
|
return blob, trace.Stack(time.Since(start), c.Name()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
blob, trace, err = c.that.Get(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, trace.Stack(time.Since(start), c.Name()), err
|
||||||
|
}
|
||||||
|
metrics.ThatHitCount.Inc()
|
||||||
|
return blob, trace.Stack(time.Since(start), c.Name()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put not implemented
|
||||||
|
func (c *ITTTStore) Put(hash string, blob stream.Blob) error {
|
||||||
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutSD not implemented
|
||||||
|
func (c *ITTTStore) PutSD(hash string, blob stream.Blob) error {
|
||||||
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete not implemented
|
||||||
|
func (c *ITTTStore) Delete(hash string) error {
|
||||||
|
return errors.Err(shared.ErrNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (c *ITTTStore) Shutdown() {}
|
120
store/lru.go
120
store/lru.go
|
@ -1,120 +0,0 @@
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
|
||||||
|
|
||||||
golru "github.com/hashicorp/golang-lru"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LRUStore adds a max cache size and LRU eviction to a BlobStore
|
|
||||||
type LRUStore struct {
|
|
||||||
// underlying store
|
|
||||||
store BlobStore
|
|
||||||
// lru implementation
|
|
||||||
lru *golru.Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLRUStore initialize a new LRUStore
|
|
||||||
func NewLRUStore(component string, store BlobStore, maxItems int) *LRUStore {
|
|
||||||
lru, err := golru.NewWithEvict(maxItems, func(key interface{}, value interface{}) {
|
|
||||||
metrics.CacheLRUEvictCount.With(metrics.CacheLabels(store.Name(), component)).Inc()
|
|
||||||
_ = store.Delete(key.(string)) // TODO: log this error. may happen if underlying entry is gone but cache entry still there
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l := &LRUStore{
|
|
||||||
store: store,
|
|
||||||
lru: lru,
|
|
||||||
}
|
|
||||||
|
|
||||||
if lstr, ok := store.(lister); ok {
|
|
||||||
err = l.loadExisting(lstr, maxItems)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // TODO: what should happen here? panic? return nil? just keep going?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameLRU = "lru"
|
|
||||||
|
|
||||||
// Name is the cache type name
|
|
||||||
func (l *LRUStore) Name() string { return nameLRU }
|
|
||||||
|
|
||||||
// Has returns whether the blob is in the store, without updating the recent-ness.
|
|
||||||
func (l *LRUStore) Has(hash string) (bool, error) {
|
|
||||||
return l.lru.Contains(hash), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the blob or an error if the blob doesn't exist.
|
|
||||||
func (l *LRUStore) Get(hash string) (stream.Blob, error) {
|
|
||||||
_, has := l.lru.Get(hash)
|
|
||||||
if !has {
|
|
||||||
return nil, errors.Err(ErrBlobNotFound)
|
|
||||||
}
|
|
||||||
blob, err := l.store.Get(hash)
|
|
||||||
if errors.Is(err, ErrBlobNotFound) {
|
|
||||||
// Blob disappeared from underlying store
|
|
||||||
l.lru.Remove(hash)
|
|
||||||
}
|
|
||||||
return blob, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put stores the blob
|
|
||||||
func (l *LRUStore) Put(hash string, blob stream.Blob) error {
|
|
||||||
err := l.store.Put(hash, blob)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.lru.Add(hash, true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutSD stores the sd blob
|
|
||||||
func (l *LRUStore) PutSD(hash string, blob stream.Blob) error {
|
|
||||||
err := l.store.PutSD(hash, blob)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
l.lru.Add(hash, true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the blob from the store
|
|
||||||
func (l *LRUStore) Delete(hash string) error {
|
|
||||||
err := l.store.Delete(hash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// This must come after store.Delete()
|
|
||||||
// Remove triggers onEvict function, which also tries to delete blob from store
|
|
||||||
// We need to delete it manually first so any errors can be propagated up
|
|
||||||
l.lru.Remove(hash)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadExisting imports existing blobs from the underlying store into the LRU cache
|
|
||||||
func (l *LRUStore) loadExisting(store lister, maxItems int) error {
|
|
||||||
existing, err := store.list()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
added := 0
|
|
||||||
for _, h := range existing {
|
|
||||||
l.lru.Add(h, true)
|
|
||||||
added++
|
|
||||||
if maxItems > 0 && added >= maxItems { // underlying cache is bigger than LRU cache
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -2,6 +2,9 @@ package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
@ -34,14 +37,15 @@ func (m *MemStore) Has(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the blob byte slice if present and errors if the blob is not found.
|
// Get returns the blob byte slice if present and errors if the blob is not found.
|
||||||
func (m *MemStore) Get(hash string) (stream.Blob, error) {
|
func (m *MemStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
blob, ok := m.blobs[hash]
|
blob, ok := m.blobs[hash]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.Err(ErrBlobNotFound)
|
return nil, shared.NewBlobTrace(time.Since(start), m.Name()), errors.Err(ErrBlobNotFound)
|
||||||
}
|
}
|
||||||
return blob, nil
|
return blob, shared.NewBlobTrace(time.Since(start), m.Name()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores the blob in memory
|
// Put stores the blob in memory
|
||||||
|
@ -71,3 +75,6 @@ func (m *MemStore) Debug() map[string]stream.Blob {
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
return m.blobs
|
return m.blobs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (m *MemStore) Shutdown() {}
|
||||||
|
|
|
@ -25,7 +25,7 @@ func TestMemStore_Get(t *testing.T) {
|
||||||
t.Error("error getting memory blob - ", err)
|
t.Error("error getting memory blob - ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gotBlob, err := s.Get(hash)
|
gotBlob, _, err := s.Get(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got %v", err)
|
t.Errorf("Expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ func TestMemStore_Get(t *testing.T) {
|
||||||
t.Error("Got blob that is different from expected blob")
|
t.Error("Got blob that is different from expected blob")
|
||||||
}
|
}
|
||||||
|
|
||||||
missingBlob, err := s.Get("nonexistent hash")
|
missingBlob, _, err := s.Get("nonexistent hash")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected ErrBlobNotFound, got nil")
|
t.Errorf("Expected ErrBlobNotFound, got nil")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import "github.com/lbryio/lbry.go/v2/stream"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
)
|
||||||
|
|
||||||
// NoopStore is a store that does nothing
|
// NoopStore is a store that does nothing
|
||||||
type NoopStore struct{}
|
type NoopStore struct{}
|
||||||
|
@ -9,7 +15,10 @@ const nameNoop = "noop"
|
||||||
|
|
||||||
func (n *NoopStore) Name() string { return nameNoop }
|
func (n *NoopStore) Name() string { return nameNoop }
|
||||||
func (n *NoopStore) Has(_ string) (bool, error) { return false, nil }
|
func (n *NoopStore) Has(_ string) (bool, error) { return false, nil }
|
||||||
func (n *NoopStore) Get(_ string) (stream.Blob, error) { return nil, nil }
|
func (n *NoopStore) Get(_ string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(time.Now()), n.Name()), nil
|
||||||
|
}
|
||||||
func (n *NoopStore) Put(_ string, _ stream.Blob) error { return nil }
|
func (n *NoopStore) Put(_ string, _ stream.Blob) error { return nil }
|
||||||
func (n *NoopStore) PutSD(_ string, _ stream.Blob) error { return nil }
|
func (n *NoopStore) PutSD(_ string, _ stream.Blob) error { return nil }
|
||||||
func (n *NoopStore) Delete(_ string) error { return nil }
|
func (n *NoopStore) Delete(_ string) error { return nil }
|
||||||
|
func (n *NoopStore) Shutdown() { return }
|
||||||
|
|
28
store/s3.go
28
store/s3.go
|
@ -5,9 +5,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
|
@ -65,17 +67,18 @@ func (s *S3Store) Has(hash string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the blob slice if present or errors on S3.
|
// Get returns the blob slice if present or errors on S3.
|
||||||
func (s *S3Store) Get(hash string) (stream.Blob, error) {
|
func (s *S3Store) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
|
start := time.Now()
|
||||||
//Todo-Need to handle error for blob doesn't exist for consistency.
|
//Todo-Need to handle error for blob doesn't exist for consistency.
|
||||||
err := s.initOnce()
|
err := s.initOnce()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), s.Name()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Getting %s from S3", hash[:8])
|
log.Debugf("Getting %s from S3", hash[:8])
|
||||||
defer func(t time.Time) {
|
defer func(t time.Time) {
|
||||||
log.Debugf("Getting %s from S3 took %s", hash[:8], time.Since(t).String())
|
log.Debugf("Getting %s from S3 took %s", hash[:8], time.Since(t).String())
|
||||||
}(time.Now())
|
}(start)
|
||||||
|
|
||||||
buf := &aws.WriteAtBuffer{}
|
buf := &aws.WriteAtBuffer{}
|
||||||
_, err = s3manager.NewDownloader(s.session).Download(buf, &s3.GetObjectInput{
|
_, err = s3manager.NewDownloader(s.session).Download(buf, &s3.GetObjectInput{
|
||||||
|
@ -86,15 +89,15 @@ func (s *S3Store) Get(hash string) (stream.Blob, error) {
|
||||||
if aerr, ok := err.(awserr.Error); ok {
|
if aerr, ok := err.(awserr.Error); ok {
|
||||||
switch aerr.Code() {
|
switch aerr.Code() {
|
||||||
case s3.ErrCodeNoSuchBucket:
|
case s3.ErrCodeNoSuchBucket:
|
||||||
return nil, errors.Err("bucket %s does not exist", s.bucket)
|
return nil, shared.NewBlobTrace(time.Since(start), s.Name()), errors.Err("bucket %s does not exist", s.bucket)
|
||||||
case s3.ErrCodeNoSuchKey:
|
case s3.ErrCodeNoSuchKey:
|
||||||
return nil, errors.Err(ErrBlobNotFound)
|
return nil, shared.NewBlobTrace(time.Since(start), s.Name()), errors.Err(ErrBlobNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buf.Bytes(), err
|
return buf.Bytes(), shared.NewBlobTrace(time.Since(start), s.Name()), err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), shared.NewBlobTrace(time.Since(start), s.Name()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores the blob on S3 or errors if S3 connection errors.
|
// Put stores the blob on S3 or errors if S3 connection errors.
|
||||||
|
@ -113,7 +116,8 @@ func (s *S3Store) Put(hash string, blob stream.Blob) error {
|
||||||
Bucket: aws.String(s.bucket),
|
Bucket: aws.String(s.bucket),
|
||||||
Key: aws.String(hash),
|
Key: aws.String(hash),
|
||||||
Body: bytes.NewBuffer(blob),
|
Body: bytes.NewBuffer(blob),
|
||||||
StorageClass: aws.String(s3.StorageClassIntelligentTiering),
|
ACL: aws.String("public-read"),
|
||||||
|
//StorageClass: aws.String(s3.StorageClassIntelligentTiering),
|
||||||
})
|
})
|
||||||
metrics.MtrOutBytesReflector.Add(float64(blob.Size()))
|
metrics.MtrOutBytesReflector.Add(float64(blob.Size()))
|
||||||
|
|
||||||
|
@ -150,6 +154,7 @@ func (s *S3Store) initOnce() error {
|
||||||
sess, err := session.NewSession(&aws.Config{
|
sess, err := session.NewSession(&aws.Config{
|
||||||
Credentials: credentials.NewStaticCredentials(s.awsID, s.awsSecret, ""),
|
Credentials: credentials.NewStaticCredentials(s.awsID, s.awsSecret, ""),
|
||||||
Region: aws.String(s.region),
|
Region: aws.String(s.region),
|
||||||
|
Endpoint: aws.String("https://s3.wasabisys.com"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -158,3 +163,8 @@ func (s *S3Store) initOnce() error {
|
||||||
s.session = sess
|
s.session = sess
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (s *S3Store) Shutdown() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/reflector.go/internal/metrics"
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
|
|
||||||
"golang.org/x/sync/singleflight"
|
"golang.org/x/sync/singleflight"
|
||||||
|
@ -29,39 +31,98 @@ func (s *singleflightStore) Name() string {
|
||||||
return "sf_" + s.BlobStore.Name()
|
return "sf_" + s.BlobStore.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getterResponse struct {
|
||||||
|
blob stream.Blob
|
||||||
|
stack shared.BlobTrace
|
||||||
|
}
|
||||||
|
|
||||||
// Get ensures that only one request per hash is sent to the origin at a time,
|
// Get ensures that only one request per hash is sent to the origin at a time,
|
||||||
// thereby protecting against https://en.wikipedia.org/wiki/Thundering_herd_problem
|
// thereby protecting against https://en.wikipedia.org/wiki/Thundering_herd_problem
|
||||||
func (s *singleflightStore) Get(hash string) (stream.Blob, error) {
|
func (s *singleflightStore) Get(hash string) (stream.Blob, shared.BlobTrace, error) {
|
||||||
metrics.CacheWaitingRequestsCount.With(metrics.CacheLabels(s.BlobStore.Name(), s.component)).Inc()
|
start := time.Now()
|
||||||
defer metrics.CacheWaitingRequestsCount.With(metrics.CacheLabels(s.BlobStore.Name(), s.component)).Dec()
|
metrics.CacheWaitingRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Inc()
|
||||||
|
defer metrics.CacheWaitingRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Dec()
|
||||||
|
|
||||||
blob, err, _ := s.sf.Do(hash, s.getter(hash))
|
gr, err, _ := s.sf.Do(hash, s.getter(hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, shared.NewBlobTrace(time.Since(start), s.Name()), err
|
||||||
}
|
}
|
||||||
return blob.(stream.Blob), nil
|
if gr == nil {
|
||||||
|
return nil, shared.NewBlobTrace(time.Since(start), s.Name()), errors.Err("getter response is nil")
|
||||||
|
}
|
||||||
|
rsp := gr.(getterResponse)
|
||||||
|
return rsp.blob, rsp.stack, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getter returns a function that gets a blob from the origin
|
// getter returns a function that gets a blob from the origin
|
||||||
// only one getter per hash will be executing at a time
|
// only one getter per hash will be executing at a time
|
||||||
func (s *singleflightStore) getter(hash string) func() (interface{}, error) {
|
func (s *singleflightStore) getter(hash string) func() (interface{}, error) {
|
||||||
return func() (interface{}, error) {
|
return func() (interface{}, error) {
|
||||||
metrics.CacheOriginRequestsCount.With(metrics.CacheLabels(s.BlobStore.Name(), s.component)).Inc()
|
metrics.CacheOriginRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Inc()
|
||||||
defer metrics.CacheOriginRequestsCount.With(metrics.CacheLabels(s.BlobStore.Name(), s.component)).Dec()
|
defer metrics.CacheOriginRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Dec()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
blob, err := s.BlobStore.Get(hash)
|
blob, stack, err := s.BlobStore.Get(hash)
|
||||||
|
if err != nil {
|
||||||
|
return getterResponse{
|
||||||
|
blob: nil,
|
||||||
|
stack: stack.Stack(time.Since(start), s.Name()),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rate := float64(len(blob)) / 1024 / 1024 / time.Since(start).Seconds()
|
||||||
|
metrics.CacheRetrievalSpeed.With(map[string]string{
|
||||||
|
metrics.LabelCacheType: s.Name(),
|
||||||
|
metrics.LabelComponent: s.component,
|
||||||
|
metrics.LabelSource: "origin",
|
||||||
|
}).Set(rate)
|
||||||
|
|
||||||
|
return getterResponse{
|
||||||
|
blob: blob,
|
||||||
|
stack: stack.Stack(time.Since(start), s.Name()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put ensures that only one request per hash is sent to the origin at a time,
|
||||||
|
// thereby protecting against https://en.wikipedia.org/wiki/Thundering_herd_problem
|
||||||
|
func (s *singleflightStore) Put(hash string, blob stream.Blob) error {
|
||||||
|
metrics.CacheWaitingRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Inc()
|
||||||
|
defer metrics.CacheWaitingRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Dec()
|
||||||
|
|
||||||
|
_, err, _ := s.sf.Do(hash, s.putter(hash, blob))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putter returns a function that puts a blob from the origin
|
||||||
|
// only one putter per hash will be executing at a time
|
||||||
|
func (s *singleflightStore) putter(hash string, blob stream.Blob) func() (interface{}, error) {
|
||||||
|
return func() (interface{}, error) {
|
||||||
|
metrics.CacheOriginRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Inc()
|
||||||
|
defer metrics.CacheOriginRequestsCount.With(metrics.CacheLabels(s.Name(), s.component)).Dec()
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
err := s.BlobStore.Put(hash, blob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rate := float64(len(blob)) / 1024 / 1024 / time.Since(start).Seconds()
|
rate := float64(len(blob)) / 1024 / 1024 / time.Since(start).Seconds()
|
||||||
metrics.CacheRetrievalSpeed.With(map[string]string{
|
metrics.CacheRetrievalSpeed.With(map[string]string{
|
||||||
metrics.LabelCacheType: s.BlobStore.Name(),
|
metrics.LabelCacheType: s.Name(),
|
||||||
metrics.LabelComponent: s.component,
|
metrics.LabelComponent: s.component,
|
||||||
metrics.LabelSource: "origin",
|
metrics.LabelSource: "origin",
|
||||||
}).Set(rate)
|
}).Set(rate)
|
||||||
|
|
||||||
return blob, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the store gracefully
|
||||||
|
func (s *singleflightStore) Shutdown() {
|
||||||
|
s.BlobStore.Shutdown()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/lbryio/reflector.go/internal/metrics"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
|
|
||||||
"github.com/karrick/godirwalk"
|
"github.com/karrick/godirwalk"
|
||||||
|
@ -24,6 +26,7 @@ func AllFiles(startDir string, basename bool) ([]string, error) {
|
||||||
paths := make([]string, 0, 1000)
|
paths := make([]string, 0, 1000)
|
||||||
pathWG := &sync.WaitGroup{}
|
pathWG := &sync.WaitGroup{}
|
||||||
pathWG.Add(1)
|
pathWG.Add(1)
|
||||||
|
metrics.RoutinesQueue.WithLabelValues("speedwalk", "worker").Inc()
|
||||||
go func() {
|
go func() {
|
||||||
defer pathWG.Done()
|
defer pathWG.Done()
|
||||||
for {
|
for {
|
||||||
|
@ -60,7 +63,6 @@ func AllFiles(startDir string, basename bool) ([]string, error) {
|
||||||
walkerWG.Done()
|
walkerWG.Done()
|
||||||
goroutineLimiter <- struct{}{}
|
goroutineLimiter <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = godirwalk.Walk(filepath.Join(startDir, dir), &godirwalk.Options{
|
err = godirwalk.Walk(filepath.Join(startDir, dir), &godirwalk.Options{
|
||||||
Unsorted: true, // faster this way
|
Unsorted: true, // faster this way
|
||||||
Callback: func(osPathname string, de *godirwalk.Dirent) error {
|
Callback: func(osPathname string, de *godirwalk.Dirent) error {
|
||||||
|
@ -84,6 +86,5 @@ func AllFiles(startDir string, basename bool) ([]string, error) {
|
||||||
|
|
||||||
close(pathChan)
|
close(pathChan)
|
||||||
pathWG.Wait()
|
pathWG.Wait()
|
||||||
|
|
||||||
return paths, nil
|
return paths, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/lbryio/reflector.go/shared"
|
||||||
|
|
||||||
"github.com/lbryio/lbry.go/v2/extras/errors"
|
"github.com/lbryio/lbry.go/v2/extras/errors"
|
||||||
"github.com/lbryio/lbry.go/v2/stream"
|
"github.com/lbryio/lbry.go/v2/stream"
|
||||||
)
|
)
|
||||||
|
@ -9,16 +11,18 @@ import (
|
||||||
type BlobStore interface {
|
type BlobStore interface {
|
||||||
// Name of blob store (useful for metrics)
|
// Name of blob store (useful for metrics)
|
||||||
Name() string
|
Name() string
|
||||||
// Does blob exist in the store.
|
// Has Does blob exist in the store.
|
||||||
Has(hash string) (bool, error)
|
Has(hash string) (bool, error)
|
||||||
// Get the blob from the store. Must return ErrBlobNotFound if blob is not in store.
|
// Get the blob from the store. Must return ErrBlobNotFound if blob is not in store.
|
||||||
Get(hash string) (stream.Blob, error)
|
Get(hash string) (stream.Blob, shared.BlobTrace, error)
|
||||||
// Put the blob into the store.
|
// Put the blob into the store.
|
||||||
Put(hash string, blob stream.Blob) error
|
Put(hash string, blob stream.Blob) error
|
||||||
// Put an SD blob into the store.
|
// PutSD an SD blob into the store.
|
||||||
PutSD(hash string, blob stream.Blob) error
|
PutSD(hash string, blob stream.Blob) error
|
||||||
// Delete the blob from the store.
|
// Delete the blob from the store.
|
||||||
Delete(hash string) error
|
Delete(hash string) error
|
||||||
|
// Shutdown the store gracefully
|
||||||
|
Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocklister is a store that supports blocking blobs to prevent their inclusion in the store.
|
// Blocklister is a store that supports blocking blobs to prevent their inclusion in the store.
|
||||||
|
|
Loading…
Reference in a new issue