mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
functions: hot containers (#332)
* functions: modify datastore to accomodate hot containers support * functions: protocol between functions and hot containers * functions: add hot containers clockwork * fn: add hot containers support
This commit is contained in:
19
api/runner/protocol/default.go
Normal file
19
api/runner/protocol/default.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/iron-io/functions/api/runner/task"
|
||||
)
|
||||
|
||||
// DefaultProtocol is the protocol used by cold-containers
|
||||
type DefaultProtocol struct {
|
||||
}
|
||||
|
||||
func (p *DefaultProtocol) IsStreamable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *DefaultProtocol) Dispatch(ctx context.Context, t task.Request) error {
|
||||
return nil
|
||||
}
|
||||
52
api/runner/protocol/factory.go
Normal file
52
api/runner/protocol/factory.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/runner/task"
|
||||
)
|
||||
|
||||
var errInvalidProtocol = errors.New("Invalid Protocol")
|
||||
|
||||
// ContainerIO defines the interface used to talk to a hot container.
|
||||
// Internally, a protocol must know when to alternate between stdin and stdout.
|
||||
// It returns any protocol error, if present.
|
||||
type ContainerIO interface {
|
||||
IsStreamable() bool
|
||||
Dispatch(ctx context.Context, t task.Request) error
|
||||
}
|
||||
|
||||
// Protocol defines all protocols that operates a ContainerIO.
|
||||
type Protocol string
|
||||
|
||||
// Hot container protocols
|
||||
const (
|
||||
Default Protocol = models.FormatDefault
|
||||
HTTP Protocol = models.FormatHTTP
|
||||
)
|
||||
|
||||
// New creates a valid protocol handler from a I/O pipe representing containers
|
||||
// stdin/stdout.
|
||||
func New(p Protocol, in io.Writer, out io.Reader) (ContainerIO, error) {
|
||||
switch p {
|
||||
case HTTP:
|
||||
return &HTTPProtocol{in, out}, nil
|
||||
case Default:
|
||||
return &DefaultProtocol{}, nil
|
||||
default:
|
||||
return nil, errInvalidProtocol
|
||||
}
|
||||
}
|
||||
|
||||
// IsStreamable says whether the given protocol can be used for streaming into
|
||||
// hot containers.
|
||||
func IsStreamable(p string) (bool, error) {
|
||||
proto, err := New(Protocol(p), nil, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return proto.IsStreamable(), nil
|
||||
}
|
||||
73
api/runner/protocol/http.go
Normal file
73
api/runner/protocol/http.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"time"
|
||||
|
||||
"github.com/iron-io/functions/api/models"
|
||||
"github.com/iron-io/functions/api/runner/task"
|
||||
)
|
||||
|
||||
// HTTPProtocol converts stdin/stdout streams into HTTP/1.1 compliant
|
||||
// communication. It relies on Content-Length to know when to stop reading from
|
||||
// containers stdout. It also mandates valid HTTP headers back and forth, thus
|
||||
// returning errors in case of parsing problems.
|
||||
type HTTPProtocol struct {
|
||||
in io.Writer
|
||||
out io.Reader
|
||||
}
|
||||
|
||||
func (p *HTTPProtocol) IsStreamable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *HTTPProtocol) Dispatch(ctx context.Context, t task.Request) error {
|
||||
var retErr error
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
var body bytes.Buffer
|
||||
io.Copy(&body, t.Config.Stdin)
|
||||
req, err := http.NewRequest("GET", "/", &body)
|
||||
if err != nil {
|
||||
retErr = err
|
||||
return
|
||||
}
|
||||
for k, v := range t.Config.Env {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
req.Header.Set("Content-Length", fmt.Sprint(body.Len()))
|
||||
req.Header.Set("Task-ID", t.Config.ID)
|
||||
raw, err := httputil.DumpRequest(req, true)
|
||||
if err != nil {
|
||||
retErr = err
|
||||
return
|
||||
}
|
||||
p.in.Write(raw)
|
||||
|
||||
res, err := http.ReadResponse(bufio.NewReader(p.out), req)
|
||||
if err != nil {
|
||||
retErr = err
|
||||
return
|
||||
}
|
||||
|
||||
io.Copy(t.Config.Stdout, res.Body)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
timeout := time.After(t.Config.Timeout)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-timeout:
|
||||
return models.ErrRunnerTimeout
|
||||
case <-done:
|
||||
return retErr
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user