featurebase/dax/computer/service/computer.go
Travis Turner 468461fbcf
Add Drop Database and Drop Table support (#2208)
* Support Drop Table in serverless (include Snapshotter, Writelogger)

* Finish Database methods

Things like:
- `Databases`
- `DatabaseByID`
- `DatabaseByName`
- `DropDatabase`

* Change Poller to use NodeService instead of its own map

Instead of the Poller maintaining its own map of Addresses to poll, this
commit changes the Poller to use the NodeService interface to get all
known nodes from the Controller.

The next commit needs to:
Next, the logic in the boltdb NodeService implementation was moved to
the boltdb Balancer implementation. That way, the Balancer can be the
source of truth for all things nodes/workers/jobs.

* Move NodeService from Controller to Balancer

This commit moves the implementation of the NodeService into the
Balancer, and aligns `Balancer.AddWorker` with `NodeService.CreateNode`
so that they stay in sync. (Same for `Balancer.RemoveWorker` and
`NodeService.DeleteNode`).

* fix import of private repo

* Fix go vet issues

* Fix bug in DeregisterNode

We need to remove the node from the NodeService even if it's not
assigned to a database. The logic had a bug in it.

This also adds some no-op implementations for SnapshotService and
WriteloggerService. If a directory was not configured for that, then the
computer node would panic on trying to read from the Snapshotter upon
receiving a Directive.

* queryer response content-type: json

* Add support for NULL to WriteloggerDir and SnapshotterDir configs

This commit changes the way WriteLoggerDir and SnapshotterDir are
handled.
If value is empty `""`, an error will be returned on computer startup.
If value is `"NULL"`, a no-op implementation of the service will be
used. This would be for a case that wanted to run serverless on-prem
with no durable storage.
Finally, any other value will be used as the directory to use.

Some things which aren't considered here and may result in unexpected
behavior:
- a value with spaces `" "`
- any "null" which is not "NULL"... like lowercase.

* Finish the DropTable test

* Change "disable service" value to case-insensitive "off"

This commit also removes an unnecessary sleep in the tests.

* Fix docker-compose variables for IDK test

Co-authored-by: Matthew Jaffee <jaffee@pilosa.com>
2023-01-23 19:59:47 -06:00

185 lines
5.5 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"
mdsclient "github.com/featurebasedb/featurebase/v3/dax/mds/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,
}
}
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.MDSAddress != "" {
mdsAddr := dax.Address(c.cfg.ComputerConfig.MDSAddress)
// Set mds (registrar) on computer.
if err := c.SetMDS(mdsAddr); err != nil {
return errors.Wrapf(err, "setting mds 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,
},
}
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) SetMDS(addr dax.Address) error {
c.computer.Registrar = mdsclient.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 }