[MM-55268] Implement ServeMetrics plugins hook (#24249)
* Implement ServeMetrics plugins hook * Update error id * Simplify * Revert "Simplify" This reverts commit c9dc5d5eac6c933ff69e158cf3e34d0973bd3bcd. * Add comment and error handler * Wrap error * Update translation file --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
926142ca22
Коммит
aa3a12f183
@@ -953,3 +953,107 @@ func (s *apiRPCServer) UploadData(args *Z_UploadDataArgs, returns *Z_UploadDataR
|
||||
returns.A, returns.B = hook.UploadData(args.A, pluginReader)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
hookNameToId["ServeMetrics"] = ServeMetricsID
|
||||
}
|
||||
|
||||
type Z_ServeMetricsArgs struct {
|
||||
ResponseWriterStream uint32
|
||||
Request *http.Request
|
||||
Context *Context
|
||||
RequestBodyStream uint32
|
||||
}
|
||||
|
||||
func (g *hooksRPCClient) ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if !g.implemented[ServeMetricsID] {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
serveMetricsStreamId := g.muxBroker.NextId()
|
||||
go func() {
|
||||
connection, err := g.muxBroker.Accept(serveMetricsStreamId)
|
||||
if err != nil {
|
||||
g.log.Error("Plugin failed to ServeMetrics, muxBroker couldn't accept connection", mlog.Uint32("serve_http_stream_id", serveMetricsStreamId), mlog.Err(err))
|
||||
return
|
||||
}
|
||||
defer connection.Close()
|
||||
|
||||
rpcServer := rpc.NewServer()
|
||||
if err := rpcServer.RegisterName("Plugin", &httpResponseWriterRPCServer{w: w, log: g.log}); err != nil {
|
||||
g.log.Error("Plugin failed to ServeMetrics, couldn't register RPC name", mlog.Err(err))
|
||||
return
|
||||
}
|
||||
rpcServer.ServeConn(connection)
|
||||
}()
|
||||
|
||||
requestBodyStreamId := uint32(0)
|
||||
if r.Body != nil {
|
||||
requestBodyStreamId = g.muxBroker.NextId()
|
||||
go func() {
|
||||
bodyConnection, err := g.muxBroker.Accept(requestBodyStreamId)
|
||||
if err != nil {
|
||||
g.log.Error("Plugin failed to ServeMetrics, muxBroker couldn't Accept request body connection", mlog.Err(err))
|
||||
return
|
||||
}
|
||||
defer bodyConnection.Close()
|
||||
serveIOReader(r.Body, bodyConnection)
|
||||
}()
|
||||
}
|
||||
|
||||
forwardedRequest := &http.Request{
|
||||
Method: r.Method,
|
||||
URL: r.URL,
|
||||
Proto: r.Proto,
|
||||
ProtoMajor: r.ProtoMajor,
|
||||
ProtoMinor: r.ProtoMinor,
|
||||
Header: r.Header,
|
||||
Host: r.Host,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
RequestURI: r.RequestURI,
|
||||
}
|
||||
|
||||
if err := g.client.Call("Plugin.ServeMetrics", Z_ServeMetricsArgs{
|
||||
Context: c,
|
||||
ResponseWriterStream: serveMetricsStreamId,
|
||||
Request: forwardedRequest,
|
||||
RequestBodyStream: requestBodyStreamId,
|
||||
}, nil); err != nil {
|
||||
g.log.Error("Plugin failed to ServeMetrics, RPC call failed", mlog.Err(err))
|
||||
http.Error(w, "500 internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *hooksRPCServer) ServeMetrics(args *Z_ServeMetricsArgs, returns *struct{}) error {
|
||||
connection, err := s.muxBroker.Dial(args.ResponseWriterStream)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote response writer stream, error: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
w := connectHTTPResponseWriter(connection)
|
||||
defer w.Close()
|
||||
|
||||
r := args.Request
|
||||
if args.RequestBodyStream != 0 {
|
||||
connection, err := s.muxBroker.Dial(args.RequestBodyStream)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ERROR] Can't connect to remote request body stream, error: %v", err.Error())
|
||||
return err
|
||||
}
|
||||
r.Body = connectIOReader(connection)
|
||||
} else {
|
||||
r.Body = io.NopCloser(&bytes.Buffer{})
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
if hook, ok := s.impl.(interface {
|
||||
ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
}); ok {
|
||||
hook.ServeMetrics(args.Context, w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ const (
|
||||
UserHasBeenDeactivatedID = 36
|
||||
MessageHasBeenDeletedID = 37
|
||||
MessagesWillBeConsumedID = 38
|
||||
ServeMetricsID = 39
|
||||
TotalHooksID = iota
|
||||
)
|
||||
|
||||
@@ -322,4 +323,11 @@ type Hooks interface {
|
||||
//
|
||||
// Minimum server version: 9.1
|
||||
UserHasBeenDeactivated(c *Context, user *model.User)
|
||||
|
||||
// ServeMetrics allows plugins to expose their own metrics endpoint through
|
||||
// the server's metrics HTTP listener (e.g. "localhost:8067").
|
||||
// Requests destined to the /plugins/{id}/metrics path will be routed to the plugin.
|
||||
//
|
||||
// Minimum server version: 9.2
|
||||
ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
@@ -244,3 +244,9 @@ func (hooks *hooksTimerLayer) UserHasBeenDeactivated(c *Context, user *model.Use
|
||||
hooks.hooksImpl.UserHasBeenDeactivated(c, user)
|
||||
hooks.recordTime(startTime, "UserHasBeenDeactivated", true)
|
||||
}
|
||||
|
||||
func (hooks *hooksTimerLayer) ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
startTime := timePkg.Now()
|
||||
hooks.hooksImpl.ServeMetrics(c, w, r)
|
||||
hooks.recordTime(startTime, "ServeMetrics", true)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ var excludedPluginHooks = []string{
|
||||
"PluginHTTP",
|
||||
"ServeHTTP",
|
||||
"UploadData",
|
||||
"ServeMetrics",
|
||||
}
|
||||
|
||||
var excludedProductHooks = []string{
|
||||
|
||||
@@ -360,6 +360,11 @@ func (_m *Hooks) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
|
||||
_m.Called(c, w, r)
|
||||
}
|
||||
|
||||
// ServeMetrics provides a mock function with given fields: c, w, r
|
||||
func (_m *Hooks) ServeMetrics(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||
_m.Called(c, w, r)
|
||||
}
|
||||
|
||||
// UserHasBeenCreated provides a mock function with given fields: c, user
|
||||
func (_m *Hooks) UserHasBeenCreated(c *plugin.Context, user *model.User) {
|
||||
_m.Called(c, user)
|
||||
|
||||
@@ -9,6 +9,7 @@ package plugin
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
@@ -134,6 +135,10 @@ type UserHasBeenDeactivatedIFace interface {
|
||||
UserHasBeenDeactivated(c *Context, user *model.User)
|
||||
}
|
||||
|
||||
type ServeMetricsIFace interface {
|
||||
ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type HooksAdapter struct {
|
||||
implemented map[int]struct{}
|
||||
productHooks any
|
||||
@@ -417,6 +422,15 @@ func NewAdapter(productHooks any) (*HooksAdapter, error) {
|
||||
return nil, errors.New("hook has UserHasBeenDeactivated method but does not implement plugin.UserHasBeenDeactivated interface")
|
||||
}
|
||||
|
||||
// Assessing the type of the productHooks if it individually implements ServeMetrics interface.
|
||||
tt = reflect.TypeOf((*ServeMetricsIFace)(nil)).Elem()
|
||||
|
||||
if ft.Implements(tt) {
|
||||
a.implemented[ServeMetricsID] = struct{}{}
|
||||
} else if _, ok := ft.MethodByName("ServeMetrics"); ok {
|
||||
return nil, errors.New("hook has ServeMetrics method but does not implement plugin.ServeMetrics interface")
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
@@ -689,3 +703,12 @@ func (a *HooksAdapter) UserHasBeenDeactivated(c *Context, user *model.User) {
|
||||
a.productHooks.(UserHasBeenDeactivatedIFace).UserHasBeenDeactivated(c, user)
|
||||
|
||||
}
|
||||
|
||||
func (a *HooksAdapter) ServeMetrics(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := a.implemented[ServeMetricsID]; !ok {
|
||||
panic("product hooks must implement ServeMetrics")
|
||||
}
|
||||
|
||||
a.productHooks.(ServeMetricsIFace).ServeMetrics(c, w, r)
|
||||
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user