owned this note
owned this note
Published
Linked with GitHub
# \[Proposal\] Add support for plugins to functions-framework
## Motivation
We need to add observability capabilities to functions, which facilitate observing and tracking the operation of functions in large-scale scenarios.
Referring to [#7](https://github.com/OpenFunction/functions-framework/issues/8), we can take the form of a plugin in functions-framework to wake up the observability component to run at a specific node.
For example, in [functions-framework-go](https://github.com/OpenFunction/functions-framework-go), we can add a plugin hook before and after the function is run, and run the logic related to the observability component in the hook.
Reference [this](https://github.com/OpenFunction/functions-framework-go/blob/81a7b2951b8af0897978dcc483c1217ac98f02fb/functionframeworks/frameworks.go#L133):
```go
func registerHTTPFunction(path string, fn func(http.ResponseWriter, *http.Request), h *http.ServeMux) error {
h.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
defer recoverPanicHTTP(w, "Function panic")
// execute pre-run plugins
fn(w, r)
// execute post-run plugins
})
return nil
}
```
We should also consider as much as possible the consistency of the scheme's implementation in different languages.
## Associated issueses
https://github.com/OpenFunction/OpenFunction/issues/146
https://github.com/OpenFunction/functions-framework/issues/8
https://github.com/OpenFunction/functions-framework/issues/9
## Goals
1. design a common interface in functions-framework-go to support the tracing capabilities of SkyAPM/Go2Sky and OpenTelemetry
2. improve functions-framework-nodejs (to support observability capabilities)
3. improve builder (rewrite the builder to make it better support functions-framework)
## Proposal
I'll use `function-framework-go` as an example to illustrate how functions-framework works in this proposal:
1. Directory structure
```
\- context
\- context.go
\- functionframeworks
\- knative
\- knative.go
\- async
\- aysnc.go
\- frameworks.go
\- plugin
\- plugin-a
\- plugin-a.go
\- plugin-b
\- plugin-b.go
\- interface.go
```
**context**: Contains logic for handling OpenFunction Context related matters.
**functionframeworks**: Contains logic for handling function conversions on different serving runtimes.
**plugin**: Contains logic for handling plugins.
2. context
Responsible for maintaining the OpenFunction Context structure, which provides the following functions:
- Parse the OpenFunction Context
- Dock to the function input source and direct the input data to the function
- Dock to the function output target and direct the function data to the output target
- Encapsulate the return values of function
- Provide an interface to call plugins
3. frameworks
Responsible for handling function conversion logic on different runtimes, which provides the following functions:
- Depending on the serving runtime currently supported by OpenFunction, there are two implementations, `knative` and `async`
- Register functions and associate user functions to listening service
- Fetch the plugins from OpenFunction Context and call the plugin interface to execute the plugins
- Start the listening service
4. plugin
Responsible for handling plugins, which provides the following functions:
- Register plugins
- Execute function pre-run plugins
- Execute function post-run plugins
Example:
Demonstrating the logic of the function conversion section in knative runtime:
```go
// Uniform function signature as func(*ofctx.OpenFunctionContext, []byte) ofctx.RetValue
func (r *Runtime) RegisterFunction(ctx context.Context, fn func(*ofctx.OpenFunctionContext, []byte) ofctx.RetValue) error {
// Register the handler function with the path r.pattern
r.handler.HandleFunc(r.pattern, func(w http.ResponseWriter, r *http.Request) {
defer functionframeworks.RecoverPanicHTTP(w, "Function panic")
// Parse the OpenFunction Context and return a Context instance
c, err := functionframeworks.ParseOpenFunctionContext()
...
// Get the content of the http request and return data of type []byte as function input
in, err := c.GetInput(r)
...
// Fetch the list of plugins from the Context and execute the PrePhase stage plugins here
r.ProcessPlugins(ctx)
...
// Execute the user function and get the return value
ret := fn(c, in)
// Fetch the list of plugins from the Context and execute the PostPhase stage plugins here
err := r.ProcessPlugins(ctx, ofctx.GetPlugins(ofctx.PostPhase))
// Write the return value of the function to the http response
w.Write(ret.Data)
})
return nil
}
func (r *Runtime) ProcessPlugins(ctx context.Context, plugins []plugin.Interface) error {
for _, plg := range plugins {
if err := plg.Exec(ctx); err != nil {
return err
}
}
return nil
}
// Start the server
func (r *Runtime) Start(ctx context.Context, ofctx *ofctx.OpenFunctionContext) error {
log.Info("Knative Function serving http: listening on port %s", r.port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", r.port), r.handler))
return nil
}
```