fn: Call extensions/overriding and more customization friendly docker driver (#1065)

In pure-runner and LB agent, service providers might want to set specific driver options.

For example, to add cpu-shares to functions, LB can add the information as extensions
to the Call and pass this via gRPC to runners. Runners then pick these extensions from
gRPC call and pass it to driver. Using a custom driver implementation, pure-runners can
process these extensions to modify docker.CreateContainerOptions.

To achieve this, LB agents can now be configured using a call overrider.

Pure-runners can be configured using a custom docker driver.

RunnerCall and Call interfaces both expose call extensions.

An example to demonstrate this is implemented in test/fn-system-tests/system_test.go
which registers a call overrider for LB agent as well as a simple custom docker driver.
In this example, LB agent adds a key-value to extensions and runners add this key-value
as an environment variable to the container.
This commit is contained in:
Tolga Ceylan
2018-06-18 14:42:28 -07:00
committed by GitHub
parent 199827b319
commit e67d0e5f3f
17 changed files with 741 additions and 411 deletions

View File

@@ -460,41 +460,41 @@ func (ch *callHandle) getDataMsg() *runner.DataFrame {
return msg
}
// TODO: decomission/remove this once dependencies are cleaned up
type CapacityGate interface {
CheckAndReserveCapacity(units uint64) error
ReleaseCapacity(units uint64)
}
// pureRunner implements Agent and delegates execution of functions to an internal Agent; basically it wraps around it
// and provides the gRPC server that implements the LB <-> Runner protocol.
type pureRunner struct {
gRPCServer *grpc.Server
listen string
creds credentials.TransportCredentials
a Agent
inflight int32
}
// implements Agent
func (pr *pureRunner) GetAppID(ctx context.Context, appName string) (string, error) {
return pr.a.GetAppID(ctx, appName)
}
// implements Agent
func (pr *pureRunner) GetAppByID(ctx context.Context, appID string) (*models.App, error) {
return pr.a.GetAppByID(ctx, appID)
}
// implements Agent
func (pr *pureRunner) GetCall(opts ...CallOpt) (Call, error) {
return pr.a.GetCall(opts...)
}
// implements Agent
func (pr *pureRunner) GetRoute(ctx context.Context, appID string, path string) (*models.Route, error) {
return pr.a.GetRoute(ctx, appID, path)
}
// implements Agent
func (pr *pureRunner) Submit(Call) error {
return errors.New("Submit cannot be called directly in a Pure Runner.")
}
// implements Agent
func (pr *pureRunner) Close() error {
// First stop accepting requests
pr.gRPCServer.GracefulStop()
@@ -506,10 +506,12 @@ func (pr *pureRunner) Close() error {
return nil
}
// implements Agent
func (pr *pureRunner) AddCallListener(cl fnext.CallListener) {
pr.a.AddCallListener(cl)
}
// implements Agent
func (pr *pureRunner) Enqueue(context.Context, *models.Call) error {
return errors.New("Enqueue cannot be called directly in a Pure Runner.")
}
@@ -531,7 +533,11 @@ func (pr *pureRunner) handleTryCall(tc *runner.TryCall, state *callHandle) error
return err
}
agent_call, err := pr.a.GetCall(FromModelAndInput(&c, state.pipeToFnR), WithWriter(state), WithContext(state.ctx))
agent_call, err := pr.a.GetCall(FromModelAndInput(&c, state.pipeToFnR),
WithWriter(state),
WithContext(state.ctx),
WithExtensions(tc.GetExtensions()),
)
if err != nil {
state.enqueueCallResponse(err)
return err
@@ -551,6 +557,7 @@ func (pr *pureRunner) handleTryCall(tc *runner.TryCall, state *callHandle) error
return nil
}
// implements RunnerProtocolServer
// Handles a client engagement
func (pr *pureRunner) Engage(engagement runner.RunnerProtocol_EngageServer) error {
grpc.EnableTracing = false
@@ -605,80 +612,87 @@ DataLoop:
return state.waitError()
}
// implements RunnerProtocolServer
func (pr *pureRunner) Status(ctx context.Context, _ *empty.Empty) (*runner.RunnerStatus, error) {
return &runner.RunnerStatus{
Active: atomic.LoadInt32(&pr.inflight),
}, nil
}
func (pr *pureRunner) Start() error {
logrus.Info("Pure Runner listening on ", pr.listen)
lis, err := net.Listen("tcp", pr.listen)
if err != nil {
return fmt.Errorf("Could not listen on %s: %s", pr.listen, err)
}
if err := pr.gRPCServer.Serve(lis); err != nil {
return fmt.Errorf("grpc serve error: %s", err)
}
return err
}
func UnsecuredPureRunner(cancel context.CancelFunc, addr string, da DataAccess) (Agent, error) {
return NewPureRunner(cancel, addr, da, "", "", "", nil)
}
func DefaultPureRunner(cancel context.CancelFunc, addr string, da DataAccess, cert string, key string, ca string) (Agent, error) {
return NewPureRunner(cancel, addr, da, cert, key, ca, nil)
agent := New(da, WithoutAsyncDequeue())
// WARNING: SSL creds are optional.
if cert == "" || key == "" || ca == "" {
return NewPureRunner(cancel, addr, PureRunnerWithAgent(agent))
}
return NewPureRunner(cancel, addr, PureRunnerWithAgent(agent), PureRunnerWithSSL(cert, key, ca))
}
func ValidatePureRunnerConfig() AgentOption {
return func(a *agent) error {
type PureRunnerOption func(*pureRunner) error
if a.cfg.MaxResponseSize == 0 {
return errors.New("pure runner requires MaxResponseSize limits")
func PureRunnerWithSSL(cert string, key string, ca string) PureRunnerOption {
return func(pr *pureRunner) error {
c, err := createCreds(cert, key, ca)
if err != nil {
return fmt.Errorf("Failed to create pure runner credentials: %s", err)
}
if a.cfg.MaxRequestSize == 0 {
return errors.New("pure runner requires MaxRequestSize limits")
}
// pure runner requires a non-blocking resource tracker
if !a.cfg.EnableNBResourceTracker {
return errors.New("pure runner requires EnableNBResourceTracker true")
}
pr.creds = c
return nil
}
}
func NewPureRunner(cancel context.CancelFunc, addr string, da DataAccess, cert string, key string, ca string, unused CapacityGate) (Agent, error) {
// TODO: gate unused, decommission/remove it after cleaning up dependencies to it.
func PureRunnerWithAgent(a Agent) PureRunnerOption {
return func(pr *pureRunner) error {
if pr.a != nil {
return errors.New("Failed to create pure runner: agent already created")
}
a := createAgent(da, ValidatePureRunnerConfig())
var pr *pureRunner
var err error
if cert != "" && key != "" && ca != "" {
c, err := creds(cert, key, ca)
pr.a = a
return nil
}
}
func NewPureRunner(cancel context.CancelFunc, addr string, options ...PureRunnerOption) (Agent, error) {
pr := &pureRunner{}
for _, option := range options {
err := option(pr)
if err != nil {
logrus.WithField("runner_addr", addr).Warn("Failed to create credentials!")
return nil, err
}
pr, err = createPureRunner(addr, a, c)
if err != nil {
return nil, err
}
} else {
logrus.Warn("Running pure runner in insecure mode!")
pr, err = createPureRunner(addr, a, nil)
if err != nil {
return nil, err
logrus.WithError(err).Fatalf("error in pure runner options")
}
}
if pr.a == nil {
logrus.Fatal("agent not provided in pure runner options")
}
var opts []grpc.ServerOption
opts = append(opts, grpc.StreamInterceptor(grpcutil.RIDStreamServerInterceptor))
opts = append(opts, grpc.UnaryInterceptor(grpcutil.RIDUnaryServerInterceptor))
if pr.creds != nil {
opts = append(opts, grpc.Creds(pr.creds))
} else {
logrus.Warn("Running pure runner in insecure mode!")
}
pr.gRPCServer = grpc.NewServer(opts...)
runner.RegisterRunnerProtocolServer(pr.gRPCServer, pr)
lis, err := net.Listen("tcp", addr)
if err != nil {
logrus.WithError(err).Fatalf("Could not listen on %s", addr)
}
logrus.Info("Pure Runner listening on ", addr)
go func() {
err := pr.Start()
if err != nil {
logrus.WithError(err).Error("Failed to start pure runner")
if err := pr.gRPCServer.Serve(lis); err != nil {
logrus.WithError(err).Error("grpc serve error")
cancel()
}
}()
@@ -686,7 +700,11 @@ func NewPureRunner(cancel context.CancelFunc, addr string, da DataAccess, cert s
return pr, nil
}
func creds(cert string, key string, ca string) (credentials.TransportCredentials, error) {
func createCreds(cert string, key string, ca string) (credentials.TransportCredentials, error) {
if cert == "" || key == "" || ca == "" {
return nil, errors.New("Failed to create credentials, cert/key/ca not provided")
}
// Load the certificates from disk
certificate, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
@@ -711,25 +729,5 @@ func creds(cert string, key string, ca string) (credentials.TransportCredentials
}), nil
}
func createPureRunner(addr string, a Agent, creds credentials.TransportCredentials) (*pureRunner, error) {
var srv *grpc.Server
var opts []grpc.ServerOption
sInterceptor := grpc.StreamInterceptor(grpcutil.RIDStreamServerInterceptor)
uInterceptor := grpc.UnaryInterceptor(grpcutil.RIDUnaryServerInterceptor)
opts = append(opts, sInterceptor)
opts = append(opts, uInterceptor)
if creds != nil {
opts = append(opts, grpc.Creds(creds))
}
srv = grpc.NewServer(opts...)
pr := &pureRunner{
gRPCServer: srv,
listen: addr,
a: a,
}
runner.RegisterRunnerProtocolServer(srv, pr)
return pr, nil
}
var _ runner.RunnerProtocolServer = &pureRunner{}
var _ Agent = &pureRunner{}