mirror of
https://github.com/sot-tech/mochi.git
synced 2026-07-18 06:08:10 -07:00
(tested) complete replace logrus with zerolog
* remove cobra dependency and split execs to mochi and e2e * add log init synchronization
This commit is contained in:
@@ -1,82 +1,50 @@
|
||||
//go:build e2e
|
||||
// +build e2e
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/torrent/tracker"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/sot-tech/mochi/bittorrent"
|
||||
"github.com/sot-tech/mochi/pkg/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
e2eCmd = &cobra.Command{
|
||||
Use: "e2e",
|
||||
Short: "exec e2e tests",
|
||||
Long: "Execute the Conf end-to-end test suite",
|
||||
RunE: EndToEndRunCmdFunc,
|
||||
}
|
||||
func main() {
|
||||
httpAddress := flag.String("httpaddr", "http://127.0.0.1:6969/announce", "address of the HTTP tracker")
|
||||
udpAddress := flag.String("udpaddr", "udp://127.0.0.1:6969", "address of the UDP tracker")
|
||||
delay := flag.Duration("delay", time.Second, "delay between announces")
|
||||
flag.Parse()
|
||||
|
||||
e2eCmd.Flags().String("httpaddr", "http://127.0.0.1:6969/announce", "address of the HTTP tracker")
|
||||
e2eCmd.Flags().String("udpaddr", "udp://127.0.0.1:6969", "address of the UDP tracker")
|
||||
e2eCmd.Flags().Duration("delay", time.Second, "delay between announces")
|
||||
}
|
||||
|
||||
// EndToEndRunCmdFunc implements a Cobra command that runs the end-to-end test
|
||||
// suite for a Conf build.
|
||||
func EndToEndRunCmdFunc(cmd *cobra.Command, args []string) error {
|
||||
delay, err := cmd.Flags().GetDuration("delay")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Test the HTTP tracker
|
||||
httpAddr, err := cmd.Flags().GetString("httpaddr")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(httpAddr) != 0 {
|
||||
log.Info("testing HTTP...")
|
||||
err := test(httpAddr, delay)
|
||||
if len(*httpAddress) != 0 {
|
||||
log.Println("testing HTTP...")
|
||||
err := test(*httpAddress, *delay)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Info("success")
|
||||
log.Println("success")
|
||||
}
|
||||
|
||||
// Test the UDP tracker.
|
||||
udpAddr, err := cmd.Flags().GetString("udpaddr")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(udpAddr) != 0 {
|
||||
log.Info("testing UDP...")
|
||||
err := test(udpAddr, delay)
|
||||
if len(*udpAddress) != 0 {
|
||||
log.Println("testing UDP...")
|
||||
err := test(*udpAddress, *delay)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Info("success")
|
||||
log.Println("success")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func test(addr string, delay time.Duration) error {
|
||||
b := make([]byte, bittorrent.InfoHashV1Len)
|
||||
rand.Read(b)
|
||||
ih, _ := bittorrent.NewInfoHash(b)
|
||||
return testWithInfohash(ih, addr, delay)
|
||||
return testWithInfoHash(ih, addr, delay)
|
||||
}
|
||||
|
||||
func testWithInfohash(infoHash bittorrent.InfoHash, url string, delay time.Duration) error {
|
||||
func testWithInfoHash(infoHash bittorrent.InfoHash, url string, delay time.Duration) error {
|
||||
var ih [bittorrent.InfoHashV1Len]byte
|
||||
req := tracker.AnnounceRequest{
|
||||
InfoHash: ih,
|
||||
+21
-220
@@ -1,182 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/sot-tech/mochi/frontend/http"
|
||||
"github.com/sot-tech/mochi/frontend/udp"
|
||||
"github.com/sot-tech/mochi/middleware"
|
||||
"github.com/sot-tech/mochi/pkg/conf"
|
||||
"github.com/sot-tech/mochi/pkg/log"
|
||||
"github.com/sot-tech/mochi/pkg/metrics"
|
||||
_ "github.com/sot-tech/mochi/pkg/randseed"
|
||||
"github.com/sot-tech/mochi/pkg/stop"
|
||||
"github.com/sot-tech/mochi/storage"
|
||||
l "github.com/sot-tech/mochi/pkg/log"
|
||||
)
|
||||
|
||||
var e2eCmd *cobra.Command
|
||||
|
||||
// Run represents the state of a running instance of Conf.
|
||||
type Run struct {
|
||||
configFilePath string
|
||||
storage storage.PeerStorage
|
||||
logic *middleware.Logic
|
||||
sg *stop.Group
|
||||
}
|
||||
|
||||
// NewRun runs an instance of Conf.
|
||||
func NewRun(configFilePath string) (*Run, error) {
|
||||
r := &Run{
|
||||
configFilePath: configFilePath,
|
||||
}
|
||||
|
||||
return r, r.Start(nil)
|
||||
}
|
||||
|
||||
// Start begins an instance of Conf.
|
||||
// It is optional to provide an instance of the peer store to avoid the
|
||||
// creation of a new one.
|
||||
func (r *Run) Start(ps storage.PeerStorage) error {
|
||||
configFile, err := ParseConfigFile(r.configFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read config: %w", err)
|
||||
}
|
||||
cfg := configFile.Conf
|
||||
|
||||
r.sg = stop.NewGroup()
|
||||
|
||||
if len(cfg.MetricsAddr) > 0 {
|
||||
log.Info().Str("addr", cfg.MetricsAddr).Msg("starting metrics server")
|
||||
r.sg.Add(metrics.NewServer(cfg.MetricsAddr))
|
||||
} else {
|
||||
log.Info().Msg("metrics disabled because of empty address")
|
||||
}
|
||||
|
||||
if ps == nil {
|
||||
log.Info().Str("name", cfg.Storage.Name).Msg("starting storage")
|
||||
ps, err = storage.NewStorage(cfg.Storage.Name, cfg.Storage.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create storage: %w", err)
|
||||
}
|
||||
log.Info().Object("config", ps).Msg("started storage")
|
||||
}
|
||||
r.storage = ps
|
||||
|
||||
preHooks, err := middleware.HooksFromHookConfigs(cfg.PreHooks, r.storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate hook config: %w", err)
|
||||
}
|
||||
postHooks, err := middleware.HooksFromHookConfigs(cfg.PostHooks, r.storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate hook config: %w", err)
|
||||
}
|
||||
|
||||
r.logic = middleware.NewLogic(cfg.AnnounceInterval, cfg.MinAnnounceInterval, r.storage, preHooks, postHooks)
|
||||
|
||||
if len(cfg.HTTPConfig) > 0 {
|
||||
log.Info().Object("config", cfg.HTTPConfig).Msg("starting HTTP frontend")
|
||||
httpFE, err := http.NewFrontend(r.logic, cfg.HTTPConfig)
|
||||
if err == nil {
|
||||
r.sg.Add(httpFE)
|
||||
} else if !errors.Is(err, conf.ErrNilConfigMap) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.UDPConfig) > 0 {
|
||||
log.Info().Object("config", cfg.HTTPConfig).Msg("starting UDP frontend")
|
||||
udpFE, err := udp.NewFrontend(r.logic, cfg.UDPConfig)
|
||||
if err == nil {
|
||||
r.sg.Add(udpFE)
|
||||
} else if !errors.Is(err, conf.ErrNilConfigMap) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func combineErrors(prefix string, errs []error) error {
|
||||
errStrs := make([]string, 0, len(errs))
|
||||
for _, err := range errs {
|
||||
errStrs = append(errStrs, err.Error())
|
||||
}
|
||||
|
||||
return errors.New(prefix + ": " + strings.Join(errStrs, "; "))
|
||||
}
|
||||
|
||||
// Stop shuts down an instance of Conf.
|
||||
func (r *Run) Stop(keepPeerStore bool) (storage.PeerStorage, error) {
|
||||
log.Debug().Msg("stopping frontends and metrics server")
|
||||
if errs := r.sg.Stop().Wait(); len(errs) != 0 {
|
||||
return nil, combineErrors("failed while shutting down frontends", errs)
|
||||
}
|
||||
|
||||
log.Debug().Msg("stopping logic")
|
||||
if errs := r.logic.Stop().Wait(); len(errs) != 0 {
|
||||
return nil, combineErrors("failed while shutting down middleware", errs)
|
||||
}
|
||||
|
||||
if !keepPeerStore {
|
||||
log.Debug().Msg("stopping peer store")
|
||||
if errs := r.storage.Stop().Wait(); len(errs) != 0 {
|
||||
return nil, combineErrors("failed while shutting down peer store", errs)
|
||||
}
|
||||
r.storage = nil
|
||||
}
|
||||
|
||||
return r.storage, nil
|
||||
}
|
||||
|
||||
// RootRunCmdFunc implements a Cobra command that runs an instance of Conf
|
||||
// and handles reloading and shutdown via process signals.
|
||||
func RootRunCmdFunc(cmd *cobra.Command, _ []string) error {
|
||||
configFilePath, err := cmd.Flags().GetString(configArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := NewRun(configFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
shutdown, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
reload, _ := signal.NotifyContext(context.Background(), ReloadSignals...)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-reload.Done():
|
||||
log.Info().Msg("reloading; received reload signal")
|
||||
peerStore, err := r.Stop(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.Start(peerStore); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-shutdown.Done():
|
||||
log.Info().Msg("shutting down; received shutdown signal")
|
||||
if _, err := r.Stop(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
appName = "mochi"
|
||||
logOutArg = "logOut"
|
||||
logLevelArg = "logLevel"
|
||||
logPrettyArg = "logPretty"
|
||||
@@ -184,59 +19,25 @@ const (
|
||||
configArg = "config"
|
||||
)
|
||||
|
||||
// configureLogger handles command line flags for the logger.
|
||||
func configureLogger(cmd *cobra.Command, _ []string) (err error) {
|
||||
var out, lvl string
|
||||
var pretty, colored bool
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
out, err = flags.GetString(logOutArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lvl, err = flags.GetString(logLevelArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pretty, err = flags.GetBool(logPrettyArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
colored, err = cmd.Flags().GetBool(logColorsArg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return log.ConfigureLogger(out, lvl, pretty, colored)
|
||||
}
|
||||
|
||||
func main() {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: appName,
|
||||
Short: "BitTorrent Tracker",
|
||||
Long: "A customizable, multi-protocol BitTorrent Tracker",
|
||||
PersistentPreRunE: configureLogger,
|
||||
RunE: RootRunCmdFunc,
|
||||
var s Server
|
||||
|
||||
logOut := flag.String(logOutArg, "stderr", "output for logging, might be 'stderr', 'stdout' or file path")
|
||||
logLevel := flag.String(logLevelArg, "warn", "logging level: trace, debug, info, warn, error, fatal, panic")
|
||||
logPretty := flag.Bool(logPrettyArg, false, "enable log pretty print. used only if 'logOut' set to 'stdout' or 'stderr'. if not set, log outputs json")
|
||||
logColored := flag.Bool(logColorsArg, runtime.GOOS == "windows", "enable log coloring. used only if set 'logPretty'")
|
||||
configPath := flag.String(configArg, "/etc/mochi.yaml", "location of configuration file")
|
||||
flag.Parse()
|
||||
|
||||
if err := l.ConfigureLogger(*logOut, *logLevel, *logPretty, *logColored); err != nil {
|
||||
log.Fatal("unable to configure logger ", err)
|
||||
}
|
||||
|
||||
flags := rootCmd.PersistentFlags()
|
||||
|
||||
flags.String(logOutArg, "", "output for logging, might be 'stderr', 'stdout' of file path. 'stderr' if not set")
|
||||
flags.String(logLevelArg, "info", "logging level (trace, debug, info, warn, error, fatal, panic). 'warn' if not set")
|
||||
flags.Bool(logPrettyArg, false, "enable log pretty print. used only if 'logOut' set to 'stdout' or 'stderr'. if not set, log outputs json)")
|
||||
flags.Bool(logColorsArg, runtime.GOOS == "windows", "enable log coloring. used only if set 'logPretty'")
|
||||
|
||||
rootCmd.Flags().String("config", "/etc/mochi.yaml", "location of configuration file")
|
||||
|
||||
if e2eCmd != nil {
|
||||
rootCmd.AddCommand(e2eCmd)
|
||||
}
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatal().Err(err).Msg("failed while executing root command")
|
||||
if err := s.Run(*configPath); err != nil {
|
||||
log.Fatal("unable to start server ", err)
|
||||
}
|
||||
defer s.Dispose()
|
||||
ch := make(chan os.Signal, 2)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||
<-ch
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/sot-tech/mochi/frontend/http"
|
||||
"github.com/sot-tech/mochi/frontend/udp"
|
||||
"github.com/sot-tech/mochi/middleware"
|
||||
"github.com/sot-tech/mochi/pkg/log"
|
||||
"github.com/sot-tech/mochi/pkg/metrics"
|
||||
"github.com/sot-tech/mochi/pkg/stop"
|
||||
"github.com/sot-tech/mochi/storage"
|
||||
)
|
||||
|
||||
// Server represents the state of a running instance.
|
||||
type Server struct {
|
||||
storage storage.PeerStorage
|
||||
logic *middleware.Logic
|
||||
sg *stop.Group
|
||||
}
|
||||
|
||||
// Run begins an instance of Conf.
|
||||
// It is optional to provide an instance of the peer store to avoid the
|
||||
// creation of a new one.
|
||||
func (r *Server) Run(configFilePath string) error {
|
||||
configFile, err := ParseConfigFile(configFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read config: %w", err)
|
||||
}
|
||||
cfg := configFile.Conf
|
||||
|
||||
r.sg = stop.NewGroup()
|
||||
|
||||
if len(cfg.MetricsAddr) > 0 {
|
||||
log.Info().Str("address", cfg.MetricsAddr).Msg("starting metrics server")
|
||||
r.sg.Add(metrics.NewServer(cfg.MetricsAddr))
|
||||
} else {
|
||||
log.Info().Msg("metrics disabled because of empty address")
|
||||
}
|
||||
|
||||
log.Info().Str("name", cfg.Storage.Name).Msg("starting storage")
|
||||
r.storage, err = storage.NewStorage(cfg.Storage.Name, cfg.Storage.Config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create storage: %w", err)
|
||||
}
|
||||
log.Info().Object("config", r.storage).Msg("started storage")
|
||||
|
||||
preHooks, err := middleware.NewHooks(cfg.PreHooks, r.storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate hook config: %w", err)
|
||||
}
|
||||
postHooks, err := middleware.NewHooks(cfg.PostHooks, r.storage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to validate hook config: %w", err)
|
||||
}
|
||||
|
||||
r.logic = middleware.NewLogic(cfg.AnnounceInterval, cfg.MinAnnounceInterval, r.storage, preHooks, postHooks)
|
||||
|
||||
var started bool
|
||||
if len(cfg.HTTPConfig) > 0 {
|
||||
log.Info().Object("config", cfg.HTTPConfig).Msg("starting HTTP frontend")
|
||||
httpFE, err := http.NewFrontend(r.logic, cfg.HTTPConfig)
|
||||
if err == nil {
|
||||
r.sg.Add(httpFE)
|
||||
started = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.UDPConfig) > 0 {
|
||||
log.Info().Object("config", cfg.UDPConfig).Msg("starting UDP frontend")
|
||||
udpFE, err := udp.NewFrontend(r.logic, cfg.UDPConfig)
|
||||
if err == nil {
|
||||
r.sg.Add(udpFE)
|
||||
started = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !started {
|
||||
return errors.New("no frontends configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispose shuts down an instance of Server.
|
||||
func (r *Server) Dispose() {
|
||||
log.Debug().Msg("stopping frontends and metrics server")
|
||||
if errs := r.sg.Stop().Wait(); len(errs) > 0 {
|
||||
log.Error().Errs("errors", errs).Msg("error occurred while shutting down frontends")
|
||||
}
|
||||
|
||||
log.Debug().Msg("stopping logic")
|
||||
if errs := r.logic.Stop().Wait(); len(errs) > 0 {
|
||||
log.Error().Errs("errors", errs).Msg("error occurred while shutting down middlewares")
|
||||
}
|
||||
|
||||
log.Debug().Msg("stopping peer store")
|
||||
if errs := r.storage.Stop().Wait(); len(errs) != 0 {
|
||||
log.Error().Errs("errors", errs).Msg("error occurred while shutting down peer store")
|
||||
}
|
||||
log.Close()
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//go:build darwin || freebsd || linux || netbsd || openbsd || dragonfly || solaris
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ReloadSignals are the signals that the current OS will send to the process
|
||||
// when a configuration reload is requested.
|
||||
var ReloadSignals = []os.Signal{
|
||||
syscall.SIGUSR1,
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ReloadSignals are the signals that the current OS will send to the process
|
||||
// when a configuration reload is requested.
|
||||
var ReloadSignals = []os.Signal{
|
||||
syscall.SIGHUP,
|
||||
}
|
||||
Reference in New Issue
Block a user