fix(edge): stack associated no dynamic group being deployed [EE-5531] (#10224)

pull/10081/head
Oscar Zhou 2023-09-04 17:04:45 +12:00 committed by GitHub
parent 490e4ec655
commit 440f4e8dda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 360 additions and 7 deletions

View File

@ -125,8 +125,8 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
if payload.GroupID != nil {
groupID := portainer.EndpointGroupID(*payload.GroupID)
endpoint.GroupID = groupID
updateRelations = updateRelations || groupID != endpoint.GroupID
endpoint.GroupID = groupID
}
if payload.TagIDs != nil {

View File

@ -69,7 +69,7 @@ func GetEndpointsFromEdgeGroups(edgeGroupIDs []portainer.EdgeGroupID, datastore
return response, nil
}
// edgeGroupRelatedToEndpoint returns true is edgeGroup is associated with environment(endpoint)
// edgeGroupRelatedToEndpoint returns true if edgeGroup is associated with environment(endpoint)
func edgeGroupRelatedToEndpoint(edgeGroup *portainer.EdgeGroup, endpoint *portainer.Endpoint, endpointGroup *portainer.EndpointGroup) bool {
if !edgeGroup.Dynamic {
for _, endpointID := range edgeGroup.Endpoints {
@ -91,5 +91,5 @@ func edgeGroupRelatedToEndpoint(edgeGroup *portainer.EdgeGroup, endpoint *portai
return len(intersection) != 0
}
return tag.Contains(edgeGroupTags, endpointTags)
return tag.FullMatch(edgeGroupTags, endpointTags)
}

View File

@ -50,13 +50,16 @@ func Union(sets ...tagSet) tagSet {
// Contains return true if setA contains setB
func Contains(setA tagSet, setB tagSet) bool {
containedTags := 0
if len(setA) == 0 || len(setB) == 0 {
return false
}
for tag := range setB {
if setA[tag] {
containedTags++
if !setA[tag] {
return false
}
}
return containedTags == len(setA)
return true
}
// Difference returns the set difference tagsA - tagsB

View File

@ -0,0 +1,11 @@
package tag
// FullMatch returns true if environment tags matches all edge group tags
func FullMatch(edgeGroupTags tagSet, environmentTags tagSet) bool {
return Contains(environmentTags, edgeGroupTags)
}
// PartialMatch returns true if environment tags matches at least one edge group tag
func PartialMatch(edgeGroupTags tagSet, environmentTags tagSet) bool {
return len(Intersection(edgeGroupTags, environmentTags)) != 0
}

View File

@ -0,0 +1,135 @@
package tag
import (
"testing"
portainer "github.com/portainer/portainer/api"
)
func TestFullMatch(t *testing.T) {
cases := []struct {
name string
edgeGroupTags tagSet
environmentTag tagSet
expected bool
}{
{
name: "environment tag partially match edge group tags",
edgeGroupTags: Set([]portainer.TagID{1, 2, 3}),
environmentTag: Set([]portainer.TagID{1, 2}),
expected: false,
},
{
name: "edge group tags equal to environment tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{1, 2}),
expected: true,
},
{
name: "environment tags fully match edge group tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{1, 2, 3}),
expected: true,
},
{
name: "environment tags do not match edge group tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{3, 4}),
expected: false,
},
{
name: "edge group has no tags and environment has tags",
edgeGroupTags: Set([]portainer.TagID{}),
environmentTag: Set([]portainer.TagID{1, 2}),
expected: false,
},
{
name: "edge group has tags and environment has no tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{}),
expected: false,
},
{
name: "both edge group and environment have no tags",
edgeGroupTags: Set([]portainer.TagID{}),
environmentTag: Set([]portainer.TagID{}),
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := FullMatch(tc.edgeGroupTags, tc.environmentTag)
if result != tc.expected {
t.Errorf("Expected %v, got %v", tc.expected, result)
}
})
}
}
func TestPartialMatch(t *testing.T) {
cases := []struct {
name string
edgeGroupTags tagSet
environmentTag tagSet
expected bool
}{
{
name: "environment tags partially match edge group tags 1",
edgeGroupTags: Set([]portainer.TagID{1, 2, 3}),
environmentTag: Set([]portainer.TagID{1, 2}),
expected: true,
},
{
name: "environment tags partially match edge group tags 2",
edgeGroupTags: Set([]portainer.TagID{1, 2, 3}),
environmentTag: Set([]portainer.TagID{1, 4, 5}),
expected: true,
},
{
name: "edge group tags equal to environment tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{1, 2}),
expected: true,
},
{
name: "environment tags fully match edge group tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{1, 2, 3}),
expected: true,
},
{
name: "environment tags do not match edge group tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{3, 4}),
expected: false,
},
{
name: "edge group has no tags and environment has tags",
edgeGroupTags: Set([]portainer.TagID{}),
environmentTag: Set([]portainer.TagID{1, 2}),
expected: false,
},
{
name: "edge group has tags and environment has no tags",
edgeGroupTags: Set([]portainer.TagID{1, 2}),
environmentTag: Set([]portainer.TagID{}),
expected: false,
},
{
name: "both edge group and environment have no tags",
edgeGroupTags: Set([]portainer.TagID{}),
environmentTag: Set([]portainer.TagID{}),
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := PartialMatch(tc.edgeGroupTags, tc.environmentTag)
if result != tc.expected {
t.Errorf("Expected %v, got %v", tc.expected, result)
}
})
}
}

View File

@ -0,0 +1,204 @@
package tag
import (
"reflect"
"testing"
portainer "github.com/portainer/portainer/api"
)
func TestIntersection(t *testing.T) {
cases := []struct {
name string
setA tagSet
setB tagSet
expected tagSet
}{
{
name: "positive numbers set intersection",
setA: Set([]portainer.TagID{1, 2, 3, 4, 5}),
setB: Set([]portainer.TagID{4, 5, 6, 7}),
expected: Set([]portainer.TagID{4, 5}),
},
{
name: "empty setA intersection",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{}),
expected: Set([]portainer.TagID{}),
},
{
name: "empty setB intersection",
setA: Set([]portainer.TagID{}),
setB: Set([]portainer.TagID{1, 2, 3}),
expected: Set([]portainer.TagID{}),
},
{
name: "no common elements sets intersection",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{4, 5, 6}),
expected: Set([]portainer.TagID{}),
},
{
name: "equal sets intersection",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{1, 2, 3}),
expected: Set([]portainer.TagID{1, 2, 3}),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := Intersection(tc.setA, tc.setB)
if !reflect.DeepEqual(result, tc.expected) {
t.Errorf("Expected %v, got %v", tc.expected, result)
}
})
}
}
func TestUnion(t *testing.T) {
cases := []struct {
name string
setA tagSet
setB tagSet
expected tagSet
}{
{
name: "non-duplicate set union",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{4, 5, 6}),
expected: Set([]portainer.TagID{1, 2, 3, 4, 5, 6}),
},
{
name: "empty setA union",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{}),
expected: Set([]portainer.TagID{1, 2, 3}),
},
{
name: "empty setB union",
setA: Set([]portainer.TagID{}),
setB: Set([]portainer.TagID{1, 2, 3}),
expected: Set([]portainer.TagID{1, 2, 3}),
},
{
name: "duplicate elements in set union",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{3, 4, 5}),
expected: Set([]portainer.TagID{1, 2, 3, 4, 5}),
},
{
name: "equal sets union",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{1, 2, 3}),
expected: Set([]portainer.TagID{1, 2, 3}),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := Union(tc.setA, tc.setB)
if !reflect.DeepEqual(result, tc.expected) {
t.Errorf("Expected %v, got %v", tc.expected, result)
}
})
}
}
func TestContains(t *testing.T) {
cases := []struct {
name string
setA tagSet
setB tagSet
expected bool
}{
{
name: "setA contains setB",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{1, 2}),
expected: true,
},
{
name: "setA equals to setB",
setA: Set([]portainer.TagID{1, 2}),
setB: Set([]portainer.TagID{1, 2}),
expected: true,
},
{
name: "setA contains parts of setB",
setA: Set([]portainer.TagID{1, 2}),
setB: Set([]portainer.TagID{1, 2, 3}),
expected: false,
},
{
name: "setA does not contain setB",
setA: Set([]portainer.TagID{1, 2}),
setB: Set([]portainer.TagID{3, 4}),
expected: false,
},
{
name: "setA is empty and setB is not empty",
setA: Set([]portainer.TagID{}),
setB: Set([]portainer.TagID{1, 2}),
expected: false,
},
{
name: "setA is not empty and setB is empty",
setA: Set([]portainer.TagID{1, 2}),
setB: Set([]portainer.TagID{}),
expected: false,
},
{
name: "setA is empty and setB is empty",
setA: Set([]portainer.TagID{}),
setB: Set([]portainer.TagID{}),
expected: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := Contains(tc.setA, tc.setB)
if result != tc.expected {
t.Errorf("Expected %v, got %v", tc.expected, result)
}
})
}
}
func TestDifference(t *testing.T) {
cases := []struct {
name string
setA tagSet
setB tagSet
expected tagSet
}{
{
name: "positive numbers set difference",
setA: Set([]portainer.TagID{1, 2, 3, 4, 5}),
setB: Set([]portainer.TagID{4, 5, 6, 7}),
expected: Set([]portainer.TagID{1, 2, 3}),
},
{
name: "empty set difference",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{}),
expected: Set([]portainer.TagID{1, 2, 3}),
},
{
name: "equal sets difference",
setA: Set([]portainer.TagID{1, 2, 3}),
setB: Set([]portainer.TagID{1, 2, 3}),
expected: Set([]portainer.TagID{}),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := Difference(tc.setA, tc.setB)
if !reflect.DeepEqual(result, tc.expected) {
t.Errorf("Expected %v, got %v", tc.expected, result)
}
})
}
}