mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 05:45:22 +02:00
fix(edgegroups): convert the related endpoint IDs to roaring bitmaps to increase performance BE-12053 (#903)
This commit is contained in:
parent
caf382b64c
commit
937456596a
32 changed files with 1041 additions and 133 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
)
|
||||
|
||||
type endpointSetType map[portainer.EndpointID]bool
|
||||
|
@ -49,22 +50,29 @@ func GetEndpointsByTags(tx dataservices.DataStoreTx, tagIDs []portainer.TagID, p
|
|||
return results, nil
|
||||
}
|
||||
|
||||
func getTrustedEndpoints(tx dataservices.DataStoreTx, endpointIDs []portainer.EndpointID) ([]portainer.EndpointID, error) {
|
||||
func getTrustedEndpoints(tx dataservices.DataStoreTx, endpointIDs roar.Roar[portainer.EndpointID]) ([]portainer.EndpointID, error) {
|
||||
var innerErr error
|
||||
|
||||
results := []portainer.EndpointID{}
|
||||
for _, endpointID := range endpointIDs {
|
||||
|
||||
endpointIDs.Iterate(func(endpointID portainer.EndpointID) bool {
|
||||
endpoint, err := tx.Endpoint().Endpoint(endpointID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
innerErr = err
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if !endpoint.UserTrusted {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
results = append(results, endpoint.ID)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
return true
|
||||
})
|
||||
|
||||
return results, innerErr
|
||||
}
|
||||
|
||||
func mapEndpointGroupToEndpoints(endpoints []portainer.Endpoint) map[portainer.EndpointGroupID]endpointSetType {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
)
|
||||
|
@ -52,6 +53,7 @@ func calculateEndpointsOrTags(tx dataservices.DataStoreTx, edgeGroup *portainer.
|
|||
}
|
||||
|
||||
edgeGroup.Endpoints = endpointIDs
|
||||
edgeGroup.EndpointIDs = roar.FromSlice(endpointIDs)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -94,6 +96,7 @@ func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request)
|
|||
Dynamic: payload.Dynamic,
|
||||
TagIDs: []portainer.TagID{},
|
||||
Endpoints: []portainer.EndpointID{},
|
||||
EndpointIDs: roar.Roar[portainer.EndpointID]{},
|
||||
PartialMatch: payload.PartialMatch,
|
||||
}
|
||||
|
||||
|
@ -108,5 +111,5 @@ func (handler *Handler) edgeGroupCreate(w http.ResponseWriter, r *http.Request)
|
|||
return nil
|
||||
})
|
||||
|
||||
return txResponse(w, edgeGroup, err)
|
||||
return txResponse(w, shadowedEdgeGroup{EdgeGroup: *edgeGroup}, err)
|
||||
}
|
||||
|
|
62
api/http/handler/edgegroups/edgegroup_create_test.go
Normal file
62
api/http/handler/edgegroups/edgegroup_create_test.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEdgeGroupCreateHandler(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "Test Endpoint " + strconv.Itoa(i+1),
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EndpointRelation().Create(&portainer.EndpointRelation{
|
||||
EndpointID: portainer.EndpointID(i + 1),
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/edge_groups",
|
||||
strings.NewReader(`{"Name": "New Edge Group", "Endpoints": [1, 2, 3]}`),
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
)
|
||||
|
@ -33,7 +34,9 @@ func (handler *Handler) edgeGroupInspect(w http.ResponseWriter, r *http.Request)
|
|||
return err
|
||||
})
|
||||
|
||||
return txResponse(w, edgeGroup, err)
|
||||
edgeGroup.Endpoints = edgeGroup.EndpointIDs.ToSlice()
|
||||
|
||||
return txResponse(w, shadowedEdgeGroup{EdgeGroup: *edgeGroup}, err)
|
||||
}
|
||||
|
||||
func getEdgeGroup(tx dataservices.DataStoreTx, ID portainer.EdgeGroupID) (*portainer.EdgeGroup, error) {
|
||||
|
@ -50,7 +53,7 @@ func getEdgeGroup(tx dataservices.DataStoreTx, ID portainer.EdgeGroupID) (*porta
|
|||
return nil, httperror.InternalServerError("Unable to retrieve environments and environment groups for Edge group", err)
|
||||
}
|
||||
|
||||
edgeGroup.Endpoints = endpoints
|
||||
edgeGroup.EndpointIDs = roar.FromSlice(endpoints)
|
||||
}
|
||||
|
||||
return edgeGroup, err
|
||||
|
|
137
api/http/handler/edgegroups/edgegroup_inspect_test.go
Normal file
137
api/http/handler/edgegroups/edgegroup_inspect_test.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEdgeGroupInspectHandler(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "Test Endpoint " + strconv.Itoa(i+1),
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EndpointRelation().Create(&portainer.EndpointRelation{
|
||||
EndpointID: portainer.EndpointID(i + 1),
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1, 2, 3}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/edge_groups/1",
|
||||
nil,
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
||||
|
||||
func TestDynamicEdgeGroupInspectHandler(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.Tag().Create(&portainer.Tag{
|
||||
ID: 1,
|
||||
Name: "Test Tag",
|
||||
Endpoints: map[portainer.EndpointID]bool{
|
||||
1: true,
|
||||
2: true,
|
||||
3: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "Test Endpoint " + strconv.Itoa(i+1),
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: 1,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
UserTrusted: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EndpointRelation().Create(&portainer.EndpointRelation{
|
||||
EndpointID: portainer.EndpointID(i + 1),
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
Dynamic: true,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/edge_groups/1",
|
||||
nil,
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
|
@ -7,11 +7,17 @@ import (
|
|||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
)
|
||||
|
||||
type decoratedEdgeGroup struct {
|
||||
type shadowedEdgeGroup struct {
|
||||
portainer.EdgeGroup
|
||||
EndpointIds int `json:"EndpointIds,omitempty"` // Shadow to avoid exposing in the API
|
||||
}
|
||||
|
||||
type decoratedEdgeGroup struct {
|
||||
shadowedEdgeGroup
|
||||
HasEdgeStack bool `json:"HasEdgeStack"`
|
||||
HasEdgeJob bool `json:"HasEdgeJob"`
|
||||
EndpointTypes []portainer.EndpointType
|
||||
|
@ -76,8 +82,8 @@ func getEdgeGroupList(tx dataservices.DataStoreTx) ([]decoratedEdgeGroup, error)
|
|||
}
|
||||
|
||||
edgeGroup := decoratedEdgeGroup{
|
||||
EdgeGroup: orgEdgeGroup,
|
||||
EndpointTypes: []portainer.EndpointType{},
|
||||
shadowedEdgeGroup: shadowedEdgeGroup{EdgeGroup: orgEdgeGroup},
|
||||
EndpointTypes: []portainer.EndpointType{},
|
||||
}
|
||||
if edgeGroup.Dynamic {
|
||||
endpointIDs, err := GetEndpointsByTags(tx, edgeGroup.TagIDs, edgeGroup.PartialMatch)
|
||||
|
@ -88,15 +94,16 @@ func getEdgeGroupList(tx dataservices.DataStoreTx) ([]decoratedEdgeGroup, error)
|
|||
edgeGroup.Endpoints = endpointIDs
|
||||
edgeGroup.TrustedEndpoints = endpointIDs
|
||||
} else {
|
||||
trustedEndpoints, err := getTrustedEndpoints(tx, edgeGroup.Endpoints)
|
||||
trustedEndpoints, err := getTrustedEndpoints(tx, edgeGroup.EndpointIDs)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve environments for Edge group", err)
|
||||
}
|
||||
|
||||
edgeGroup.Endpoints = edgeGroup.EndpointIDs.ToSlice()
|
||||
edgeGroup.TrustedEndpoints = trustedEndpoints
|
||||
}
|
||||
|
||||
endpointTypes, err := getEndpointTypes(tx, edgeGroup.Endpoints)
|
||||
endpointTypes, err := getEndpointTypes(tx, edgeGroup.EndpointIDs)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve environment types for Edge group", err)
|
||||
}
|
||||
|
@ -111,15 +118,26 @@ func getEdgeGroupList(tx dataservices.DataStoreTx) ([]decoratedEdgeGroup, error)
|
|||
return decoratedEdgeGroups, nil
|
||||
}
|
||||
|
||||
func getEndpointTypes(tx dataservices.DataStoreTx, endpointIds []portainer.EndpointID) ([]portainer.EndpointType, error) {
|
||||
func getEndpointTypes(tx dataservices.DataStoreTx, endpointIds roar.Roar[portainer.EndpointID]) ([]portainer.EndpointType, error) {
|
||||
var innerErr error
|
||||
|
||||
typeSet := map[portainer.EndpointType]bool{}
|
||||
for _, endpointID := range endpointIds {
|
||||
|
||||
endpointIds.Iterate(func(endpointID portainer.EndpointID) bool {
|
||||
endpoint, err := tx.Endpoint().Endpoint(endpointID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed fetching environment: %w", err)
|
||||
innerErr = fmt.Errorf("failed fetching environment: %w", err)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
typeSet[endpoint.Type] = true
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
return nil, innerErr
|
||||
}
|
||||
|
||||
endpointTypes := make([]portainer.EndpointType, 0, len(typeSet))
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_getEndpointTypes(t *testing.T) {
|
||||
|
@ -38,7 +46,7 @@ func Test_getEndpointTypes(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ans, err := getEndpointTypes(datastore, test.endpointIds)
|
||||
ans, err := getEndpointTypes(datastore, roar.FromSlice(test.endpointIds))
|
||||
assert.NoError(t, err, "getEndpointTypes shouldn't fail")
|
||||
|
||||
assert.ElementsMatch(t, test.expected, ans, "getEndpointTypes expected to return %b for %v, but returned %b", test.expected, test.endpointIds, ans)
|
||||
|
@ -48,6 +56,61 @@ func Test_getEndpointTypes(t *testing.T) {
|
|||
func Test_getEndpointTypes_failWhenEndpointDontExist(t *testing.T) {
|
||||
datastore := testhelpers.NewDatastore(testhelpers.WithEndpoints([]portainer.Endpoint{}))
|
||||
|
||||
_, err := getEndpointTypes(datastore, []portainer.EndpointID{1})
|
||||
_, err := getEndpointTypes(datastore, roar.FromSlice([]portainer.EndpointID{1}))
|
||||
assert.Error(t, err, "getEndpointTypes should fail")
|
||||
}
|
||||
|
||||
func TestEdgeGroupListHandler(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "Test Endpoint " + strconv.Itoa(i+1),
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EndpointRelation().Create(&portainer.EndpointRelation{
|
||||
EndpointID: portainer.EndpointID(i + 1),
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1, 2, 3}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodGet,
|
||||
"/edge_groups",
|
||||
nil,
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroups []decoratedEdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroups)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, responseGroups, 1)
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroups[0].Endpoints)
|
||||
require.Len(t, responseGroups[0].TrustedEndpoints, 0)
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ func (handler *Handler) edgeGroupUpdate(w http.ResponseWriter, r *http.Request)
|
|||
return nil
|
||||
})
|
||||
|
||||
return txResponse(w, edgeGroup, err)
|
||||
return txResponse(w, shadowedEdgeGroup{EdgeGroup: *edgeGroup}, err)
|
||||
}
|
||||
|
||||
func (handler *Handler) updateEndpointStacks(tx dataservices.DataStoreTx, endpoint *portainer.Endpoint, edgeGroups []portainer.EdgeGroup, edgeStacks []portainer.EdgeStack) error {
|
||||
|
|
70
api/http/handler/edgegroups/edgegroup_update_test.go
Normal file
70
api/http/handler/edgegroups/edgegroup_update_test.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package edgegroups
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEdgeGroupUpdateHandler(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer())
|
||||
handler.DataStore = store
|
||||
|
||||
err := store.EndpointGroup().Create(&portainer.EndpointGroup{
|
||||
ID: 1,
|
||||
Name: "Test Group",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: portainer.EndpointID(i + 1),
|
||||
Name: "Test Endpoint " + strconv.Itoa(i+1),
|
||||
Type: portainer.EdgeAgentOnDockerEnvironment,
|
||||
GroupID: 1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EndpointRelation().Create(&portainer.EndpointRelation{
|
||||
EndpointID: portainer.EndpointID(i + 1),
|
||||
EdgeStacks: map[portainer.EdgeStackID]bool{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Test Edge Group",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
req := httptest.NewRequest(
|
||||
http.MethodPut,
|
||||
"/edge_groups/1",
|
||||
strings.NewReader(`{"Endpoints": [1, 2, 3]}`),
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusOK, rr.Result().StatusCode)
|
||||
|
||||
var responseGroup portainer.EdgeGroup
|
||||
err = json.NewDecoder(rr.Body).Decode(&responseGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t, []portainer.EndpointID{1, 2, 3}, responseGroup.Endpoints)
|
||||
}
|
|
@ -8,9 +8,10 @@ import (
|
|||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Create
|
||||
|
@ -24,7 +25,7 @@ func TestCreateAndInspect(t *testing.T) {
|
|||
Name: "EdgeGroup 1",
|
||||
Dynamic: false,
|
||||
TagIDs: nil,
|
||||
Endpoints: []portainer.EndpointID{endpoint.ID},
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{endpoint.ID}),
|
||||
PartialMatch: false,
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/portainer/portainer/api/internal/edge/edgestacks"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/jwt"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -103,7 +104,7 @@ func createEdgeStack(t *testing.T, store dataservices.DataStore, endpointID port
|
|||
Name: "EdgeGroup 1",
|
||||
Dynamic: false,
|
||||
TagIDs: nil,
|
||||
Endpoints: []portainer.EndpointID{endpointID},
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{endpointID}),
|
||||
PartialMatch: false,
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,10 @@ import (
|
|||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Update
|
||||
|
@ -43,7 +44,7 @@ func TestUpdateAndInspect(t *testing.T) {
|
|||
Name: "EdgeGroup 2",
|
||||
Dynamic: false,
|
||||
TagIDs: nil,
|
||||
Endpoints: []portainer.EndpointID{newEndpoint.ID},
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{newEndpoint.ID}),
|
||||
PartialMatch: false,
|
||||
}
|
||||
|
||||
|
@ -112,7 +113,7 @@ func TestUpdateWithInvalidEdgeGroups(t *testing.T) {
|
|||
Name: "EdgeGroup 2",
|
||||
Dynamic: false,
|
||||
TagIDs: nil,
|
||||
Endpoints: []portainer.EndpointID{8889},
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{8889}),
|
||||
PartialMatch: false,
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/portainer/portainer/api/filesystem"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/jwt"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/segmentio/encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -366,8 +367,8 @@ func TestEdgeJobsResponse(t *testing.T) {
|
|||
unrelatedEndpoint := localCreateEndpoint(80, nil)
|
||||
|
||||
staticEdgeGroup := portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Endpoints: []portainer.EndpointID{endpointFromStaticEdgeGroup.ID},
|
||||
ID: 1,
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{endpointFromStaticEdgeGroup.ID}),
|
||||
}
|
||||
err := handler.DataStore.EdgeGroup().Create(&staticEdgeGroup)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -3,7 +3,6 @@ package endpoints
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
@ -200,9 +199,7 @@ func (handler *Handler) deleteEndpoint(tx dataservices.DataStoreTx, endpointID p
|
|||
}
|
||||
|
||||
for _, edgeGroup := range edgeGroups {
|
||||
edgeGroup.Endpoints = slices.DeleteFunc(edgeGroup.Endpoints, func(e portainer.EndpointID) bool {
|
||||
return e == endpoint.ID
|
||||
})
|
||||
edgeGroup.EndpointIDs.Remove(endpoint.ID)
|
||||
|
||||
if err := tx.EdgeGroup().Update(edgeGroup.ID, &edgeGroup); err != nil {
|
||||
log.Warn().Err(err).Msg("Unable to update edge group")
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
)
|
||||
|
||||
func TestEndpointDeleteEdgeGroupsConcurrently(t *testing.T) {
|
||||
|
@ -42,9 +43,9 @@ func TestEndpointDeleteEdgeGroupsConcurrently(t *testing.T) {
|
|||
}
|
||||
|
||||
if err := store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "edgegroup-1",
|
||||
Endpoints: endpointIDs,
|
||||
ID: 1,
|
||||
Name: "edgegroup-1",
|
||||
EndpointIDs: roar.FromSlice(endpointIDs),
|
||||
}); err != nil {
|
||||
t.Fatal("could not create edge group:", err)
|
||||
}
|
||||
|
@ -78,7 +79,7 @@ func TestEndpointDeleteEdgeGroupsConcurrently(t *testing.T) {
|
|||
t.Fatal("could not retrieve the edge group:", err)
|
||||
}
|
||||
|
||||
if len(edgeGroup.Endpoints) > 0 {
|
||||
if edgeGroup.EndpointIDs.Len() > 0 {
|
||||
t.Fatal("the edge group is not consistent")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/edge"
|
||||
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||
"github.com/portainer/portainer/api/slicesx"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -146,7 +146,9 @@ func (handler *Handler) filterEndpointsByQuery(
|
|||
totalAvailableEndpoints := len(filteredEndpoints)
|
||||
|
||||
if len(query.endpointIds) > 0 {
|
||||
filteredEndpoints = filteredEndpointsByIds(filteredEndpoints, query.endpointIds)
|
||||
endpointIDs := roar.FromSlice(query.endpointIds)
|
||||
|
||||
filteredEndpoints = filteredEndpointsByIds(filteredEndpoints, endpointIDs)
|
||||
}
|
||||
|
||||
if len(query.excludeIds) > 0 {
|
||||
|
@ -275,7 +277,7 @@ func filterEndpointsByEdgeStack(endpoints []portainer.Endpoint, edgeStackId port
|
|||
return nil, errors.WithMessage(err, "Unable to retrieve edge stack from the database")
|
||||
}
|
||||
|
||||
envIds := make([]portainer.EndpointID, 0)
|
||||
envIds := roar.Roar[portainer.EndpointID]{}
|
||||
for _, edgeGroupdId := range stack.EdgeGroups {
|
||||
edgeGroup, err := datastore.EdgeGroup().Read(edgeGroupdId)
|
||||
if err != nil {
|
||||
|
@ -287,32 +289,37 @@ func filterEndpointsByEdgeStack(endpoints []portainer.Endpoint, edgeStackId port
|
|||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "Unable to retrieve environments and environment groups for Edge group")
|
||||
}
|
||||
edgeGroup.Endpoints = endpointIDs
|
||||
edgeGroup.EndpointIDs = roar.FromSlice(endpointIDs)
|
||||
}
|
||||
|
||||
envIds = append(envIds, edgeGroup.Endpoints...)
|
||||
envIds.Union(edgeGroup.EndpointIDs)
|
||||
}
|
||||
|
||||
if statusFilter != nil {
|
||||
n := 0
|
||||
for _, envId := range envIds {
|
||||
var innerErr error
|
||||
|
||||
envIds.Iterate(func(envId portainer.EndpointID) bool {
|
||||
edgeStackStatus, err := datastore.EdgeStackStatus().Read(edgeStackId, envId)
|
||||
if dataservices.IsErrObjectNotFound(err) {
|
||||
continue
|
||||
return true
|
||||
} else if err != nil {
|
||||
return nil, errors.WithMessagef(err, "Unable to retrieve edge stack status for environment %d", envId)
|
||||
innerErr = errors.WithMessagef(err, "Unable to retrieve edge stack status for environment %d", envId)
|
||||
return false
|
||||
}
|
||||
|
||||
if endpointStatusInStackMatchesFilter(edgeStackStatus, envId, *statusFilter) {
|
||||
envIds[n] = envId
|
||||
n++
|
||||
if !endpointStatusInStackMatchesFilter(edgeStackStatus, portainer.EndpointID(envId), *statusFilter) {
|
||||
envIds.Remove(envId)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
return nil, innerErr
|
||||
}
|
||||
envIds = envIds[:n]
|
||||
}
|
||||
|
||||
uniqueIds := slicesx.Unique(envIds)
|
||||
filteredEndpoints := filteredEndpointsByIds(endpoints, uniqueIds)
|
||||
filteredEndpoints := filteredEndpointsByIds(endpoints, envIds)
|
||||
|
||||
return filteredEndpoints, nil
|
||||
}
|
||||
|
@ -344,16 +351,14 @@ func filterEndpointsByEdgeGroupIDs(endpoints []portainer.Endpoint, edgeGroups []
|
|||
}
|
||||
edgeGroups = edgeGroups[:n]
|
||||
|
||||
endpointIDSet := make(map[portainer.EndpointID]struct{})
|
||||
endpointIDSet := roar.Roar[portainer.EndpointID]{}
|
||||
for _, edgeGroup := range edgeGroups {
|
||||
for _, endpointID := range edgeGroup.Endpoints {
|
||||
endpointIDSet[endpointID] = struct{}{}
|
||||
}
|
||||
endpointIDSet.Union(edgeGroup.EndpointIDs)
|
||||
}
|
||||
|
||||
n = 0
|
||||
for _, endpoint := range endpoints {
|
||||
if _, exists := endpointIDSet[endpoint.ID]; exists {
|
||||
if endpointIDSet.Contains(endpoint.ID) {
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
|
@ -369,12 +374,11 @@ func filterEndpointsByExcludeEdgeGroupIDs(endpoints []portainer.Endpoint, edgeGr
|
|||
}
|
||||
|
||||
n := 0
|
||||
excludeEndpointIDSet := make(map[portainer.EndpointID]struct{})
|
||||
excludeEndpointIDSet := roar.Roar[portainer.EndpointID]{}
|
||||
|
||||
for _, edgeGroup := range edgeGroups {
|
||||
if _, ok := excludeEdgeGroupIDSet[edgeGroup.ID]; ok {
|
||||
for _, endpointID := range edgeGroup.Endpoints {
|
||||
excludeEndpointIDSet[endpointID] = struct{}{}
|
||||
}
|
||||
excludeEndpointIDSet.Union(edgeGroup.EndpointIDs)
|
||||
} else {
|
||||
edgeGroups[n] = edgeGroup
|
||||
n++
|
||||
|
@ -384,7 +388,7 @@ func filterEndpointsByExcludeEdgeGroupIDs(endpoints []portainer.Endpoint, edgeGr
|
|||
|
||||
n = 0
|
||||
for _, endpoint := range endpoints {
|
||||
if _, ok := excludeEndpointIDSet[endpoint.ID]; !ok {
|
||||
if !excludeEndpointIDSet.Contains(endpoint.ID) {
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
|
@ -609,15 +613,10 @@ func endpointFullMatchTags(endpoint portainer.Endpoint, endpointGroup portainer.
|
|||
return len(missingTags) == 0
|
||||
}
|
||||
|
||||
func filteredEndpointsByIds(endpoints []portainer.Endpoint, ids []portainer.EndpointID) []portainer.Endpoint {
|
||||
idsSet := make(map[portainer.EndpointID]bool, len(ids))
|
||||
for _, id := range ids {
|
||||
idsSet[id] = true
|
||||
}
|
||||
|
||||
func filteredEndpointsByIds(endpoints []portainer.Endpoint, ids roar.Roar[portainer.EndpointID]) []portainer.Endpoint {
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
if idsSet[endpoint.ID] {
|
||||
if ids.Contains(endpoint.ID) {
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
|
|
|
@ -8,9 +8,11 @@ import (
|
|||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
"github.com/portainer/portainer/api/slicesx"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type filterTest struct {
|
||||
|
@ -175,7 +177,7 @@ func BenchmarkFilterEndpointsBySearchCriteria_PartialMatch(b *testing.B) {
|
|||
edgeGroups = append(edgeGroups, portainer.EdgeGroup{
|
||||
ID: portainer.EdgeGroupID(i + 1),
|
||||
Name: "edge-group-" + strconv.Itoa(i+1),
|
||||
Endpoints: append([]portainer.EndpointID{}, endpointIDs...),
|
||||
EndpointIDs: roar.FromSlice(endpointIDs),
|
||||
Dynamic: true,
|
||||
TagIDs: []portainer.TagID{1, 2, 3},
|
||||
PartialMatch: true,
|
||||
|
@ -222,11 +224,11 @@ func BenchmarkFilterEndpointsBySearchCriteria_FullMatch(b *testing.B) {
|
|||
edgeGroups := []portainer.EdgeGroup{}
|
||||
for i := range 1000 {
|
||||
edgeGroups = append(edgeGroups, portainer.EdgeGroup{
|
||||
ID: portainer.EdgeGroupID(i + 1),
|
||||
Name: "edge-group-" + strconv.Itoa(i+1),
|
||||
Endpoints: append([]portainer.EndpointID{}, endpointIDs...),
|
||||
Dynamic: true,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
ID: portainer.EdgeGroupID(i + 1),
|
||||
Name: "edge-group-" + strconv.Itoa(i+1),
|
||||
EndpointIDs: roar.FromSlice(endpointIDs),
|
||||
Dynamic: true,
|
||||
TagIDs: []portainer.TagID{1},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -300,3 +302,127 @@ func setupFilterTest(t *testing.T, endpoints []portainer.Endpoint) *Handler {
|
|||
|
||||
return handler
|
||||
}
|
||||
|
||||
func TestFilterEndpointsByEdgeStack(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, false, false)
|
||||
|
||||
endpoints := []portainer.Endpoint{
|
||||
{ID: 1, Name: "Endpoint 1"},
|
||||
{ID: 2, Name: "Endpoint 2"},
|
||||
{ID: 3, Name: "Endpoint 3"},
|
||||
{ID: 4, Name: "Endpoint 4"},
|
||||
}
|
||||
|
||||
edgeStackId := portainer.EdgeStackID(1)
|
||||
|
||||
err := store.EdgeStack().Create(edgeStackId, &portainer.EdgeStack{
|
||||
ID: edgeStackId,
|
||||
Name: "Test Edge Stack",
|
||||
EdgeGroups: []portainer.EdgeGroupID{1, 2},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Edge Group 1",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 2,
|
||||
Name: "Edge Group 2",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{2, 3}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
es, err := filterEndpointsByEdgeStack(endpoints, edgeStackId, nil, store)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, es, 3)
|
||||
require.Contains(t, es, endpoints[0]) // Endpoint 1
|
||||
require.Contains(t, es, endpoints[1]) // Endpoint 2
|
||||
require.Contains(t, es, endpoints[2]) // Endpoint 3
|
||||
require.NotContains(t, es, endpoints[3]) // Endpoint 4
|
||||
}
|
||||
|
||||
func TestFilterEndpointsByEdgeGroup(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, false, false)
|
||||
|
||||
endpoints := []portainer.Endpoint{
|
||||
{ID: 1, Name: "Endpoint 1"},
|
||||
{ID: 2, Name: "Endpoint 2"},
|
||||
{ID: 3, Name: "Endpoint 3"},
|
||||
{ID: 4, Name: "Endpoint 4"},
|
||||
}
|
||||
|
||||
err := store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Edge Group 1",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 2,
|
||||
Name: "Edge Group 2",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{2, 3}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
edgeGroups, err := store.EdgeGroup().ReadAll()
|
||||
require.NoError(t, err)
|
||||
|
||||
es, egs := filterEndpointsByEdgeGroupIDs(endpoints, edgeGroups, []portainer.EdgeGroupID{1, 2})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, es, 3)
|
||||
require.Contains(t, es, endpoints[0]) // Endpoint 1
|
||||
require.Contains(t, es, endpoints[1]) // Endpoint 2
|
||||
require.Contains(t, es, endpoints[2]) // Endpoint 3
|
||||
require.NotContains(t, es, endpoints[3]) // Endpoint 4
|
||||
|
||||
require.Len(t, egs, 2)
|
||||
require.Equal(t, egs[0].ID, portainer.EdgeGroupID(1))
|
||||
require.Equal(t, egs[1].ID, portainer.EdgeGroupID(2))
|
||||
}
|
||||
|
||||
func TestFilterEndpointsByExcludeEdgeGroupIDs(t *testing.T) {
|
||||
_, store := datastore.MustNewTestStore(t, false, false)
|
||||
|
||||
endpoints := []portainer.Endpoint{
|
||||
{ID: 1, Name: "Endpoint 1"},
|
||||
{ID: 2, Name: "Endpoint 2"},
|
||||
{ID: 3, Name: "Endpoint 3"},
|
||||
{ID: 4, Name: "Endpoint 4"},
|
||||
}
|
||||
|
||||
err := store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 1,
|
||||
Name: "Edge Group 1",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{1}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = store.EdgeGroup().Create(&portainer.EdgeGroup{
|
||||
ID: 2,
|
||||
Name: "Edge Group 2",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{2, 3}),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
edgeGroups, err := store.EdgeGroup().ReadAll()
|
||||
require.NoError(t, err)
|
||||
|
||||
es, egs := filterEndpointsByExcludeEdgeGroupIDs(endpoints, edgeGroups, []portainer.EdgeGroupID{1})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, es, 3)
|
||||
require.Equal(t, es, []portainer.Endpoint{
|
||||
{ID: 2, Name: "Endpoint 2"},
|
||||
{ID: 3, Name: "Endpoint 3"},
|
||||
{ID: 4, Name: "Endpoint 4"},
|
||||
})
|
||||
|
||||
require.Len(t, egs, 1)
|
||||
require.Equal(t, egs[0].ID, portainer.EdgeGroupID(2))
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/set"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func updateEnvironmentEdgeGroups(tx dataservices.DataStoreTx, newEdgeGroups []portainer.EdgeGroupID, environmentID portainer.EndpointID) (bool, error) {
|
||||
|
@ -19,10 +18,8 @@ func updateEnvironmentEdgeGroups(tx dataservices.DataStoreTx, newEdgeGroups []po
|
|||
|
||||
environmentEdgeGroupsSet := set.Set[portainer.EdgeGroupID]{}
|
||||
for _, edgeGroup := range edgeGroups {
|
||||
for _, eID := range edgeGroup.Endpoints {
|
||||
if eID == environmentID {
|
||||
environmentEdgeGroupsSet[edgeGroup.ID] = true
|
||||
}
|
||||
if edgeGroup.EndpointIDs.Contains(environmentID) {
|
||||
environmentEdgeGroupsSet[edgeGroup.ID] = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,20 +49,16 @@ func updateEnvironmentEdgeGroups(tx dataservices.DataStoreTx, newEdgeGroups []po
|
|||
}
|
||||
|
||||
removeEdgeGroups := environmentEdgeGroupsSet.Difference(newEdgeGroupsSet)
|
||||
err = updateSet(removeEdgeGroups, func(edgeGroup *portainer.EdgeGroup) {
|
||||
edgeGroup.Endpoints = slices.DeleteFunc(edgeGroup.Endpoints, func(eID portainer.EndpointID) bool {
|
||||
return eID == environmentID
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
if err := updateSet(removeEdgeGroups, func(edgeGroup *portainer.EdgeGroup) {
|
||||
edgeGroup.EndpointIDs.Remove(environmentID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
addToEdgeGroups := newEdgeGroupsSet.Difference(environmentEdgeGroupsSet)
|
||||
err = updateSet(addToEdgeGroups, func(edgeGroup *portainer.EdgeGroup) {
|
||||
edgeGroup.Endpoints = append(edgeGroup.Endpoints, environmentID)
|
||||
})
|
||||
if err != nil {
|
||||
if err := updateSet(addToEdgeGroups, func(edgeGroup *portainer.EdgeGroup) {
|
||||
edgeGroup.EndpointIDs.Add(environmentID)
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -14,10 +15,9 @@ func Test_updateEdgeGroups(t *testing.T) {
|
|||
groups := make([]portainer.EdgeGroup, len(names))
|
||||
for index, name := range names {
|
||||
group := &portainer.EdgeGroup{
|
||||
Name: name,
|
||||
Dynamic: false,
|
||||
TagIDs: make([]portainer.TagID, 0),
|
||||
Endpoints: make([]portainer.EndpointID, 0),
|
||||
Name: name,
|
||||
Dynamic: false,
|
||||
TagIDs: make([]portainer.TagID, 0),
|
||||
}
|
||||
|
||||
if err := store.EdgeGroup().Create(group); err != nil {
|
||||
|
@ -35,13 +35,8 @@ func Test_updateEdgeGroups(t *testing.T) {
|
|||
group, err := store.EdgeGroup().Read(groupID)
|
||||
is.NoError(err)
|
||||
|
||||
for _, endpoint := range group.Endpoints {
|
||||
if endpoint == endpointID {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
is.Fail("expected endpoint to be in group")
|
||||
is.True(group.EndpointIDs.Contains(endpointID),
|
||||
"expected endpoint to be in group")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +76,7 @@ func Test_updateEdgeGroups(t *testing.T) {
|
|||
|
||||
endpointGroups := groupsByName(groups, testCase.endpointGroupNames)
|
||||
for _, group := range endpointGroups {
|
||||
group.Endpoints = append(group.Endpoints, testCase.endpoint.ID)
|
||||
group.EndpointIDs.Add(testCase.endpoint.ID)
|
||||
|
||||
err = store.EdgeGroup().Update(group.ID, &group)
|
||||
is.NoError(err)
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
)
|
||||
|
||||
func Test_updateTags(t *testing.T) {
|
||||
|
||||
createTags := func(store *datastore.Store, tagNames []string) ([]portainer.Tag, error) {
|
||||
tags := make([]portainer.Tag, len(tagNames))
|
||||
for index, tagName := range tagNames {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package tags
|
||||
|
||||
import (
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
|
@ -9,9 +8,11 @@ import (
|
|||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
portainerDsErrors "github.com/portainer/portainer/api/dataservices/errors"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/roar"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -127,9 +128,9 @@ func TestHandler_tagDelete(t *testing.T) {
|
|||
require.NoError(t, store.EdgeGroup().Create(dynamicEdgeGroup))
|
||||
|
||||
staticEdgeGroup := &portainer.EdgeGroup{
|
||||
ID: 2,
|
||||
Name: "edgegroup-2",
|
||||
Endpoints: []portainer.EndpointID{endpoint2.ID},
|
||||
ID: 2,
|
||||
Name: "edgegroup-2",
|
||||
EndpointIDs: roar.FromSlice([]portainer.EndpointID{endpoint2.ID}),
|
||||
}
|
||||
require.NoError(t, store.EdgeGroup().Create(staticEdgeGroup))
|
||||
|
||||
|
@ -163,14 +164,14 @@ func TestHandler_tagDelete(t *testing.T) {
|
|||
dynamicEdgeGroup, err = store.EdgeGroup().Read(dynamicEdgeGroup.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, dynamicEdgeGroup.TagIDs, 0, "dynamic edge group should not have any tags")
|
||||
assert.Len(t, dynamicEdgeGroup.Endpoints, 0, "dynamic edge group should not have any endpoints")
|
||||
assert.Equal(t, 0, dynamicEdgeGroup.EndpointIDs.Len(), "dynamic edge group should not have any endpoints")
|
||||
|
||||
// Check that the static edge group is not updated
|
||||
staticEdgeGroup, err = store.EdgeGroup().Read(staticEdgeGroup.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, staticEdgeGroup.TagIDs, 0, "static edge group should not have any tags")
|
||||
assert.Len(t, staticEdgeGroup.Endpoints, 1, "static edge group should have one endpoint")
|
||||
assert.Equal(t, endpoint2.ID, staticEdgeGroup.Endpoints[0], "static edge group should have the endpoint-2")
|
||||
assert.Equal(t, 1, staticEdgeGroup.EndpointIDs.Len(), "static edge group should have one endpoint")
|
||||
assert.True(t, staticEdgeGroup.EndpointIDs.Contains(endpoint2.ID), "static edge group should have the endpoint-2")
|
||||
})
|
||||
|
||||
// Test the tx.IsErrObjectNotFound logic when endpoint is not found during cleanup
|
||||
|
@ -185,14 +186,10 @@ func TestHandler_tagDelete(t *testing.T) {
|
|||
}
|
||||
|
||||
err := store.Tag().Create(tag)
|
||||
if err != nil {
|
||||
t.Fatal("could not create tag:", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
err = deleteTag(store, 1)
|
||||
if err != nil {
|
||||
t.Fatal("could not delete tag:", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue