fix(api/edge-stacks): avoid overriding updates with old values (#1047)

pull/11646/merge
LP B 2025-08-16 03:52:13 +02:00 committed by GitHub
parent 2035c42c3c
commit c9d18b614b
7 changed files with 54 additions and 14 deletions

View File

@ -91,9 +91,9 @@ func (service *Service) UpdateEndpointRelation(endpointID portainer.EndpointID,
}) })
} }
func (service *Service) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error { func (service *Service) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStack *portainer.EdgeStack) error {
return service.connection.UpdateTx(func(tx portainer.Transaction) error { return service.connection.UpdateTx(func(tx portainer.Transaction) error {
return service.Tx(tx).AddEndpointRelationsForEdgeStack(endpointIDs, edgeStackID) return service.Tx(tx).AddEndpointRelationsForEdgeStack(endpointIDs, edgeStack)
}) })
} }

View File

@ -5,6 +5,7 @@ import (
portainer "github.com/portainer/portainer/api" portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/database/boltdb" "github.com/portainer/portainer/api/database/boltdb"
"github.com/portainer/portainer/api/dataservices/edgestack"
"github.com/portainer/portainer/api/internal/edge/cache" "github.com/portainer/portainer/api/internal/edge/cache"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -102,3 +103,38 @@ func TestUpdateRelation(t *testing.T) {
require.Equal(t, 0, edgeStacks[edgeStackID1].NumDeployments) require.Equal(t, 0, edgeStacks[edgeStackID1].NumDeployments)
require.Equal(t, 0, edgeStacks[edgeStackID2].NumDeployments) require.Equal(t, 0, edgeStacks[edgeStackID2].NumDeployments)
} }
func TestAddEndpointRelationsForEdgeStack(t *testing.T) {
var conn portainer.Connection = &boltdb.DbConnection{Path: t.TempDir()}
err := conn.Open()
require.NoError(t, err)
defer conn.Close()
service, err := NewService(conn)
require.NoError(t, err)
edgeStackService, err := edgestack.NewService(conn, func(t portainer.Transaction, esi portainer.EdgeStackID) {})
require.NoError(t, err)
service.RegisterUpdateStackFunction(edgeStackService.UpdateEdgeStackFuncTx)
require.NoError(t, edgeStackService.Create(1, &portainer.EdgeStack{}))
require.NoError(t, service.Create(&portainer.EndpointRelation{EndpointID: 1, EdgeStacks: map[portainer.EdgeStackID]bool{}}))
require.NoError(t, service.AddEndpointRelationsForEdgeStack([]portainer.EndpointID{1}, &portainer.EdgeStack{ID: 1}))
}
func TestEndpointRelations(t *testing.T) {
var conn portainer.Connection = &boltdb.DbConnection{Path: t.TempDir()}
err := conn.Open()
require.NoError(t, err)
defer conn.Close()
service, err := NewService(conn)
require.NoError(t, err)
require.NoError(t, service.Create(&portainer.EndpointRelation{EndpointID: 1}))
rels, err := service.EndpointRelations()
require.NoError(t, err)
require.Equal(t, 1, len(rels))
}

View File

@ -76,14 +76,14 @@ func (service ServiceTx) UpdateEndpointRelation(endpointID portainer.EndpointID,
return nil return nil
} }
func (service ServiceTx) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error { func (service ServiceTx) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStack *portainer.EdgeStack) error {
for _, endpointID := range endpointIDs { for _, endpointID := range endpointIDs {
rel, err := service.EndpointRelation(endpointID) rel, err := service.EndpointRelation(endpointID)
if err != nil { if err != nil {
return err return err
} }
rel.EdgeStacks[edgeStackID] = true rel.EdgeStacks[edgeStack.ID] = true
identifier := service.service.connection.ConvertToKey(int(endpointID)) identifier := service.service.connection.ConvertToKey(int(endpointID))
err = service.tx.UpdateObject(BucketName, identifier, rel) err = service.tx.UpdateObject(BucketName, identifier, rel)
@ -97,8 +97,12 @@ func (service ServiceTx) AddEndpointRelationsForEdgeStack(endpointIDs []portaine
service.service.endpointRelationsCache = nil service.service.endpointRelationsCache = nil
service.service.mu.Unlock() service.service.mu.Unlock()
if err := service.service.updateStackFnTx(service.tx, edgeStackID, func(edgeStack *portainer.EdgeStack) { if err := service.service.updateStackFnTx(service.tx, edgeStack.ID, func(es *portainer.EdgeStack) {
edgeStack.NumDeployments += len(endpointIDs) es.NumDeployments += len(endpointIDs)
// sync changes in `edgeStack` in case it is re-persisted after `AddEndpointRelationsForEdgeStack` call
// to avoid overriding with the previous values
edgeStack.NumDeployments = es.NumDeployments
}); err != nil { }); err != nil {
log.Error().Err(err).Msg("could not update the number of deployments") log.Error().Err(err).Msg("could not update the number of deployments")
} }

View File

@ -126,7 +126,7 @@ type (
EndpointRelation(EndpointID portainer.EndpointID) (*portainer.EndpointRelation, error) EndpointRelation(EndpointID portainer.EndpointID) (*portainer.EndpointRelation, error)
Create(endpointRelation *portainer.EndpointRelation) error Create(endpointRelation *portainer.EndpointRelation) error
UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error UpdateEndpointRelation(EndpointID portainer.EndpointID, endpointRelation *portainer.EndpointRelation) error
AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStack *portainer.EdgeStack) error
RemoveEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error RemoveEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error
DeleteEndpointRelation(EndpointID portainer.EndpointID) error DeleteEndpointRelation(EndpointID portainer.EndpointID) error
BucketName() string BucketName() string

View File

@ -99,7 +99,7 @@ func (handler *Handler) updateEdgeStack(tx dataservices.DataStoreTx, stackID por
groupsIds := stack.EdgeGroups groupsIds := stack.EdgeGroups
if payload.EdgeGroups != nil { if payload.EdgeGroups != nil {
newRelated, _, err := handler.handleChangeEdgeGroups(tx, stack.ID, payload.EdgeGroups, relatedEndpointIds, relationConfig) newRelated, _, err := handler.handleChangeEdgeGroups(tx, stack, payload.EdgeGroups, relatedEndpointIds, relationConfig)
if err != nil { if err != nil {
return nil, httperror.InternalServerError("Unable to handle edge groups change", err) return nil, httperror.InternalServerError("Unable to handle edge groups change", err)
} }
@ -136,7 +136,7 @@ func (handler *Handler) updateEdgeStack(tx dataservices.DataStoreTx, stackID por
return stack, nil return stack, nil
} }
func (handler *Handler) handleChangeEdgeGroups(tx dataservices.DataStoreTx, edgeStackID portainer.EdgeStackID, newEdgeGroupsIDs []portainer.EdgeGroupID, oldRelatedEnvironmentIDs []portainer.EndpointID, relationConfig *edge.EndpointRelationsConfig) ([]portainer.EndpointID, set.Set[portainer.EndpointID], error) { func (handler *Handler) handleChangeEdgeGroups(tx dataservices.DataStoreTx, edgeStack *portainer.EdgeStack, newEdgeGroupsIDs []portainer.EdgeGroupID, oldRelatedEnvironmentIDs []portainer.EndpointID, relationConfig *edge.EndpointRelationsConfig) ([]portainer.EndpointID, set.Set[portainer.EndpointID], error) {
newRelatedEnvironmentIDs, err := edge.EdgeStackRelatedEndpoints(newEdgeGroupsIDs, relationConfig.Endpoints, relationConfig.EndpointGroups, relationConfig.EdgeGroups) newRelatedEnvironmentIDs, err := edge.EdgeStackRelatedEndpoints(newEdgeGroupsIDs, relationConfig.Endpoints, relationConfig.EndpointGroups, relationConfig.EdgeGroups)
if err != nil { if err != nil {
return nil, nil, errors.WithMessage(err, "Unable to retrieve edge stack related environments from database") return nil, nil, errors.WithMessage(err, "Unable to retrieve edge stack related environments from database")
@ -149,13 +149,13 @@ func (handler *Handler) handleChangeEdgeGroups(tx dataservices.DataStoreTx, edge
relatedEnvironmentsToRemove := oldRelatedEnvironmentsSet.Difference(newRelatedEnvironmentsSet) relatedEnvironmentsToRemove := oldRelatedEnvironmentsSet.Difference(newRelatedEnvironmentsSet)
if len(relatedEnvironmentsToRemove) > 0 { if len(relatedEnvironmentsToRemove) > 0 {
if err := tx.EndpointRelation().RemoveEndpointRelationsForEdgeStack(relatedEnvironmentsToRemove.Keys(), edgeStackID); err != nil { if err := tx.EndpointRelation().RemoveEndpointRelationsForEdgeStack(relatedEnvironmentsToRemove.Keys(), edgeStack.ID); err != nil {
return nil, nil, errors.WithMessage(err, "Unable to remove edge stack relations from the database") return nil, nil, errors.WithMessage(err, "Unable to remove edge stack relations from the database")
} }
} }
if len(relatedEnvironmentsToAdd) > 0 { if len(relatedEnvironmentsToAdd) > 0 {
if err := tx.EndpointRelation().AddEndpointRelationsForEdgeStack(relatedEnvironmentsToAdd.Keys(), edgeStackID); err != nil { if err := tx.EndpointRelation().AddEndpointRelationsForEdgeStack(relatedEnvironmentsToAdd.Keys(), edgeStack); err != nil {
return nil, nil, errors.WithMessage(err, "Unable to add edge stack relations to the database") return nil, nil, errors.WithMessage(err, "Unable to add edge stack relations to the database")
} }
} }

View File

@ -111,7 +111,7 @@ func (service *Service) PersistEdgeStack(
} }
} }
if err := tx.EndpointRelation().AddEndpointRelationsForEdgeStack(relatedEndpointIds, stack.ID); err != nil { if err := tx.EndpointRelation().AddEndpointRelationsForEdgeStack(relatedEndpointIds, stack); err != nil {
return nil, fmt.Errorf("unable to add endpoint relations: %w", err) return nil, fmt.Errorf("unable to add endpoint relations: %w", err)
} }

View File

@ -230,11 +230,11 @@ func (s *stubEndpointRelationService) UpdateEndpointRelation(ID portainer.Endpoi
return nil return nil
} }
func (s *stubEndpointRelationService) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStackID portainer.EdgeStackID) error { func (s *stubEndpointRelationService) AddEndpointRelationsForEdgeStack(endpointIDs []portainer.EndpointID, edgeStack *portainer.EdgeStack) error {
for _, endpointID := range endpointIDs { for _, endpointID := range endpointIDs {
for i, r := range s.relations { for i, r := range s.relations {
if r.EndpointID == endpointID { if r.EndpointID == endpointID {
s.relations[i].EdgeStacks[edgeStackID] = true s.relations[i].EdgeStacks[edgeStack.ID] = true
} }
} }
} }