diff --git a/management/internals/modules/reverseproxy/proxy/proxy.go b/management/internals/modules/reverseproxy/proxy/proxy.go index 4763e67ef..35e147ba4 100644 --- a/management/internals/modules/reverseproxy/proxy/proxy.go +++ b/management/internals/modules/reverseproxy/proxy/proxy.go @@ -1,12 +1,9 @@ package proxy import ( - "errors" "time" ) -var ErrAccountProxyAlreadyExists = errors.New("account already has a registered proxy") - const ( StatusConnected = "connected" StatusDisconnected = "disconnected" @@ -28,7 +25,7 @@ type Proxy struct { ID string `gorm:"primaryKey;type:varchar(255)"` ClusterAddress string `gorm:"type:varchar(255);not null;index:idx_proxy_cluster_status"` IPAddress string `gorm:"type:varchar(45)"` - AccountID *string `gorm:"type:varchar(255);uniqueIndex:idx_proxy_account_id_unique"` + AccountID *string `gorm:"type:varchar(255);index:idx_proxy_account_id"` LastSeen time.Time `gorm:"not null;index:idx_proxy_last_seen"` ConnectedAt *time.Time DisconnectedAt *time.Time diff --git a/management/internals/shared/grpc/proxy.go b/management/internals/shared/grpc/proxy.go index 7f4575612..857112f48 100644 --- a/management/internals/shared/grpc/proxy.go +++ b/management/internals/shared/grpc/proxy.go @@ -194,23 +194,6 @@ func (s *ProxyServiceServer) GetMappingUpdate(req *proto.GetMappingUpdateRequest if token != nil && token.AccountID != nil { accountID = token.AccountID - existingProxy, err := s.proxyManager.GetAccountProxy(ctx, *accountID) - if err != nil { - if s, ok := nbstatus.FromError(err); ok && s.ErrorType == nbstatus.NotFound { - log.WithContext(ctx).Debugf("no existing BYOP proxy for account %s", *accountID) - } else { - return status.Errorf(codes.Internal, "failed to check existing proxy: %v", err) - } - } - if existingProxy != nil && existingProxy.ID != proxyID { - if existingProxy.Status == proxy.StatusConnected { - return status.Errorf(codes.ResourceExhausted, "limit of 1 self-hosted proxy per account") - } - if err := s.proxyManager.DeleteProxy(ctx, existingProxy.ID); err != nil { - log.WithContext(ctx).Warnf("failed to cleanup disconnected proxy %s: %v", existingProxy.ID, err) - } - } - available, err := s.proxyManager.IsClusterAddressAvailable(ctx, proxyAddress, *accountID) if err != nil { return status.Errorf(codes.Internal, "check cluster address: %v", err) @@ -248,9 +231,6 @@ func (s *ProxyServiceServer) GetMappingUpdate(req *proto.GetMappingUpdateRequest if err := s.proxyManager.Connect(ctx, proxyID, proxyAddress, peerInfo, accountID, caps); err != nil { if accountID != nil { cancel() - if errors.Is(err, proxy.ErrAccountProxyAlreadyExists) { - return status.Errorf(codes.ResourceExhausted, "limit of 1 self-hosted proxy per account") - } return status.Errorf(codes.Internal, "failed to register BYOP proxy: %v", err) } log.WithContext(ctx).Warnf("Failed to register proxy %s in database: %v", proxyID, err) diff --git a/management/server/store/sql_store.go b/management/server/store/sql_store.go index c2bb6520b..31dc4249e 100644 --- a/management/server/store/sql_store.go +++ b/management/server/store/sql_store.go @@ -17,7 +17,6 @@ import ( "time" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" "github.com/rs/xid" log "github.com/sirupsen/logrus" @@ -5476,26 +5475,11 @@ func (s *SqlStore) SaveProxy(ctx context.Context, p *proxy.Proxy) error { result := s.db.WithContext(ctx).Save(p) if result.Error != nil { log.WithContext(ctx).Errorf("failed to save proxy: %v", result.Error) - if isUniqueConstraintError(result.Error) { - return proxy.ErrAccountProxyAlreadyExists - } return status.Errorf(status.Internal, "failed to save proxy") } return nil } -func isUniqueConstraintError(err error) bool { - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) && pgErr.Code == "23505" { - return true - } - errStr := err.Error() - return strings.Contains(errStr, "UNIQUE constraint") || - strings.Contains(errStr, "duplicate key") || - strings.Contains(errStr, "Duplicate entry") || - strings.Contains(errStr, "Error 1062") -} - func (s *SqlStore) DisconnectProxy(ctx context.Context, proxyID string) error { now := time.Now() result := s.db.WithContext(ctx). diff --git a/management/server/store/store.go b/management/server/store/store.go index fd2e13915..0e5cd6bb3 100644 --- a/management/server/store/store.go +++ b/management/server/store/store.go @@ -502,6 +502,9 @@ func getMigrationsPostAuto(ctx context.Context) []migrationFunc { func(db *gorm.DB) error { return migration.CreateIndexIfNotExists[nbpeer.Peer](ctx, db, "idx_peers_key_unique", "key") }, + func(db *gorm.DB) error { + return migration.DropIndex[proxy.Proxy](ctx, db, "idx_proxy_account_id_unique") + }, } } diff --git a/proxy/management_byop_integration_test.go b/proxy/management_byop_integration_test.go index ea29b6960..2a5493847 100644 --- a/proxy/management_byop_integration_test.go +++ b/proxy/management_byop_integration_test.go @@ -249,7 +249,7 @@ func TestIntegration_BYOPProxy_AccountBReceivesOnlyItsServices(t *testing.T) { assert.Equal(t, setup.accountB, mappings[0].GetAccountId()) } -func TestIntegration_BYOPProxy_LimitOnePerAccount(t *testing.T) { +func TestIntegration_BYOPProxy_MultiplePerAccount(t *testing.T) { setup := setupBYOPIntegrationTest(t) defer setup.cleanup() @@ -269,7 +269,8 @@ func TestIntegration_BYOPProxy_LimitOnePerAccount(t *testing.T) { }) require.NoError(t, err) - _ = receiveBYOPMappings(t, stream1) + mappings1 := receiveBYOPMappings(t, stream1) + assert.Len(t, mappings1, 2, "first BYOP proxy should receive account A's 2 services") ctx2, cancel2 := context.WithTimeout(byopContext(context.Background(), setup.accountAToken), 5*time.Second) defer cancel2() @@ -281,13 +282,11 @@ func TestIntegration_BYOPProxy_LimitOnePerAccount(t *testing.T) { }) require.NoError(t, err) - _, err = stream2.Recv() - require.Error(t, err) - - st, ok := grpcstatus.FromError(err) - require.True(t, ok) - assert.Equal(t, codes.ResourceExhausted, st.Code(), "second BYOP proxy should be rejected with ResourceExhausted") - t.Logf("expected rejection: %s", st.Message()) + mappings2 := receiveBYOPMappings(t, stream2) + assert.Len(t, mappings2, 2, "second BYOP proxy from same account should also receive the 2 services") + for _, m := range mappings2 { + assert.Equal(t, setup.accountA, m.GetAccountId()) + } } func TestIntegration_BYOPProxy_ClusterAddressConflict(t *testing.T) {