1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-04 21:35:23 +02:00

fix(edgejobs): decouple the Edge Jobs from the reverse tunnel service BE-10866 (#11)

This commit is contained in:
andres-portainer 2024-10-14 10:37:13 -03:00 committed by GitHub
parent 7a35b5b0e4
commit 61c5269353
14 changed files with 179 additions and 243 deletions

View file

@ -9,6 +9,7 @@ import (
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/dataservices"
"github.com/portainer/portainer/api/http/middlewares"
"github.com/portainer/portainer/api/internal/edge/cache"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/request"
"github.com/portainer/portainer/pkg/libhttp/response"
@ -84,8 +85,7 @@ func (handler *Handler) getEdgeJobLobs(tx dataservices.DataStoreTx, endpointID p
return httperror.InternalServerError("Unable to find an edge job with the specified identifier inside the database", err)
}
err = handler.FileService.StoreEdgeJobTaskLogFileFromBytes(strconv.Itoa(int(edgeJobID)), strconv.Itoa(int(endpointID)), []byte(payload.FileContent))
if err != nil {
if err := handler.FileService.StoreEdgeJobTaskLogFileFromBytes(strconv.Itoa(int(edgeJobID)), strconv.Itoa(int(endpoint.ID)), []byte(payload.FileContent)); err != nil {
return httperror.InternalServerError("Unable to save task log to the filesystem", err)
}
@ -96,13 +96,11 @@ func (handler *Handler) getEdgeJobLobs(tx dataservices.DataStoreTx, endpointID p
edgeJob.Endpoints[endpoint.ID] = meta
}
err = tx.EdgeJob().Update(edgeJob.ID, edgeJob)
handler.ReverseTunnelService.AddEdgeJob(endpoint, edgeJob)
if err != nil {
if err := tx.EdgeJob().Update(edgeJob.ID, edgeJob); err != nil {
return httperror.InternalServerError("Unable to persist edge job changes to the database", err)
}
cache.Del(endpointID)
return nil
}

View file

@ -170,7 +170,7 @@ func (handler *Handler) inspectStatus(tx dataservices.DataStoreTx, r *http.Reque
Credentials: tunnel.Credentials,
}
schedules, handlerErr := handler.buildSchedules(endpoint.ID)
schedules, handlerErr := handler.buildSchedules(tx, endpoint.ID)
if handlerErr != nil {
return nil, handlerErr
}
@ -208,9 +208,33 @@ func parseAgentPlatform(r *http.Request) (portainer.EndpointType, error) {
}
}
func (handler *Handler) buildSchedules(endpointID portainer.EndpointID) ([]edgeJobResponse, *httperror.HandlerError) {
func (handler *Handler) buildSchedules(tx dataservices.DataStoreTx, endpointID portainer.EndpointID) ([]edgeJobResponse, *httperror.HandlerError) {
schedules := []edgeJobResponse{}
for _, job := range handler.ReverseTunnelService.EdgeJobs(endpointID) {
edgeJobs, err := tx.EdgeJob().ReadAll()
if err != nil {
return nil, httperror.InternalServerError("Unable to retrieve Edge Jobs", err)
}
for _, job := range edgeJobs {
_, endpointHasJob := job.Endpoints[endpointID]
if !endpointHasJob {
for _, edgeGroupID := range job.EdgeGroups {
member, _, err := edge.EndpointInEdgeGroup(tx, endpointID, edgeGroupID)
if err != nil {
return nil, httperror.InternalServerError("Unable to retrieve relations", err)
} else if member {
endpointHasJob = true
break
}
}
}
if !endpointHasJob {
continue
}
var collectLogs bool
if _, ok := job.GroupLogsCollection[endpointID]; ok {
collectLogs = job.GroupLogsCollection[endpointID].CollectLogs

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
@ -36,7 +37,7 @@ var endpointTestCases = []endpointTestCase{
{
portainer.Endpoint{
ID: -1,
Name: "endpoint-id--1",
Name: "endpoint-id-1",
Type: portainer.EdgeAgentOnDockerEnvironment,
URL: "https://portainer.io:9443",
EdgeID: "edge-id",
@ -342,28 +343,48 @@ func TestEdgeStackStatus(t *testing.T) {
func TestEdgeJobsResponse(t *testing.T) {
handler := mustSetupHandler(t)
endpointID := portainer.EndpointID(77)
endpoint := portainer.Endpoint{
ID: endpointID,
Name: "test-endpoint-77",
Type: portainer.EdgeAgentOnDockerEnvironment,
URL: "https://portainer.io:9443",
EdgeID: "edge-id",
LastCheckInDate: time.Now().Unix(),
localCreateEndpoint := func(endpointID portainer.EndpointID, tagIDs []portainer.TagID) *portainer.Endpoint {
endpoint := portainer.Endpoint{
ID: endpointID,
Name: "test-endpoint-" + strconv.Itoa(int(endpointID)),
Type: portainer.EdgeAgentOnDockerEnvironment,
URL: "https://portainer.io:9443",
EdgeID: "edge-id-" + strconv.Itoa(int(endpointID)),
TagIDs: tagIDs,
LastCheckInDate: time.Now().Unix(),
UserTrusted: true,
}
err := createEndpoint(handler, endpoint,
portainer.EndpointRelation{EndpointID: endpointID})
require.NoError(t, err)
return &endpoint
}
endpointRelation := portainer.EndpointRelation{
EndpointID: endpoint.ID,
}
dynamicGroupTags := []portainer.TagID{1, 2, 3}
if err := createEndpoint(handler, endpoint, endpointRelation); err != nil {
t.Fatal(err)
endpoint := localCreateEndpoint(77, nil)
endpointFromStaticEdgeGroup := localCreateEndpoint(78, nil)
endpointFromDynamicEdgeGroup := localCreateEndpoint(79, dynamicGroupTags)
unrelatedEndpoint := localCreateEndpoint(80, nil)
staticEdgeGroup := portainer.EdgeGroup{
ID: 1,
Endpoints: []portainer.EndpointID{endpointFromStaticEdgeGroup.ID},
}
err := handler.DataStore.EdgeGroup().Create(&staticEdgeGroup)
require.NoError(t, err)
dynamicEdgeGroup := portainer.EdgeGroup{
ID: 2,
Dynamic: true,
TagIDs: dynamicGroupTags,
}
err = handler.DataStore.EdgeGroup().Create(&dynamicEdgeGroup)
require.NoError(t, err)
path, err := handler.FileService.StoreEdgeJobFileFromBytes("test-script", []byte("pwd"))
if err != nil {
t.Fatal(err)
}
require.NoError(t, err)
edgeJobID := portainer.EdgeJobID(35)
edgeJob := portainer.EdgeJob{
@ -374,32 +395,42 @@ func TestEdgeJobsResponse(t *testing.T) {
ScriptPath: path,
Recurring: true,
Version: 57,
Endpoints: map[portainer.EndpointID]portainer.EdgeJobEndpointMeta{
endpoint.ID: {},
},
EdgeGroups: []portainer.EdgeGroupID{staticEdgeGroup.ID, dynamicEdgeGroup.ID},
}
handler.ReverseTunnelService.AddEdgeJob(&endpoint, &edgeJob)
err = handler.DataStore.EdgeJob().Create(&edgeJob)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
if err != nil {
t.Fatal("request error:", err)
f := func(endpoint *portainer.Endpoint, scheduleLen int) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
require.NoError(t, err)
req.Header.Set(portainer.PortainerAgentEdgeIDHeader, endpoint.EdgeID)
req.Header.Set(portainer.HTTPResponseAgentPlatform, "1")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
var data endpointEdgeStatusInspectResponse
err = json.NewDecoder(rec.Body).Decode(&data)
require.NoError(t, err)
require.Len(t, data.Schedules, scheduleLen)
if scheduleLen > 0 {
require.Equal(t, edgeJob.ID, data.Schedules[0].ID)
require.Equal(t, edgeJob.CronExpression, data.Schedules[0].CronExpression)
require.Equal(t, edgeJob.Version, data.Schedules[0].Version)
}
}
req.Header.Set(portainer.PortainerAgentEdgeIDHeader, "edge-id")
req.Header.Set(portainer.HTTPResponseAgentPlatform, "1")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected a %d response, found: %d", http.StatusOK, rec.Code)
}
var data endpointEdgeStatusInspectResponse
if err := json.NewDecoder(rec.Body).Decode(&data); err != nil {
t.Fatal("error decoding response:", err)
}
assert.Len(t, data.Schedules, 1)
assert.Equal(t, edgeJob.ID, data.Schedules[0].ID)
assert.Equal(t, edgeJob.CronExpression, data.Schedules[0].CronExpression)
assert.Equal(t, edgeJob.Version, data.Schedules[0].Version)
f(endpoint, 1)
f(endpointFromStaticEdgeGroup, 1)
f(endpointFromDynamicEdgeGroup, 1)
f(unrelatedEndpoint, 0)
}