github.com/ipni/storetheindex@v0.8.30/assigner/command/daemon.go (about)

     1  package command
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/ipfs/boxo/bootstrap"
    10  	"github.com/ipfs/boxo/peering"
    11  	logging "github.com/ipfs/go-log/v2"
    12  	"github.com/ipni/go-libipni/mautil"
    13  	"github.com/ipni/storetheindex/assigner/config"
    14  	"github.com/ipni/storetheindex/assigner/core"
    15  	server "github.com/ipni/storetheindex/assigner/server"
    16  	sticfg "github.com/ipni/storetheindex/config"
    17  	"github.com/libp2p/go-libp2p"
    18  	"github.com/libp2p/go-libp2p/core/host"
    19  	"github.com/libp2p/go-libp2p/core/network"
    20  	"github.com/multiformats/go-multiaddr"
    21  	"github.com/urfave/cli/v2"
    22  )
    23  
    24  var log = logging.Logger("assigner")
    25  
    26  const progName = "assigner"
    27  
    28  var DaemonCmd = &cli.Command{
    29  	Name:   "daemon",
    30  	Usage:  "Start assigner service daemon",
    31  	Flags:  daemonFlags,
    32  	Action: daemonAction,
    33  }
    34  
    35  var daemonFlags = []cli.Flag{
    36  	&cli.StringFlag{
    37  		Name:     "listen-admin",
    38  		Usage:    "Admin HTTP API listen address",
    39  		EnvVars:  []string{"ASSIGNER_LISTEN_ADMIN"},
    40  		Required: false,
    41  	},
    42  	&cli.StringFlag{
    43  		Name:     "listen-http",
    44  		Usage:    "HTTP listen address",
    45  		EnvVars:  []string{"ASSIGNER_LISTEN_HTTP"},
    46  		Required: false,
    47  	},
    48  	&cli.StringFlag{
    49  		Name:     "listen-p2p",
    50  		Usage:    "P2P listen address",
    51  		EnvVars:  []string{"ASSIGNER_LISTEN_P2P"},
    52  		Required: false,
    53  	},
    54  }
    55  
    56  func daemonAction(cctx *cli.Context) error {
    57  	cfg, err := loadConfig("")
    58  	if err != nil {
    59  		if errors.Is(err, sticfg.ErrNotInitialized) {
    60  			fmt.Fprintf(os.Stderr, "%s is not initialized\n", progName)
    61  			fmt.Fprintf(os.Stderr, "To initialize, run the command: ./%s init\n", progName)
    62  			os.Exit(1)
    63  		}
    64  		return err
    65  	}
    66  
    67  	err = setLoggingConfig(cfg.Logging)
    68  	if err != nil {
    69  		return err
    70  	}
    71  
    72  	if cfg.Version != config.Version {
    73  		log.Warnf("Configuration file out-of-date. Upgrade by running: ./%s init --upgrade", progName)
    74  	}
    75  
    76  	var p2pHost host.Host
    77  	p2pAddr := cfg.Daemon.P2PAddr
    78  	if cctx.String("listen-p2p") != "" {
    79  		p2pAddr = cctx.String("listen-p2p")
    80  	}
    81  	if p2pAddr != "none" {
    82  		_, privKey, err := cfg.Identity.Decode()
    83  		if err != nil {
    84  			return err
    85  		}
    86  		p2pmaddr, err := multiaddr.NewMultiaddr(p2pAddr)
    87  		if err != nil {
    88  			return fmt.Errorf("bad p2p address %s: %w", p2pAddr, err)
    89  		}
    90  		p2pOpts := []libp2p.Option{
    91  			// Use the keypair generated during init
    92  			libp2p.Identity(privKey),
    93  			// Listen at specific address
    94  			libp2p.ListenAddrs(p2pmaddr),
    95  		}
    96  		if cfg.Daemon.NoResourceManager {
    97  			log.Info("libp2p resource manager disabled")
    98  			p2pOpts = append(p2pOpts, libp2p.ResourceManager(&network.NullResourceManager{}))
    99  		}
   100  
   101  		p2pHost, err = libp2p.New(p2pOpts...)
   102  		if err != nil {
   103  			return err
   104  		}
   105  		defer p2pHost.Close()
   106  
   107  		bootstrapper, err := startBootstrapper(cfg.Bootstrap, p2pHost)
   108  		if err != nil {
   109  			return fmt.Errorf("cannot start bootstrapper: %s", err)
   110  		}
   111  		if bootstrapper != nil {
   112  			defer bootstrapper.Close()
   113  		}
   114  
   115  		peeringService, err := startPeering(cfg.Peering, p2pHost)
   116  		if err != nil {
   117  			return fmt.Errorf("cannot start peering service: %s", err)
   118  		}
   119  		if peeringService != nil {
   120  			defer peeringService.Stop()
   121  		}
   122  
   123  		log.Infow("libp2p servers initialized", "host_id", p2pHost.ID(), "multiaddr", p2pmaddr)
   124  	}
   125  
   126  	assigner, err := core.NewAssigner(cctx.Context, cfg.Assignment, p2pHost)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// Create HTTP server
   132  	var httpServer *server.Server
   133  	httpAddr := cfg.Daemon.HTTPAddr
   134  	if cctx.String("listen-http") != "" {
   135  		httpAddr = cctx.String("listen-http")
   136  	}
   137  	if httpAddr != "none" {
   138  		httpNetAddr, err := mautil.MultiaddrStringToNetAddr(httpAddr)
   139  		if err != nil {
   140  			return fmt.Errorf("bad http address %s: %w", httpAddr, err)
   141  		}
   142  
   143  		httpServer, err = server.New(httpNetAddr.String(), assigner,
   144  			server.WithVersion(cctx.App.Version))
   145  		if err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	svrErrChan := make(chan error, 3)
   151  
   152  	log.Info("Starting http servers")
   153  	if httpServer != nil {
   154  		go func() {
   155  			svrErrChan <- httpServer.Start()
   156  		}()
   157  		fmt.Println("http server:\t", httpAddr)
   158  	} else {
   159  		fmt.Println("http server:\t disabled")
   160  	}
   161  
   162  	// Output message to user (not to log).
   163  	fmt.Println("Daemon is ready")
   164  	var finalErr error
   165  
   166  	for endDaemon := false; !endDaemon; {
   167  		select {
   168  		case <-cctx.Done():
   169  			// Command was canceled (ctrl-c)
   170  			endDaemon = true
   171  		case err = <-svrErrChan:
   172  			finalErr = fmt.Errorf("failed to start server: %w", err)
   173  			endDaemon = true
   174  		}
   175  	}
   176  
   177  	log.Infow("Shutting down daemon")
   178  
   179  	if httpServer != nil {
   180  		if err = httpServer.Close(); err != nil {
   181  			finalErr = fmt.Errorf("error shutting down http server: %w", err)
   182  		}
   183  	}
   184  
   185  	if err = assigner.Close(); err != nil {
   186  		finalErr = fmt.Errorf("error closing assigner: %w", err)
   187  	}
   188  
   189  	log.Info("Daemon stopped")
   190  	return finalErr
   191  }
   192  
   193  func setLoggingConfig(cfgLogging config.Logging) error {
   194  	// Set overall log level.
   195  	err := logging.SetLogLevel("*", cfgLogging.Level)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	// Set level for individual loggers.
   201  	for loggerName, level := range cfgLogging.Loggers {
   202  		err = logging.SetLogLevel(loggerName, level)
   203  		if err != nil {
   204  			return err
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  func loadConfig(filePath string) (*config.Config, error) {
   211  	cfg, err := config.Load(filePath)
   212  	if err != nil {
   213  		return nil, fmt.Errorf("cannot load config file: %w", err)
   214  	}
   215  	if cfg.Version != config.Version {
   216  		log.Warnf("Configuration file out-of-date. Upgrade by running: ./%s init --upgrade", progName)
   217  	}
   218  
   219  	return cfg, nil
   220  }
   221  
   222  func startBootstrapper(cfg sticfg.Bootstrap, p2pHost host.Host) (io.Closer, error) {
   223  	// If there are bootstrap peers and bootstrapping is enabled, then try to
   224  	// connect to the minimum set of peers.  This connects the indexer to other
   225  	// nodes in the gossip mesh, allowing it to receive advertisements from
   226  	// providers.
   227  	if len(cfg.Peers) == 0 || cfg.MinimumPeers == 0 {
   228  		return nil, nil
   229  	}
   230  	addrs, err := cfg.PeerAddrs()
   231  	if err != nil {
   232  		return nil, fmt.Errorf("bad bootstrap peer: %s", err)
   233  	}
   234  
   235  	bootCfg := bootstrap.BootstrapConfigWithPeers(addrs)
   236  	bootCfg.MinPeerThreshold = cfg.MinimumPeers
   237  
   238  	return bootstrap.Bootstrap(p2pHost.ID(), p2pHost, nil, bootCfg)
   239  }
   240  
   241  func startPeering(cfg sticfg.Peering, p2pHost host.Host) (*peering.PeeringService, error) {
   242  	if len(cfg.Peers) == 0 {
   243  		return nil, nil
   244  	}
   245  
   246  	curPeers, err := cfg.PeerAddrs()
   247  	if err != nil {
   248  		return nil, fmt.Errorf("bad peering peer: %s", err)
   249  	}
   250  
   251  	peeringService := peering.NewPeeringService(p2pHost)
   252  	for i := range curPeers {
   253  		peeringService.AddPeer(curPeers[i])
   254  	}
   255  	if err = peeringService.Start(); err != nil {
   256  		return nil, err
   257  	}
   258  	return peeringService, nil
   259  }