mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
For on-prem serverless, if we restart the process containing the controller and computer(s), when they come back up, the controller doesn't know that the computers have been restarted, so it doesn't send them a directive. This change forces the controller to send a directive upon startup by a computer.
186 lines
5.6 KiB
Go
186 lines
5.6 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
featurebase "github.com/featurebasedb/featurebase/v3"
|
|
"github.com/featurebasedb/featurebase/v3/dax"
|
|
"github.com/featurebasedb/featurebase/v3/dax/computer"
|
|
controllerclient "github.com/featurebasedb/featurebase/v3/dax/controller/client"
|
|
"github.com/featurebasedb/featurebase/v3/dax/snapshotter"
|
|
"github.com/featurebasedb/featurebase/v3/dax/writelogger"
|
|
"github.com/featurebasedb/featurebase/v3/errors"
|
|
"github.com/featurebasedb/featurebase/v3/logger"
|
|
fbserver "github.com/featurebasedb/featurebase/v3/server"
|
|
)
|
|
|
|
// Ensure type implements interface.
|
|
var _ dax.ComputerService = (*computerService)(nil)
|
|
|
|
type computerService struct {
|
|
addr dax.Address
|
|
cfg CommandConfig
|
|
key dax.ServiceKey
|
|
computer *fbserver.Command
|
|
logger logger.Logger
|
|
}
|
|
|
|
func New(addr dax.Address, cfg CommandConfig, logger logger.Logger) *computerService {
|
|
cfg.ComputerConfig.Advertise = addr.HostPort()
|
|
|
|
return &computerService{
|
|
addr: addr,
|
|
cfg: cfg,
|
|
logger: logger.WithPrefix("Computer: "),
|
|
}
|
|
}
|
|
|
|
func (c *computerService) Start() error {
|
|
// We initialize the fbserver.Command at start (as opposed to in New()
|
|
// above) because we want to inject (via cfg.Name) the ServiceKey into the
|
|
// *fbserver.Command returned by newCommand() so that it can be used
|
|
// internally. In the future, when we have the pilosa package moved down to
|
|
// the computer package, we should be able to clean this up so that things
|
|
// happen in a reasonable order like we do with the other service types.
|
|
if c.computer == nil {
|
|
c.cfg.Name = string(c.Key())
|
|
if cmd, err := newCommand(c.addr, c.cfg); err != nil {
|
|
return errors.Wrapf(err, "getting new command for computer config: %s", c.cfg.Name)
|
|
} else {
|
|
c.computer = cmd
|
|
}
|
|
|
|
if c.cfg.ComputerConfig.ControllerAddress != "" {
|
|
controllerAddr := dax.Address(c.cfg.ComputerConfig.ControllerAddress)
|
|
// Set Controller (registrar) on computer.
|
|
if err := c.SetController(controllerAddr); err != nil {
|
|
return errors.Wrapf(err, "setting controller service on computer: %s", c.cfg.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := c.computer.StartNoServe(c.Address()); err != nil {
|
|
return errors.Wrap(err, "starting featurebase command")
|
|
}
|
|
|
|
if c.computer.Registrar != nil {
|
|
node := &dax.Node{
|
|
Address: c.Address(),
|
|
RoleTypes: []dax.RoleType{
|
|
dax.RoleTypeCompute,
|
|
dax.RoleTypeTranslate,
|
|
},
|
|
HasDirective: false,
|
|
}
|
|
|
|
if err := c.computer.Registrar.RegisterNode(context.TODO(), node); err != nil {
|
|
return errors.Wrapf(err, "registering computer: %s", c.Address())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *computerService) Stop() error {
|
|
return c.computer.Close()
|
|
}
|
|
|
|
func (c *computerService) Key() dax.ServiceKey {
|
|
return c.key
|
|
}
|
|
|
|
func (c *computerService) SetKey(key dax.ServiceKey) {
|
|
c.key = key
|
|
}
|
|
|
|
func (c *computerService) Address() dax.Address {
|
|
return dax.Address(c.addr.HostPort() + "/" + string(c.key))
|
|
}
|
|
|
|
func (c *computerService) HTTPHandler() http.Handler {
|
|
return c.computer.HTTPHandler()
|
|
}
|
|
|
|
func (c *computerService) SetController(addr dax.Address) error {
|
|
c.computer.Registrar = controllerclient.New(addr, c.logger)
|
|
return nil
|
|
}
|
|
|
|
type CommandConfig struct {
|
|
// Name is used to distinguish between locally running commands.
|
|
// For example, it's appended to DataDir so that each cmd has a
|
|
// separate data directory for its holder.
|
|
Name string
|
|
|
|
ComputerConfig fbserver.Config
|
|
|
|
Listener net.Listener
|
|
RootDataDir string
|
|
|
|
Stderr io.Writer
|
|
Logger logger.Logger
|
|
}
|
|
|
|
// serviceOffValue is a reserved term used to explicitly disable a service. When
|
|
// used, the service will be set to use its no-op implementation. For the user,
|
|
// this is case-insensitive.
|
|
var serviceOffValue = "off"
|
|
|
|
func newCommand(addr dax.Address, cfg CommandConfig) (*fbserver.Command, error) {
|
|
// Set up Writelogger.
|
|
var wlSvc computer.WritelogService
|
|
wlDirToCompare := strings.TrimSpace(strings.ToLower(cfg.ComputerConfig.WriteloggerDir))
|
|
switch wlDirToCompare {
|
|
case "":
|
|
return nil, errors.New(errors.ErrUncoded, "no writelogger directory configured")
|
|
case serviceOffValue:
|
|
wlSvc = computer.NewNopWritelogService()
|
|
cfg.Logger.Warnf("No writelogger configured, dynamic scaling will not function properly.")
|
|
default:
|
|
wlSvc = writelogger.New(cfg.ComputerConfig.WriteloggerDir, cfg.Logger)
|
|
}
|
|
|
|
// Set up Snapshotter.
|
|
var ssSvc computer.SnapshotService
|
|
ssDirToCompare := strings.TrimSpace(strings.ToLower(cfg.ComputerConfig.SnapshotterDir))
|
|
switch ssDirToCompare {
|
|
case "":
|
|
return nil, errors.New(errors.ErrUncoded, "no snapshotter directory configured")
|
|
case serviceOffValue:
|
|
ssSvc = computer.NewNopSnapshotterService()
|
|
cfg.Logger.Warnf("No snapshotter configured, dynamic scaling will not function properly.")
|
|
default:
|
|
ssSvc = snapshotter.New(cfg.ComputerConfig.SnapshotterDir, cfg.Logger)
|
|
}
|
|
|
|
// Set the FeatureBase.Config values based on the top-level Config
|
|
// values.
|
|
cfg.ComputerConfig.Listener = &nopListener{}
|
|
cfg.ComputerConfig.Advertise = addr.HostPort()
|
|
cfg.ComputerConfig.GRPCListener = &nopListener{}
|
|
cfg.ComputerConfig.DataDir = cfg.RootDataDir + "/" + cfg.Name
|
|
|
|
fbcmd := fbserver.NewCommand(cfg.Stderr,
|
|
fbserver.OptCommandSetConfig(&cfg.ComputerConfig),
|
|
fbserver.OptCommandServerOptions(
|
|
featurebase.OptServerIsComputeNode(true),
|
|
featurebase.OptServerLogger(cfg.Logger),
|
|
),
|
|
fbserver.OptCommandInjections(fbserver.Injections{
|
|
Writelogger: wlSvc,
|
|
Snapshotter: ssSvc,
|
|
IsComputeNode: true,
|
|
}),
|
|
)
|
|
|
|
return fbcmd, nil
|
|
}
|
|
|
|
type nopListener struct{}
|
|
|
|
func (n *nopListener) Accept() (net.Conn, error) { return nil, nil }
|
|
func (n *nopListener) Close() error { return nil }
|
|
func (n *nopListener) Addr() net.Addr { return nil }
|