github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/tenantfetchersvc/resync/tenant_mover.go (about)

     1  package resync
     2  
     3  import (
     4  	"context"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/avast/retry-go/v4"
     9  	"github.com/kyma-incubator/compass/components/director/internal/domain/label"
    10  	tnt "github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    11  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    12  	"github.com/kyma-incubator/compass/components/director/internal/model"
    13  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    14  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  const (
    19  	// DefaultScenario is the name of the default scenario
    20  	DefaultScenario = "DEFAULT"
    21  )
    22  
    23  // RuntimeService missing godoc
    24  //
    25  //go:generate mockery --name=RuntimeService --output=automock --outpkg=automock --case=underscore --disable-version-string
    26  type RuntimeService interface {
    27  	ListByFilters(ctx context.Context, filters []*labelfilter.LabelFilter) ([]*model.Runtime, error)
    28  }
    29  
    30  // LabelRepo missing godoc
    31  //
    32  //go:generate mockery --name=LabelRepo --output=automock --outpkg=automock --case=underscore --disable-version-string
    33  type LabelRepo interface {
    34  	GetScenarioLabelsForRuntimes(ctx context.Context, tenantID string, runtimesIDs []string) ([]model.Label, error)
    35  }
    36  
    37  type tenantMover struct {
    38  	externalTenantsManager
    39  
    40  	transact              persistence.Transactioner
    41  	tenantStorageService  TenantStorageService
    42  	runtimeStorageService RuntimeService
    43  	labelRepo             LabelRepo
    44  }
    45  
    46  // NewSubaccountsMover returns a new etity responsible for retrieving moved tenants from one parent tenant to another
    47  // from an external registry, and proceeding with moving said tenants across parents if possible
    48  func NewSubaccountsMover(jobConfig JobConfig, transact persistence.Transactioner, directorClient DirectorGraphQLClient, eventAPIClient EventAPIClient, tenantConverter TenantConverter, storageSvc TenantStorageService, runtimeSvc RuntimeService, labelRepo LabelRepo) TenantMover {
    49  	return &tenantMover{
    50  		externalTenantsManager: externalTenantsManager{
    51  			gqlClient:       directorClient,
    52  			eventAPIClient:  eventAPIClient,
    53  			config:          jobConfig.EventsConfig,
    54  			tenantConverter: tenantConverter,
    55  			tenantProvider:  jobConfig.TenantProvider,
    56  		},
    57  		transact:              transact,
    58  		tenantStorageService:  storageSvc,
    59  		runtimeStorageService: runtimeSvc,
    60  		labelRepo:             labelRepo,
    61  	}
    62  }
    63  
    64  // TenantsToMove returns all tenants that should be moved from one parent tenant to another
    65  func (tmv *tenantMover) TenantsToMove(ctx context.Context, region, fromTimestamp string) ([]model.MovedSubaccountMappingInput, error) {
    66  	configProvider := eventsQueryConfigProviderWithRegion(tmv.config, fromTimestamp, region)
    67  	return fetchMovedSubaccountsWithRetries(ctx, tmv.eventAPIClient, tmv.config.RetryAttempts, configProvider)
    68  }
    69  
    70  // MoveTenants Moves all eligible tenants from one parent tenant to another.
    71  func (tmv *tenantMover) MoveTenants(ctx context.Context, movedSubaccountMappings []model.MovedSubaccountMappingInput) error {
    72  	tx, err := tmv.transact.Begin()
    73  	if err != nil {
    74  		return err
    75  	}
    76  	defer tmv.transact.RollbackUnlessCommitted(ctx, tx)
    77  	ctx = persistence.SaveToContext(ctx, tx)
    78  
    79  	tenantsToUpdate, tenantsToCreate, err := tmv.tenantsToUpsert(ctx, movedSubaccountMappings)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	log.C(ctx).Infof("Moving tenants from one parent tenant to another")
    85  	for _, t := range tenantsToUpdate {
    86  		if err := tmv.moveSubaccount(ctx, t); err != nil {
    87  			return errors.Wrapf(err, "while moving subaccount with ID %s", t.ExternalTenant)
    88  		}
    89  	}
    90  
    91  	if err := tx.Commit(); err != nil {
    92  		return errors.Wrap(err, "while committing transaction")
    93  	}
    94  
    95  	if len(tenantsToCreate) > 0 {
    96  		log.C(ctx).Infof("Creating non-existing tenants in the correct parent tenant")
    97  		tenantsToCreateGQL := tmv.tenantConverter.MultipleInputToGraphQLInput(tenantsToCreate)
    98  		if err := tmv.gqlClient.WriteTenants(ctx, tenantsToCreateGQL); err != nil {
    99  			return errors.Wrap(err, "while creating missing tenants")
   100  		}
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  func (tmv *tenantMover) moveSubaccount(ctx context.Context, subaccountTenant *model.BusinessTenantMapping) error {
   107  	subaccountTenantGQL := tmv.tenantConverter.ToGraphQLInput(subaccountTenant.ToInput())
   108  	if err := tmv.gqlClient.UpdateTenant(ctx, subaccountTenant.ID, subaccountTenantGQL); err != nil {
   109  		return errors.Wrapf(err, "while updating tenant with id %s", subaccountTenant.ID)
   110  	}
   111  	log.C(ctx).Infof("Successfully moved subaccount tenant %s to new parent with ID %s", subaccountTenant.ID, subaccountTenant.Parent)
   112  	return nil
   113  }
   114  
   115  func (tmv tenantMover) checkForScenarios(ctx context.Context, subaccountInternalID, sourceGATenant string) error {
   116  	ctxWithSubaccount := tnt.SaveToContext(ctx, subaccountInternalID, "")
   117  	runtimes, err := tmv.runtimeStorageService.ListByFilters(ctxWithSubaccount, nil)
   118  	if err != nil {
   119  		return errors.Wrapf(err, "while getting runtimes in subaccount %s", subaccountInternalID)
   120  	}
   121  
   122  	if len(runtimes) == 0 {
   123  		return nil
   124  	}
   125  
   126  	sourceGA, err := tmv.tenantStorageService.GetTenantByExternalID(ctx, sourceGATenant)
   127  	if err != nil {
   128  		return errors.Wrapf(err, "while getting GA with externalID %s", sourceGATenant)
   129  	}
   130  
   131  	runtimeIDs := make([]string, 0, len(runtimes))
   132  	for _, rt := range runtimes {
   133  		runtimeIDs = append(runtimeIDs, rt.ID)
   134  	}
   135  
   136  	scenariosLabels, err := tmv.labelRepo.GetScenarioLabelsForRuntimes(ctx, sourceGA.ID, runtimeIDs)
   137  	if err != nil {
   138  		return errors.Wrapf(err, "while getting scenario labels for runtimes with ids [%s]", strings.Join(runtimeIDs, ","))
   139  	}
   140  	for _, scenariosLabel := range scenariosLabels {
   141  		scenarios, err := label.ValueToStringsSlice(scenariosLabel.Value)
   142  		if err != nil {
   143  			return err
   144  		}
   145  		for _, scenario := range scenarios {
   146  			if scenario != DefaultScenario {
   147  				return errors.Errorf("could not move subaccount %s: runtime %s is in scenario %s in the source GA %s", subaccountInternalID, scenariosLabel.ObjectID, scenario, sourceGA.ID)
   148  			}
   149  		}
   150  	}
   151  	return nil
   152  }
   153  
   154  func (tmv *tenantMover) tenantsToUpsert(ctx context.Context, mappings []model.MovedSubaccountMappingInput) ([]*model.BusinessTenantMapping, []model.BusinessTenantMappingInput, error) {
   155  	tenantsToMove, subaccountIDs, parentTenants, err := tmv.tenantsWithExistingTargetParentTenants(ctx, mappings)
   156  	if err != nil {
   157  		return nil, nil, errors.Wrap(err, "while filtering out tenants without pre-existing target parent tenant")
   158  	}
   159  	if len(tenantsToMove) == 0 {
   160  		log.C(ctx).Infof("No tenants are available for moving")
   161  		return nil, nil, nil
   162  	}
   163  
   164  	existingTenants, err := tmv.tenantStorageService.ListsByExternalIDs(ctx, subaccountIDs)
   165  	if err != nil {
   166  		return nil, nil, errors.Wrap(err, "while getting tenants from DB")
   167  	}
   168  
   169  	existingTenantsMap := tenantMappings(existingTenants)
   170  
   171  	tenantsToCreate := make([]model.BusinessTenantMappingInput, 0)
   172  	tenantsToUpdate := make([]*model.BusinessTenantMapping, 0)
   173  
   174  	for _, mapping := range tenantsToMove {
   175  		tenantFromDB, ok := existingTenantsMap[mapping.SubaccountID]
   176  		if !ok {
   177  			log.C(ctx).Infof("Subaccount with external id %s does not exist, will be created in the correct parent tenant", mapping.SubaccountID)
   178  			mapping.TenantMappingInput.Parent = parentTenants[mapping.TargetTenant].ID
   179  			tenantsToCreate = append(tenantsToCreate, mapping.TenantMappingInput)
   180  			continue
   181  		}
   182  
   183  		if tenantFromDB.Parent == parentTenants[mapping.TargetTenant].ID {
   184  			log.C(ctx).Infof("Subaccount with external id %s is already moved in global account with external id %s", tenantFromDB.ExternalTenant, mapping.TargetTenant)
   185  			continue
   186  		}
   187  
   188  		if err := tmv.checkForScenarios(ctx, tenantFromDB.ID, mapping.SourceTenant); err != nil {
   189  			return nil, nil, errors.Wrapf(err, "subaccount with external id %s is part of a scenario and cannot be moved", mapping.SubaccountID)
   190  		}
   191  
   192  		tenantFromDB.Parent = parentTenants[mapping.TargetTenant].ID
   193  		tenantsToUpdate = append(tenantsToUpdate, tenantFromDB)
   194  	}
   195  
   196  	return tenantsToUpdate, tenantsToCreate, nil
   197  }
   198  
   199  func (tmv *tenantMover) tenantsWithExistingTargetParentTenants(ctx context.Context, mappings []model.MovedSubaccountMappingInput) ([]model.MovedSubaccountMappingInput, []string, map[string]*model.BusinessTenantMapping, error) {
   200  	existingParentTenants, err := tmv.targetParentTenants(ctx, mappings)
   201  	if err != nil {
   202  		return nil, nil, nil, err
   203  	}
   204  
   205  	subaccountIDs := make([]string, 0)
   206  	mappingsWithParentTenants := make([]model.MovedSubaccountMappingInput, 0)
   207  	for _, mapping := range mappings {
   208  		if _, ok := existingParentTenants[mapping.TargetTenant]; !ok {
   209  			log.C(ctx).Errorf("Target parent tenant %s of moved subaccount tenant %s does not exist, skipping...", mapping.TargetTenant, mapping.SubaccountID)
   210  			continue
   211  		}
   212  
   213  		subaccountIDs = append(subaccountIDs, mapping.SubaccountID)
   214  		mappingsWithParentTenants = append(mappingsWithParentTenants, mapping)
   215  	}
   216  
   217  	return mappingsWithParentTenants, subaccountIDs, existingParentTenants, nil
   218  }
   219  
   220  func (tmv *tenantMover) targetParentTenants(ctx context.Context, mappings []model.MovedSubaccountMappingInput) (map[string]*model.BusinessTenantMapping, error) {
   221  	parentTenantIDs := make([]string, 0)
   222  	for _, mapping := range mappings {
   223  		parentTenantIDs = append(parentTenantIDs, mapping.TargetTenant)
   224  	}
   225  
   226  	existingParents, err := tmv.tenantStorageService.ListsByExternalIDs(ctx, parentTenantIDs)
   227  	if err != nil {
   228  		return nil, errors.Wrap(err, "while getting target parent tenant ID of moved subaccounts")
   229  	}
   230  
   231  	return tenantMappings(existingParents), nil
   232  }
   233  func tenantMappings(tenants []*model.BusinessTenantMapping) map[string]*model.BusinessTenantMapping {
   234  	tenantsMap := make(map[string]*model.BusinessTenantMapping)
   235  	for _, t := range tenants {
   236  		tenantsMap[t.ExternalTenant] = t
   237  	}
   238  	return tenantsMap
   239  }
   240  
   241  func fetchMovedSubaccountsWithRetries(ctx context.Context, eventAPIClient EventAPIClient, retryAttempts uint, configProvider func() (QueryParams, PageConfig)) ([]model.MovedSubaccountMappingInput, error) {
   242  	var tenants []model.MovedSubaccountMappingInput
   243  	err := retry.Do(func() error {
   244  		fetchedTenants, err := fetchMovedSubaccounts(ctx, eventAPIClient, configProvider)
   245  		if err != nil {
   246  			return err
   247  		}
   248  		tenants = fetchedTenants
   249  		return nil
   250  	}, retry.Attempts(retryAttempts), retry.Delay(retryDelaySeconds*time.Second))
   251  	if err != nil {
   252  		return nil, errors.Wrapf(err, "while fetching moved tenants after %d retries", retryAttempts)
   253  	}
   254  
   255  	return tenants, nil
   256  }
   257  
   258  func fetchMovedSubaccounts(ctx context.Context, eventAPIClient EventAPIClient, configProvider func() (QueryParams, PageConfig)) ([]model.MovedSubaccountMappingInput, error) {
   259  	allMappings := make([]model.MovedSubaccountMappingInput, 0)
   260  
   261  	err := walkThroughPages(ctx, eventAPIClient, MovedSubaccountType, configProvider, func(page *EventsPage) error {
   262  		mappings := page.GetMovedSubaccounts(ctx)
   263  		allMappings = append(allMappings, mappings...)
   264  		return nil
   265  	})
   266  
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	return allMappings, nil
   272  }
   273  
   274  type noOpMover struct{}
   275  
   276  func newNoOpsMover() *noOpMover {
   277  	return &noOpMover{}
   278  }
   279  
   280  func (*noOpMover) TenantsToMove(context.Context, string, string) ([]model.MovedSubaccountMappingInput, error) {
   281  	return []model.MovedSubaccountMappingInput{}, nil
   282  }
   283  
   284  func (*noOpMover) MoveTenants(context.Context, []model.MovedSubaccountMappingInput) error {
   285  	return nil
   286  }