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 }