hybrid mergy (#581)

* so it begins

* add clarification to /dequeue, change response to list to future proof

* Specify that runner endpoints are also under /v1

* Add a flag to choose operation mode (node type).

This is specified using the `FN_NODE_TYPE` environment variable. The
default is the existing behaviour, where the server supports all
operations (full API plus asynchronous and synchronous runners).

The additional modes are:
* API - the full API is available, but no functions are executed by the
  node. Async calls are placed into a message queue, and synchronous
  calls are not supported (invoking them results in an API error).
* Runner - only the invocation/route API is present. Asynchronous and
  synchronous invocation requests are supported, but asynchronous
  requests are placed onto the message queue, so might be handled by
  another runner.

* Add agent type and checks on Submit

* Sketch of a factored out data access abstraction for api/runner agents

* Fix tests, adding node/agent types to constructors

* Add tests for full, API, and runner server modes.

* Added atomic UpdateCall to datastore

* adds in server side endpoints

* Made ServerNodeType public because tests use it

* Made ServerNodeType public because tests use it

* fix test build

* add hybrid runner client

pretty simple go api client that covers surface area needed for hybrid,
returning structs from models that the agent can use directly. not exactly
sure where to put this, so put it in `/clients/hybrid` but maybe we should
make `/api/runner/client` or something and shove it in there. want to get
integration tests set up and use the real endpoints next and then wrap this up
in the DataAccessLayer stuff.

* gracefully handles errors from fn
* handles backoff & retry on 500s
* will add to existing spans for debuggo action

* minor fixes

* meh
This commit is contained in:
Reed Allman
2017-12-11 10:43:19 -08:00
committed by GitHub
parent 1df4b46c56
commit 2ebc9c7480
26 changed files with 1157 additions and 94 deletions

View File

@@ -50,12 +50,12 @@ type Params []Param
func FromRequest(appName, path string, req *http.Request, params Params) CallOpt {
return func(a *agent, c *call) error {
app, err := a.ds.GetApp(req.Context(), appName)
app, err := a.da.GetApp(req.Context(), appName)
if err != nil {
return err
}
route, err := a.ds.GetRoute(req.Context(), appName, path)
route, err := a.da.GetRoute(req.Context(), appName, path)
if err != nil {
return err
}
@@ -248,9 +248,7 @@ func (a *agent) GetCall(opts ...CallOpt) (Call, error) {
return nil, errors.New("no model or request provided for call")
}
c.ds = a.ds
c.ls = a.ls
c.mq = a.mq
c.da = a.da
c.ct = a
ctx, _ := common.LoggerWithFields(c.req.Context(),
@@ -272,9 +270,7 @@ func (a *agent) GetCall(opts ...CallOpt) (Call, error) {
type call struct {
*models.Call
ds models.Datastore
ls models.LogStore
mq models.MessageQueue
da DataAccess
w io.Writer
req *http.Request
stderr io.ReadWriteCloser
@@ -316,7 +312,7 @@ func (c *call) Start(ctx context.Context) error {
// running to avoid running the call twice and potentially mark it as
// errored (built in long running task detector, so to speak...)
err := c.mq.Delete(ctx, c.Call)
err := c.da.Start(ctx, c.Model())
if err != nil {
return err // let another thread try this
}
@@ -346,29 +342,14 @@ func (c *call) End(ctx context.Context, errIn error) error {
c.Error = errIn.Error()
}
if c.Type == models.TypeAsync {
// XXX (reed): delete MQ message, eventually
}
// ensure stats histogram is reasonably bounded
c.Call.Stats = drivers.Decimate(240, c.Call.Stats)
// this means that we could potentially store an error / timeout status for a
// call that ran successfully [by a user's perspective]
// TODO: this should be update, really
if err := c.ds.InsertCall(ctx, c.Call); err != nil {
common.Logger(ctx).WithError(err).Error("error inserting call into datastore")
if err := c.da.Finish(ctx, c.Model(), c.stderr, c.Type == models.TypeAsync); err != nil {
common.Logger(ctx).WithError(err).Error("error finalizing call on datastore/mq")
// note: Not returning err here since the job could have already finished successfully.
}
if err := c.ls.InsertLog(ctx, c.AppName, c.ID, c.stderr); err != nil {
common.Logger(ctx).WithError(err).Error("error uploading log")
// note: Not returning err here since the job could have already finished successfully.
}
// NOTE call this after InsertLog or the buffer will get reset
c.stderr.Close()
if err := c.ct.fireAfterCall(ctx, c.Model()); err != nil {
return fmt.Errorf("AfterCall: %v", err)
}