From 7b20f6f8fba814ae5449aed71de931135bd17c62 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Tue, 12 May 2020 15:00:48 +0200 Subject: [PATCH] Plugin restapi: New plugin to provide a REST API. --- restapi/README.md | 36 ++++++++++++++++++++++ restapi/restapi.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 restapi/README.md create mode 100644 restapi/restapi.go diff --git a/restapi/README.md b/restapi/README.md new file mode 100644 index 0000000..59bcaed --- /dev/null +++ b/restapi/README.md @@ -0,0 +1,36 @@ +# restapi + +Plugin **restapi** provides a REST API to communicate with the *collectd* +daemon. + +## Description + +The *restapi plugin* starts a webserver and waits for incoming REST API +requests. + +## Building + +To build this plugin, the collectd header files are required. + +On Debian and Ubuntu, the collectd headers are available from the +`collectd-dev` package. Once installed, add the import paths to the +`CGI_CPPFLAGS`: + +```bash +export CGO_CPPFLAGS="-I/usr/include/collectd/core/daemon \ +-I/usr/include/collectd/core -I/usr/include/collectd" +``` + +Alternatively, you can grab the collectd sources, run the `configure` script, +and reference the header files from there: + +```bash +TOP_SRCDIR="${HOME}/collectd" +export CGO_CPPFLAGS="-I${TOP_SRCDIR}/src -I${TOP_SRCDIR}/src/daemon" +``` + +Then build the plugin with the "c-shared" buildmode: + +```bash +go build -buildmode=c-shared -o restapi.so +``` diff --git a/restapi/restapi.go b/restapi/restapi.go new file mode 100644 index 0000000..55a956e --- /dev/null +++ b/restapi/restapi.go @@ -0,0 +1,76 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "collectd.org/api" + "collectd.org/plugin" + "go.uber.org/multierr" +) + +const pluginName = "restapi" + +type restapi struct { + srv *http.Server +} + +func init() { + mux := http.NewServeMux() + mux.HandleFunc("/valueList", valueListHandler) + + api := restapi{ + srv: &http.Server{ + Addr: ":8080", + Handler: mux, + }, + } + + go func() { + if err := api.srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + plugin.Errorf("%s plugin: ListenAndServe(): %v", pluginName, err) + } + }() + + plugin.RegisterShutdown(pluginName, api) +} + +func valueListHandler(w http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + w.WriteHeader(http.StatusNotImplemented) + fmt.Fprintln(w, "Only POST is currently supported.") + return + } + + var vls []api.ValueList + if err := json.NewDecoder(req.Body).Decode(&vls); err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, "parsing JSON failed:", err) + return + } + + var errs error + for _, vl := range vls { + errs = multierr.Append(errs, + plugin.Write(req.Context(), &vl)) + } + + if errs != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintln(w, "plugin.Write():", errs) + return + } +} + +func (api restapi) Shutdown(ctx context.Context) error { + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + return api.srv.Shutdown(ctx) +} + +func main() {} // ignored