mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
fix(performance): optimize performance for edge EE-3311 (#8040)
This commit is contained in:
parent
3d28a6f877
commit
dd0d1737b0
23 changed files with 577 additions and 164 deletions
|
@ -1,17 +1,23 @@
|
|||
package endpointedge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/middlewares"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
)
|
||||
|
||||
type stackStatusResponse struct {
|
||||
|
@ -64,9 +70,27 @@ type endpointEdgeStatusInspectResponse struct {
|
|||
// @failure 500 "Server error"
|
||||
// @router /endpoints/{id}/edge/status [get]
|
||||
func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpoint, err := middlewares.FetchEndpoint(r)
|
||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Unable to find an environment on request context", err)
|
||||
return httperror.BadRequest("Invalid environment identifier route variable", err)
|
||||
}
|
||||
|
||||
cachedResp := handler.respondFromCache(w, r, portainer.EndpointID(endpointID))
|
||||
if cachedResp {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := handler.DataStore.Endpoint().Heartbeat(portainer.EndpointID(endpointID)); !ok {
|
||||
return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", nil)
|
||||
}
|
||||
|
||||
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||
if err != nil {
|
||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||
return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint)
|
||||
|
@ -129,7 +153,7 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http
|
|||
}
|
||||
statusResponse.Stacks = edgeStacksStatus
|
||||
|
||||
return response.JSON(w, statusResponse)
|
||||
return cacheResponse(w, endpoint.ID, statusResponse)
|
||||
}
|
||||
|
||||
func parseAgentPlatform(r *http.Request) (portainer.EndpointType, error) {
|
||||
|
@ -191,17 +215,75 @@ func (handler *Handler) buildEdgeStacks(endpointID portainer.EndpointID) ([]stac
|
|||
|
||||
edgeStacksStatus := []stackStatusResponse{}
|
||||
for stackID := range relation.EdgeStacks {
|
||||
stack, err := handler.DataStore.EdgeStack().EdgeStack(stackID)
|
||||
if err != nil {
|
||||
version, ok := handler.DataStore.EdgeStack().EdgeStackVersion(stackID)
|
||||
if !ok {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve edge stack from the database", err)
|
||||
}
|
||||
|
||||
stackStatus := stackStatusResponse{
|
||||
ID: stack.ID,
|
||||
Version: stack.Version,
|
||||
ID: stackID,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
edgeStacksStatus = append(edgeStacksStatus, stackStatus)
|
||||
}
|
||||
|
||||
return edgeStacksStatus, nil
|
||||
}
|
||||
|
||||
func cacheResponse(w http.ResponseWriter, endpointID portainer.EndpointID, statusResponse endpointEdgeStatusInspectResponse) *httperror.HandlerError {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
httpErr := response.JSON(rr, statusResponse)
|
||||
if httpErr != nil {
|
||||
return httpErr
|
||||
}
|
||||
|
||||
h := fnv.New32a()
|
||||
h.Write(rr.Body.Bytes())
|
||||
etag := strconv.FormatUint(uint64(h.Sum32()), 16)
|
||||
|
||||
cache.Set(endpointID, []byte(etag))
|
||||
|
||||
resp := rr.Result()
|
||||
|
||||
for k, vs := range resp.Header {
|
||||
for _, v := range vs {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("ETag", etag)
|
||||
io.Copy(w, resp.Body)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) respondFromCache(w http.ResponseWriter, r *http.Request, endpointID portainer.EndpointID) bool {
|
||||
inmHeader := r.Header.Get("If-None-Match")
|
||||
etags := strings.Split(inmHeader, ",")
|
||||
|
||||
if len(inmHeader) == 0 || etags[0] == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
cachedETag, ok := cache.Get(endpointID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, etag := range etags {
|
||||
if !bytes.Equal([]byte(etag), cachedETag) {
|
||||
continue
|
||||
}
|
||||
|
||||
handler.DataStore.Endpoint().UpdateHeartbeat(endpointID)
|
||||
|
||||
w.Header().Set("ETag", etag)
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ func TestMissingEdgeIdentifier(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpointID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpointID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ func TestWithEndpoints(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", test.endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", test.endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ func TestLastCheckInDateIncreases(t *testing.T) {
|
|||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ func TestEmptyEdgeIdWithAgentPlatformHeader(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -348,7 +348,7 @@ func TestEdgeStackStatus(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -418,7 +418,7 @@ func TestEdgeJobsResponse(t *testing.T) {
|
|||
|
||||
handler.ReverseTunnelService.AddEdgeJob(endpoint.ID, &edgeJob)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
|
|
@ -31,14 +31,16 @@ func NewHandler(bouncer *security.RequestBouncer, dataStore dataservices.DataSto
|
|||
ReverseTunnelService: reverseTunnelService,
|
||||
}
|
||||
|
||||
endpointRouter := h.PathPrefix("/{id}").Subrouter()
|
||||
h.Handle("/api/endpoints/{id}/edge/status", bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStatusInspect))).Methods(http.MethodGet)
|
||||
|
||||
endpointRouter := h.PathPrefix("/api/endpoints/{id}").Subrouter()
|
||||
endpointRouter.Use(middlewares.WithEndpoint(dataStore.Endpoint(), "id"))
|
||||
|
||||
endpointRouter.PathPrefix("/edge/status").Handler(
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStatusInspect))).Methods(http.MethodGet)
|
||||
endpointRouter.PathPrefix("/edge/stacks/{stackId}").Handler(
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStackInspect))).Methods(http.MethodGet)
|
||||
|
||||
endpointRouter.PathPrefix("/edge/jobs/{jobID}/logs").Handler(
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeJobsLogs))).Methods(http.MethodPost)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue