mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Merge pull request #299 from fnproject/fn-the-things
FN_ prefix env vars
This commit is contained in:
@@ -30,9 +30,7 @@ import (
|
|||||||
// TODO handle timeouts / no response in sync & async (sync is json+503 atm, not 504, async is empty log+status)
|
// TODO handle timeouts / no response in sync & async (sync is json+503 atm, not 504, async is empty log+status)
|
||||||
// see also: server/runner.go wrapping the response writer there, but need to handle async too (push down?)
|
// see also: server/runner.go wrapping the response writer there, but need to handle async too (push down?)
|
||||||
// TODO herd launch prevention part deux
|
// TODO herd launch prevention part deux
|
||||||
// TODO plumb FXLB-WAIT back - can we use headers now? maybe let's use api
|
// TODO storing logs / call can push call over the timeout
|
||||||
// TODO none of the Datastore methods actually use the ctx for timeouts :(
|
|
||||||
// TODO not adding padding if call times out to store appropriately (ctx timed out, happenstance it works now cuz of ^)
|
|
||||||
// TODO all Datastore methods need to take unit of tenancy (app or route) at least (e.g. not just call id)
|
// TODO all Datastore methods need to take unit of tenancy (app or route) at least (e.g. not just call id)
|
||||||
// TODO limit the request body length when making calls
|
// TODO limit the request body length when making calls
|
||||||
// TODO discuss concrete policy for hot launch or timeout / timeout vs time left
|
// TODO discuss concrete policy for hot launch or timeout / timeout vs time left
|
||||||
@@ -50,7 +48,6 @@ import (
|
|||||||
// dies). need coordination w/ db.
|
// dies). need coordination w/ db.
|
||||||
// TODO if a cold call times out but container is created but hasn't replied, could
|
// TODO if a cold call times out but container is created but hasn't replied, could
|
||||||
// end up that the client doesn't get a reply until long after the timeout (b/c of container removal, async it?)
|
// end up that the client doesn't get a reply until long after the timeout (b/c of container removal, async it?)
|
||||||
// TODO we should prob not be logging all async output to the logs by default...
|
|
||||||
// TODO the call api should fill in all the fields
|
// TODO the call api should fill in all the fields
|
||||||
// TODO the log api should be plaintext (or at least offer it)
|
// TODO the log api should be plaintext (or at least offer it)
|
||||||
// TODO func logger needs to be hanged, dragged and quartered. in reverse order.
|
// TODO func logger needs to be hanged, dragged and quartered. in reverse order.
|
||||||
|
|||||||
275
api/agent/agent_test.go
Normal file
275
api/agent/agent_test.go
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
package agent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fnproject/fn/api/datastore"
|
||||||
|
"github.com/fnproject/fn/api/models"
|
||||||
|
"github.com/fnproject/fn/api/mqs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCallConfigurationRequest(t *testing.T) {
|
||||||
|
appName := "myapp"
|
||||||
|
path := "/sleeper"
|
||||||
|
image := "fnproject/sleeper"
|
||||||
|
const timeout = 1
|
||||||
|
const idleTimeout = 20
|
||||||
|
const memory = 256
|
||||||
|
typ := "sync"
|
||||||
|
format := "default"
|
||||||
|
|
||||||
|
cfg := models.Config{"APP_VAR": "FOO"}
|
||||||
|
rCfg := models.Config{"ROUTE_VAR": "BAR"}
|
||||||
|
|
||||||
|
ds := datastore.NewMockInit(
|
||||||
|
[]*models.App{
|
||||||
|
{Name: appName, Config: cfg},
|
||||||
|
},
|
||||||
|
[]*models.Route{
|
||||||
|
{
|
||||||
|
Config: rCfg,
|
||||||
|
Path: path,
|
||||||
|
AppName: appName,
|
||||||
|
Image: image,
|
||||||
|
Type: typ,
|
||||||
|
Format: format,
|
||||||
|
Timeout: timeout,
|
||||||
|
IdleTimeout: idleTimeout,
|
||||||
|
Memory: memory,
|
||||||
|
},
|
||||||
|
}, nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
a := New(ds, new(mqs.Mock))
|
||||||
|
defer a.Close()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
method := "GET"
|
||||||
|
url := "http://127.0.0.1:8080/r/" + appName + path
|
||||||
|
payload := "payload"
|
||||||
|
contentLength := strconv.Itoa(len(payload))
|
||||||
|
req, err := http.NewRequest(method, url, strings.NewReader(payload))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("MYREALHEADER", "FOOLORD")
|
||||||
|
req.Header.Add("MYREALHEADER", "FOOPEASANT")
|
||||||
|
req.Header.Add("Content-Length", contentLength)
|
||||||
|
req.Header.Add("FN_ROUTE", "thewrongroute") // ensures that this doesn't leak out, should be overwritten
|
||||||
|
|
||||||
|
call, err := a.GetCall(
|
||||||
|
WithWriter(w), // XXX (reed): order matters [for now]
|
||||||
|
FromRequest(appName, path, req),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
model := call.Model()
|
||||||
|
|
||||||
|
// make sure the values are all set correctly
|
||||||
|
if model.ID == "" {
|
||||||
|
t.Fatal("model does not have id, GetCall should assign id")
|
||||||
|
}
|
||||||
|
if model.AppName != appName {
|
||||||
|
t.Fatal("app name mismatch", model.AppName, appName)
|
||||||
|
}
|
||||||
|
if model.Path != path {
|
||||||
|
t.Fatal("path mismatch", model.Path, path)
|
||||||
|
}
|
||||||
|
if model.Image != image {
|
||||||
|
t.Fatal("image mismatch", model.Image, image)
|
||||||
|
}
|
||||||
|
if model.Type != "sync" {
|
||||||
|
t.Fatal("route type mismatch", model.Type)
|
||||||
|
}
|
||||||
|
if model.Priority == nil {
|
||||||
|
t.Fatal("GetCall should make priority non-nil so that async works because for whatever reason some clowns plumbed it all over the mqs even though the user can't specify it gg")
|
||||||
|
}
|
||||||
|
if model.Timeout != timeout {
|
||||||
|
t.Fatal("timeout mismatch", model.Timeout, timeout)
|
||||||
|
}
|
||||||
|
if model.IdleTimeout != idleTimeout {
|
||||||
|
t.Fatal("idle timeout mismatch", model.IdleTimeout, idleTimeout)
|
||||||
|
}
|
||||||
|
if time.Time(model.CreatedAt).IsZero() {
|
||||||
|
t.Fatal("GetCall should stamp CreatedAt, got nil timestamp")
|
||||||
|
}
|
||||||
|
if model.URL != url {
|
||||||
|
t.Fatal("url mismatch", model.URL, url)
|
||||||
|
}
|
||||||
|
if model.Method != method {
|
||||||
|
t.Fatal("method mismatch", model.Method, method)
|
||||||
|
}
|
||||||
|
if model.Payload != "" { // NOTE: this is expected atm
|
||||||
|
t.Fatal("GetCall FromRequest should not fill payload, got non-empty payload", model.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedBase := map[string]string{
|
||||||
|
"FN_FORMAT": format,
|
||||||
|
"FN_APP_NAME": appName,
|
||||||
|
"FN_ROUTE": path,
|
||||||
|
"FN_MEMORY": strconv.Itoa(memory),
|
||||||
|
"FN_TYPE": typ,
|
||||||
|
"APP_VAR": "FOO",
|
||||||
|
"ROUTE_VAR": "BAR",
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedEnv := make(map[string]string)
|
||||||
|
for k, v := range expectedBase {
|
||||||
|
expectedEnv[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range expectedBase {
|
||||||
|
if v2 := model.BaseEnv[k]; v2 != v {
|
||||||
|
t.Fatal("base var mismatch", k, v, v2, model.BaseEnv)
|
||||||
|
}
|
||||||
|
delete(expectedBase, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedBase) > 0 {
|
||||||
|
t.Fatal("got extra vars in base env set, add me to tests ;)", expectedBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedEnv["FN_CALL_ID"] = model.ID
|
||||||
|
expectedEnv["FN_METHOD"] = method
|
||||||
|
expectedEnv["FN_REQUEST_URL"] = url
|
||||||
|
|
||||||
|
// do this before the "real" headers get sucked in cuz they are formatted differently
|
||||||
|
expectedHeaders := make(http.Header)
|
||||||
|
for k, v := range expectedEnv {
|
||||||
|
expectedHeaders.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// from the request headers (look different in env than in req.Header, idk, up to user anger)
|
||||||
|
// req headers down cases things
|
||||||
|
expectedEnv["FN_HEADER_Myrealheader"] = "FOOLORD, FOOPEASANT"
|
||||||
|
expectedEnv["FN_HEADER_Content_Length"] = contentLength
|
||||||
|
|
||||||
|
for k, v := range expectedEnv {
|
||||||
|
if v2 := model.EnvVars[k]; v2 != v {
|
||||||
|
t.Fatal("env var mismatch", k, v, v2, model.EnvVars)
|
||||||
|
}
|
||||||
|
delete(expectedEnv, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedEnv) > 0 {
|
||||||
|
t.Fatal("got extra vars in base env set, add me to tests ;)", expectedBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedHeaders.Add("MYREALHEADER", "FOOLORD")
|
||||||
|
expectedHeaders.Add("MYREALHEADER", "FOOPEASANT")
|
||||||
|
expectedHeaders.Add("Content-Length", contentLength)
|
||||||
|
|
||||||
|
for k, vs := range req.Header {
|
||||||
|
for i, v := range expectedHeaders[k] {
|
||||||
|
if i >= len(vs) || vs[i] != v {
|
||||||
|
t.Fatal("header mismatch", k, vs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(expectedHeaders, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedHeaders) > 0 {
|
||||||
|
t.Fatal("got extra headers, bad")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Header()["Fn_call_id"][0] != model.ID {
|
||||||
|
t.Fatal("response writer should have the call id, or else")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check response writer for route headers
|
||||||
|
|
||||||
|
// TODO idk what param even is or how to get them, but need to test those
|
||||||
|
// TODO we should double check the things we're rewriting defaults of, like type, format, timeout, idle_timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallConfigurationModel(t *testing.T) {
|
||||||
|
appName := "myapp"
|
||||||
|
path := "/sleeper"
|
||||||
|
image := "fnproject/sleeper"
|
||||||
|
const timeout = 1
|
||||||
|
const idleTimeout = 20
|
||||||
|
const memory = 256
|
||||||
|
method := "GET"
|
||||||
|
url := "http://127.0.0.1:8080/r/" + appName + path
|
||||||
|
payload := "payload"
|
||||||
|
typ := "sync"
|
||||||
|
format := "default"
|
||||||
|
env := map[string]string{
|
||||||
|
"FN_FORMAT": format,
|
||||||
|
"FN_APP_NAME": appName,
|
||||||
|
"FN_ROUTE": path,
|
||||||
|
"FN_MEMORY": strconv.Itoa(memory),
|
||||||
|
"FN_TYPE": typ,
|
||||||
|
"APP_VAR": "FOO",
|
||||||
|
"ROUTE_VAR": "BAR",
|
||||||
|
"DOUBLE_VAR": "BIZ, BAZ",
|
||||||
|
}
|
||||||
|
|
||||||
|
cm := &models.Call{
|
||||||
|
BaseEnv: env,
|
||||||
|
EnvVars: env,
|
||||||
|
AppName: appName,
|
||||||
|
Path: path,
|
||||||
|
Image: image,
|
||||||
|
Type: typ,
|
||||||
|
Format: format,
|
||||||
|
Timeout: timeout,
|
||||||
|
IdleTimeout: idleTimeout,
|
||||||
|
Memory: memory,
|
||||||
|
Payload: payload,
|
||||||
|
URL: url,
|
||||||
|
Method: method,
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromModel doesn't need a datastore, for now...
|
||||||
|
ds := datastore.NewMockInit(nil, nil, nil)
|
||||||
|
|
||||||
|
a := New(ds, new(mqs.Mock))
|
||||||
|
defer a.Close()
|
||||||
|
|
||||||
|
callI, err := a.GetCall(FromModel(cm))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure headers seem reasonable
|
||||||
|
req := callI.(*call).req
|
||||||
|
|
||||||
|
// NOTE these are added as is atm, and if the env vars were comma joined
|
||||||
|
// they are not again here comma separated.
|
||||||
|
expectedHeaders := make(http.Header)
|
||||||
|
for k, v := range env {
|
||||||
|
expectedHeaders.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, vs := range req.Header {
|
||||||
|
for i, v := range expectedHeaders[k] {
|
||||||
|
if i >= len(vs) || vs[i] != v {
|
||||||
|
t.Fatal("header mismatch", k, vs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(expectedHeaders, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(expectedHeaders) > 0 {
|
||||||
|
t.Fatal("got extra headers, bad")
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
io.Copy(&b, req.Body)
|
||||||
|
|
||||||
|
if b.String() != payload {
|
||||||
|
t.Fatal("expected payload to match, but it was a lie")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,12 +65,8 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
|
|||||||
|
|
||||||
// baseVars are the vars on the route & app, not on this specific request [for hot functions]
|
// baseVars are the vars on the route & app, not on this specific request [for hot functions]
|
||||||
baseVars := make(map[string]string, len(app.Config)+len(route.Config)+3)
|
baseVars := make(map[string]string, len(app.Config)+len(route.Config)+3)
|
||||||
baseVars["FN_FORMAT"] = route.Format
|
|
||||||
baseVars["APP_NAME"] = appName
|
|
||||||
baseVars["ROUTE"] = route.Path
|
|
||||||
baseVars["MEMORY_MB"] = fmt.Sprintf("%d", route.Memory)
|
|
||||||
|
|
||||||
// app config
|
// add app & route config before our standard additions
|
||||||
for k, v := range app.Config {
|
for k, v := range app.Config {
|
||||||
k = toEnvName("", k)
|
k = toEnvName("", k)
|
||||||
baseVars[k] = v
|
baseVars[k] = v
|
||||||
@@ -80,6 +76,12 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
|
|||||||
baseVars[k] = v
|
baseVars[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseVars["FN_FORMAT"] = route.Format
|
||||||
|
baseVars["FN_APP_NAME"] = appName
|
||||||
|
baseVars["FN_ROUTE"] = route.Path
|
||||||
|
baseVars["FN_MEMORY"] = fmt.Sprintf("%d", route.Memory)
|
||||||
|
baseVars["FN_TYPE"] = route.Type
|
||||||
|
|
||||||
// envVars contains the full set of env vars, per request + base
|
// envVars contains the full set of env vars, per request + base
|
||||||
envVars := make(map[string]string, len(baseVars)+len(params)+len(req.Header)+3)
|
envVars := make(map[string]string, len(baseVars)+len(params)+len(req.Header)+3)
|
||||||
|
|
||||||
@@ -87,30 +89,43 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
|
|||||||
envVars[k] = v
|
envVars[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
envVars["CALL_ID"] = id
|
envVars["FN_CALL_ID"] = id
|
||||||
envVars["METHOD"] = req.Method
|
envVars["FN_METHOD"] = req.Method
|
||||||
envVars["REQUEST_URL"] = fmt.Sprintf("%v://%v%v", func() string {
|
envVars["FN_REQUEST_URL"] = func() string {
|
||||||
if req.TLS == nil {
|
if req.URL.Scheme == "" {
|
||||||
return "http"
|
if req.TLS == nil {
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
} else {
|
||||||
|
req.URL.Scheme = "https"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "https"
|
if req.URL.Host == "" {
|
||||||
}(), req.Host, req.URL.String())
|
req.URL.Host = req.Host
|
||||||
|
}
|
||||||
|
return req.URL.String()
|
||||||
|
}()
|
||||||
|
|
||||||
// params
|
// params
|
||||||
for _, param := range params {
|
for _, param := range params {
|
||||||
envVars[toEnvName("PARAM", param.Key)] = param.Value
|
envVars[toEnvName("FN_PARAM", param.Key)] = param.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
headerVars := make(map[string]string, len(req.Header))
|
headerVars := make(map[string]string, len(req.Header))
|
||||||
|
|
||||||
for k, v := range req.Header {
|
for k, v := range req.Header {
|
||||||
headerVars[toEnvName("HEADER", k)] = strings.Join(v, ", ")
|
if !noOverrideVars(k) { // NOTE if we don't do this, they'll leak in (don't want people relying on this behavior)
|
||||||
|
headerVars[toEnvName("FN_HEADER", k)] = strings.Join(v, ", ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add all the env vars we build to the request headers
|
// add all the env vars we build to the request headers
|
||||||
// TODO should we save req.Headers and copy OVER app.Config / route.Config ?
|
|
||||||
for k, v := range envVars {
|
for k, v := range envVars {
|
||||||
req.Header.Add(k, v)
|
if noOverrideVars(k) {
|
||||||
|
// overwrite the passed in request headers explicitly with the generated ones
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
} else {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range headerVars {
|
for k, v := range headerVars {
|
||||||
@@ -118,6 +133,7 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO this relies on ordering of opts, but tests make sure it works, probably re-plumb/destroy headers
|
// TODO this relies on ordering of opts, but tests make sure it works, probably re-plumb/destroy headers
|
||||||
|
// TODO async should probably supply an http.ResponseWriter that records the logs, to attach response headers to
|
||||||
if rw, ok := c.w.(http.ResponseWriter); ok {
|
if rw, ok := c.w.(http.ResponseWriter); ok {
|
||||||
rw.Header().Add("FN_CALL_ID", id)
|
rw.Header().Add("FN_CALL_ID", id)
|
||||||
for k, vs := range route.Headers {
|
for k, vs := range route.Headers {
|
||||||
@@ -161,6 +177,27 @@ func FromRequest(appName, path string, req *http.Request) CallOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func noOverrideVars(key string) bool {
|
||||||
|
// descrepency in casing b/w req headers and env vars, force matches
|
||||||
|
return overrideVars[strings.ToUpper(key)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideVars means that the app config, route config or header vars
|
||||||
|
// must not overwrite the generated values in call construction.
|
||||||
|
var overrideVars = map[string]bool{
|
||||||
|
"FN_FORMAT": true,
|
||||||
|
"FN_APP_NAME": true,
|
||||||
|
"FN_ROUTE": true,
|
||||||
|
"FN_MEMORY": true,
|
||||||
|
"FN_TYPE": true,
|
||||||
|
"FN_CALL_ID": true,
|
||||||
|
"FN_METHOD": true,
|
||||||
|
"FN_REQUEST_URL": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this currently relies on FromRequest having happened before to create the model
|
||||||
|
// here, to be a fully qualified model. We probably should double check but having a way
|
||||||
|
// to bypass will likely be what's used anyway unless forced.
|
||||||
func FromModel(mCall *models.Call) CallOpt {
|
func FromModel(mCall *models.Call) CallOpt {
|
||||||
return func(a *agent, c *call) error {
|
return func(a *agent, c *call) error {
|
||||||
c.Call = mCall
|
c.Call = mCall
|
||||||
@@ -246,6 +283,10 @@ func (c *call) Start(ctx context.Context) error {
|
|||||||
c.StartedAt = strfmt.DateTime(time.Now())
|
c.StartedAt = strfmt.DateTime(time.Now())
|
||||||
c.Status = "running"
|
c.Status = "running"
|
||||||
|
|
||||||
|
if rw, ok := c.w.(http.ResponseWriter); ok { // TODO need to figure out better way to wire response headers in
|
||||||
|
rw.Header().Set("XXX-FXLB-WAIT", time.Time(c.StartedAt).Sub(time.Time(c.CreatedAt)).String())
|
||||||
|
}
|
||||||
|
|
||||||
if c.Type == models.TypeAsync {
|
if c.Type == models.TypeAsync {
|
||||||
// XXX (reed): make sure MQ reservation is lengthy. to skirt MQ semantics,
|
// XXX (reed): make sure MQ reservation is lengthy. to skirt MQ semantics,
|
||||||
// we could add a new message to MQ w/ delay of call.Timeout and delete the
|
// we could add a new message to MQ w/ delay of call.Timeout and delete the
|
||||||
|
|||||||
@@ -41,8 +41,10 @@ func (h *HTTPProtocol) Dispatch(w io.Writer, req *http.Request) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range res.Header {
|
for k, vs := range res.Header {
|
||||||
rw.Header()[k] = v
|
for _, v := range vs {
|
||||||
|
rw.Header().Add(k, v) // on top of any specified on the route
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rw.WriteHeader(res.StatusCode)
|
rw.WriteHeader(res.StatusCode)
|
||||||
// TODO should we TCP_CORK ?
|
// TODO should we TCP_CORK ?
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/fnproject/fn/api"
|
"github.com/fnproject/fn/api"
|
||||||
"github.com/fnproject/fn/api/agent"
|
"github.com/fnproject/fn/api/agent"
|
||||||
@@ -58,8 +59,9 @@ func (s *Server) serve(c *gin.Context, appName, path string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO we could add FireBeforeDispatch right here with Call in hand
|
// TODO we could add FireBeforeDispatch right here with Call in hand
|
||||||
|
model := call.Model()
|
||||||
|
|
||||||
if model := call.Model(); model.Type == "async" {
|
if model.Type == "async" {
|
||||||
// TODO we should push this into GetCall somehow (CallOpt maybe) or maybe agent.Queue(Call) ?
|
// TODO we should push this into GetCall somehow (CallOpt maybe) or maybe agent.Queue(Call) ?
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, c.Request.ContentLength)) // TODO sync.Pool me
|
buf := bytes.NewBuffer(make([]byte, 0, c.Request.ContentLength)) // TODO sync.Pool me
|
||||||
_, err := buf.ReadFrom(c.Request.Body)
|
_, err := buf.ReadFrom(c.Request.Body)
|
||||||
@@ -86,6 +88,10 @@ func (s *Server) serve(c *gin.Context, appName, path string) {
|
|||||||
// we could filter that error out here too as right now it yells a little
|
// we could filter that error out here too as right now it yells a little
|
||||||
|
|
||||||
if err == context.DeadlineExceeded {
|
if err == context.DeadlineExceeded {
|
||||||
|
// TODO maneuver
|
||||||
|
// add this, since it means that start may not have been called [and it's relevant]
|
||||||
|
c.Writer.Header().Add("XXX-FXLB-WAIT", time.Now().Sub(time.Time(model.CreatedAt)).String())
|
||||||
|
|
||||||
err = models.ErrCallTimeout // 504 w/ friendly note
|
err = models.ErrCallTimeout // 504 w/ friendly note
|
||||||
}
|
}
|
||||||
// NOTE: if the task wrote the headers already then this will fail to write
|
// NOTE: if the task wrote the headers already then this will fail to write
|
||||||
|
|||||||
@@ -31,15 +31,18 @@ To read in the function body, just read from STDIN.
|
|||||||
|
|
||||||
You will also have access to a set of environment variables.
|
You will also have access to a set of environment variables.
|
||||||
|
|
||||||
* REQUEST_URL - the full URL for the request ([parsing example](https://github.com/fnproject/fn/tree/master/examples/tutorial/params))
|
* `FN_REQUEST_URL` - the full URL for the request ([parsing example](https://github.com/fnproject/fn/tree/master/examples/tutorial/params))
|
||||||
* APP_NAME - the name of the application that matched this route, eg: `myapp`
|
* `FN_APP_NAME` - the name of the application that matched this route, eg: `myapp`
|
||||||
* ROUTE - the matched route, eg: `/hello`
|
* `FN_ROUTE` - the matched route, eg: `/hello`
|
||||||
* METHOD - the HTTP method for the request, eg: `GET` or `POST`
|
* `FN_METHOD` - the HTTP method for the request, eg: `GET` or `POST`
|
||||||
* CALL_ID - a unique ID for each function execution.
|
* `FN_CALL_ID` - a unique ID for each function execution.
|
||||||
* FORMAT - a string representing one of the [function formats](function-format.md), currently either `default` or `http`. Default is `default`.
|
* `FN_FORMAT` - a string representing one of the [function formats](function-format.md), currently either `default` or `http`. Default is `default`.
|
||||||
* HEADER_X - the HTTP headers that were set for this request. Replace X with the upper cased name of the header and replace dashes in the header with underscores.
|
* `FN_MEMORY` - a number representing the amount of memory available to the call, in MB
|
||||||
* X - any [configuration values](https://gitlab.oracledx.com/odx/functions/blob/master/fn/README.md#application-level-configuration) you've set
|
* `FN_TYPE` - the type for this call, currently 'sync' or 'async'
|
||||||
|
* `FN_HEADER_$X` - the HTTP headers that were set for this request. Replace $X with the upper cased name of the header and replace dashes in the header with underscores.
|
||||||
|
* `$X` - any [configuration values](https://gitlab.oracledx.com/odx/functions/blob/master/fn/README.md#application-level-configuration) you've set
|
||||||
for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: `minio_secret=secret` will be exposed via MINIO_SECRET env var.
|
for the Application or the Route. Replace X with the upper cased name of the config variable you set. Ex: `minio_secret=secret` will be exposed via MINIO_SECRET env var.
|
||||||
|
* `FN_PARAM_$Y` - any variables found from parsing the URL. Replace $Y with any `:var` from the url.
|
||||||
|
|
||||||
Warning: these may change before release.
|
Warning: these may change before release.
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
var noAuth = map[string]interface{}{}
|
var noAuth = map[string]interface{}{}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
request := fmt.Sprintf("%s %s", os.Getenv("METHOD"), os.Getenv("ROUTE"))
|
request := fmt.Sprintf("%s %s", os.Getenv("FN_METHOD"), os.Getenv("FN_ROUTE"))
|
||||||
|
|
||||||
dbURI := os.Getenv("DB")
|
dbURI := os.Getenv("DB")
|
||||||
if dbURI == "" {
|
if dbURI == "" {
|
||||||
@@ -36,7 +36,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GETTING TOKEN
|
// GETTING TOKEN
|
||||||
if os.Getenv("ROUTE") == "/token" {
|
if os.Getenv("FN_ROUTE") == "/token" {
|
||||||
route.HandleToken(db)
|
route.HandleToken(db)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func HandlePostRead(db *database.Database, auth map[string]interface{}) {
|
func HandlePostRead(db *database.Database, auth map[string]interface{}) {
|
||||||
id := os.Getenv("PARAM_ID")
|
id := os.Getenv("FN_PARAM_ID")
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
SendError("Missing post ID")
|
SendError("Missing post ID")
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func HandleToken(db *database.Database) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Authentication() (map[string]interface{}, bool) {
|
func Authentication() (map[string]interface{}, bool) {
|
||||||
authorization := os.Getenv("HEADER_AUTHORIZATION")
|
authorization := os.Getenv("FN_HEADER_AUTHORIZATION")
|
||||||
|
|
||||||
p := strings.Split(authorization, " ")
|
p := strings.Split(authorization, " ")
|
||||||
if len(p) <= 1 {
|
if len(p) <= 1 {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ docker rm test-mongo-func
|
|||||||
|
|
||||||
docker run -p 27017:27017 --name test-mongo-func -d mongo
|
docker run -p 27017:27017 --name test-mongo-func -d mongo
|
||||||
|
|
||||||
echo '{ "title": "My New Post", "body": "Hello world!", "user": "test" }' | docker run --rm -i -e METHOD=POST -e ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog
|
echo '{ "title": "My New Post", "body": "Hello world!", "user": "test" }' | docker run --rm -i -e FN_METHOD=POST -e FN_ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog
|
||||||
docker run --rm -i -e METHOD=GET -e ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog
|
docker run --rm -i -e FN_METHOD=GET -e FN_ROUTE=/posts -e DB=mongo:27017 --link test-mongo-func:mongo -e TEST=1 username/func-blog
|
||||||
|
|
||||||
docker stop test-mongo-func
|
docker stop test-mongo-func
|
||||||
docker rm test-mongo-func
|
docker rm test-mongo-func
|
||||||
|
|||||||
@@ -21,21 +21,21 @@ if payload != ""
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Also check for expected env vars: https://gitlab.oracledx.com/odx/functions/blob/master/docs/writing.md#inputs
|
# Also check for expected env vars: https://gitlab.oracledx.com/odx/functions/blob/master/docs/writing.md#inputs
|
||||||
e = ENV["REQUEST_URL"]
|
e = ENV["FN_REQUEST_URL"]
|
||||||
puts e
|
puts e
|
||||||
uri = URI.parse(e)
|
uri = URI.parse(e)
|
||||||
if !uri.scheme.start_with?('http')
|
if !uri.scheme.start_with?('http')
|
||||||
raise "invalid REQUEST_URL, does not start with http"
|
raise "invalid REQUEST_URL, does not start with http"
|
||||||
end
|
end
|
||||||
e = ENV["METHOD"]
|
e = ENV["FN_METHOD"]
|
||||||
if !(e == "GET" || e == "POST" || e == "DELETE" || e == "PATCH" || e == "PUT")
|
if !(e == "GET" || e == "POST" || e == "DELETE" || e == "PATCH" || e == "PUT")
|
||||||
raise "Invalid METHOD: #{e}"
|
raise "Invalid METHOD: #{e}"
|
||||||
end
|
end
|
||||||
e = ENV["APP_NAME"]
|
e = ENV["FN_APP_NAME"]
|
||||||
if e == nil || e == ''
|
if e == nil || e == ''
|
||||||
raise "No APP_NAME found"
|
raise "No APP_NAME found"
|
||||||
end
|
end
|
||||||
e = ENV["ROUTE"]
|
e = ENV["FN_ROUTE"]
|
||||||
if e == nil || e == ''
|
if e == nil || e == ''
|
||||||
raise "No ROUTE found"
|
raise "No ROUTE found"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// command to execute, 'SELECT' or 'INSERT'
|
// command to execute, 'SELECT' or 'INSERT'
|
||||||
command = os.Getenv("HEADER_COMMAND")
|
command = os.Getenv("FN_HEADER_COMMAND")
|
||||||
// postgres host:port, e.g. 'postgres:5432'
|
// postgres host:port, e.g. 'postgres:5432'
|
||||||
server = os.Getenv("HEADER_SERVER")
|
server = os.Getenv("FN_HEADER_SERVER")
|
||||||
// postgres table name
|
// postgres table name
|
||||||
table = os.Getenv("HEADER_TABLE")
|
table = os.Getenv("FN_HEADER_TABLE")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
envvar := os.Getenv("HEADER_ENVVAR")
|
envvar := os.Getenv("FN_HEADER_ENVVAR")
|
||||||
if envvar != "" {
|
if envvar != "" {
|
||||||
fmt.Println("HEADER_ENVVAR:", envvar)
|
fmt.Println("FN_HEADER_ENVVAR:", envvar)
|
||||||
}
|
}
|
||||||
fmt.Println("hw")
|
fmt.Println("hw")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ tests:
|
|||||||
hw
|
hw
|
||||||
- name: envvar
|
- name: envvar
|
||||||
out: |
|
out: |
|
||||||
HEADER_ENVVAR: trololo
|
FN_HEADER_ENVVAR: trololo
|
||||||
hw
|
hw
|
||||||
env:
|
env:
|
||||||
envvar: trololo
|
envvar: trololo
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ tests:
|
|||||||
hw
|
hw
|
||||||
- name: envvar
|
- name: envvar
|
||||||
out: |
|
out: |
|
||||||
HEADER_ENVVAR: trololo
|
FN_HEADER_ENVVAR: trololo
|
||||||
hw
|
hw
|
||||||
env:
|
env:
|
||||||
envvar: trololo
|
envvar: trololo
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
s := os.Getenv("REQUEST_URL")
|
s := os.Getenv("FN_REQUEST_URL")
|
||||||
|
|
||||||
fmt.Printf("REQUEST_URL --> %v\n\n", s)
|
fmt.Printf("FN_REQUEST_URL --> %v\n\n", s)
|
||||||
|
|
||||||
u, err := url.Parse(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func main() {
|
|||||||
numLoops := 1
|
numLoops := 1
|
||||||
|
|
||||||
// Parse the query string
|
// Parse the query string
|
||||||
s := strings.Split(os.Getenv("REQUEST_URL"), "?")
|
s := strings.Split(os.Getenv("FN_REQUEST_URL"), "?")
|
||||||
if len(s) > 1 {
|
if len(s) > 1 {
|
||||||
for _, pair := range strings.Split(s[1], "&") {
|
for _, pair := range strings.Split(s[1], "&") {
|
||||||
kv := strings.Split(pair, "=")
|
kv := strings.Split(pair, "=")
|
||||||
|
|||||||
Reference in New Issue
Block a user