github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/eventing/service.go (about)

     1  package eventing
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/kyma-incubator/compass/components/director/pkg/normalizer"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/internal/domain/label"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    12  
    13  	"strings"
    14  
    15  	"github.com/google/uuid"
    16  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    17  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    18  	"github.com/kyma-incubator/compass/components/director/internal/model"
    19  	"github.com/pkg/errors"
    20  )
    21  
    22  const (
    23  	isNormalizedLabel = "isNormalized"
    24  	// RuntimeEventingURLLabel missing godoc
    25  	RuntimeEventingURLLabel = "runtime_eventServiceUrl"
    26  	// EmptyEventingURL missing godoc
    27  	EmptyEventingURL = ""
    28  	// RuntimeDefaultEventingLabelf missing godoc
    29  	RuntimeDefaultEventingLabelf = "%s_defaultEventing"
    30  )
    31  
    32  // RuntimeRepository missing godoc
    33  //go:generate mockery --name=RuntimeRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    34  type RuntimeRepository interface {
    35  	GetByFiltersAndID(ctx context.Context, tenant, id string, filter []*labelfilter.LabelFilter) (*model.Runtime, error)
    36  	GetOldestForFilters(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) (*model.Runtime, error)
    37  	List(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimePage, error)
    38  }
    39  
    40  // LabelRepository missing godoc
    41  //go:generate mockery --name=LabelRepository --output=automock --outpkg=automock --case=underscore --disable-version-string
    42  type LabelRepository interface {
    43  	Delete(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string, key string) error
    44  	GetByKey(ctx context.Context, tenant string, objectType model.LabelableObject, objectID, key string) (*model.Label, error)
    45  	DeleteByKey(ctx context.Context, tenant string, key string) error
    46  	Upsert(ctx context.Context, tenant string, label *model.Label) error
    47  }
    48  
    49  type service struct {
    50  	appNameNormalizer normalizer.Normalizator
    51  	runtimeRepo       RuntimeRepository
    52  	labelRepo         LabelRepository
    53  }
    54  
    55  // NewService missing godoc
    56  func NewService(appNameNormalizer normalizer.Normalizator, runtimeRepo RuntimeRepository, labelRepo LabelRepository) *service {
    57  	return &service{
    58  		appNameNormalizer: appNameNormalizer,
    59  		runtimeRepo:       runtimeRepo,
    60  		labelRepo:         labelRepo,
    61  	}
    62  }
    63  
    64  // CleanupAfterUnregisteringApplication missing godoc
    65  func (s *service) CleanupAfterUnregisteringApplication(ctx context.Context, appID uuid.UUID) (*model.ApplicationEventingConfiguration, error) {
    66  	tenantID, err := tenant.LoadFromContext(ctx)
    67  	if err != nil {
    68  		return nil, errors.Wrapf(err, "while loading tenant from context")
    69  	}
    70  
    71  	labelKey := getDefaultEventingForAppLabelKey(appID)
    72  	err = s.labelRepo.DeleteByKey(ctx, tenantID, labelKey)
    73  	if err != nil {
    74  		return nil, errors.Wrapf(err, "while deleting Labels for Application with id %s", appID)
    75  	}
    76  
    77  	return model.NewEmptyApplicationEventingConfig()
    78  }
    79  
    80  // SetForApplication missing godoc
    81  func (s *service) SetForApplication(ctx context.Context, runtimeID uuid.UUID, app model.Application) (*model.ApplicationEventingConfiguration, error) {
    82  	tenantID, err := tenant.LoadFromContext(ctx)
    83  	if err != nil {
    84  		return nil, errors.Wrapf(err, "while loading tenant from context")
    85  	}
    86  
    87  	appID, err := uuid.Parse(app.ID)
    88  	if err != nil {
    89  		return nil, errors.Wrapf(err, "while parsing application ID: %s", app.ID)
    90  	}
    91  
    92  	_, _, err = s.unsetForApplication(ctx, tenantID, appID)
    93  	if err != nil {
    94  		return nil, errors.Wrap(err, "while deleting default eventing for application")
    95  	}
    96  
    97  	runtime, found, err := s.getRuntimeForApplicationScenarios(ctx, tenantID, runtimeID, appID)
    98  	if err != nil {
    99  		return nil, errors.Wrap(err, "while getting the runtime")
   100  	}
   101  
   102  	if !found {
   103  		return nil, fmt.Errorf("does not find the given runtime [ID=%s] assigned to the application scenarios", runtimeID)
   104  	}
   105  
   106  	if err := s.setRuntimeForAppEventing(ctx, tenantID, *runtime, appID); err != nil {
   107  		return nil, errors.Wrap(err, "while setting the runtime as default for eventing for application")
   108  	}
   109  
   110  	runtimeEventingCfg, err := s.GetForRuntime(ctx, runtimeID)
   111  	if err != nil {
   112  		return nil, errors.Wrap(err, "while fetching eventing configuration for runtime")
   113  	}
   114  
   115  	shouldNormalize, err := s.shouldNormalizeApplicationName(ctx, tenantID, runtime)
   116  	if err != nil {
   117  		return nil, errors.Wrap(err, "while determining whether application name should be normalized in runtime eventing URL")
   118  	}
   119  
   120  	appName := app.Name
   121  	if shouldNormalize {
   122  		appName = s.appNameNormalizer.Normalize(app.Name)
   123  	}
   124  
   125  	return model.NewApplicationEventingConfiguration(runtimeEventingCfg.DefaultURL, appName)
   126  }
   127  
   128  // UnsetForApplication missing godoc
   129  func (s *service) UnsetForApplication(ctx context.Context, app model.Application) (*model.ApplicationEventingConfiguration, error) {
   130  	tenantID, err := tenant.LoadFromContext(ctx)
   131  	if err != nil {
   132  		return nil, errors.Wrapf(err, "while loading tenant from context")
   133  	}
   134  
   135  	appID, err := uuid.Parse(app.ID)
   136  	if err != nil {
   137  		return nil, errors.Wrapf(err, "while parsing application ID: %s", app.ID)
   138  	}
   139  
   140  	runtime, found, err := s.unsetForApplication(ctx, tenantID, appID)
   141  	if err != nil {
   142  		return nil, errors.Wrap(err, "while deleting default eventing for application")
   143  	}
   144  
   145  	if !found {
   146  		return model.NewEmptyApplicationEventingConfig()
   147  	}
   148  
   149  	runtimeID, err := uuid.Parse(runtime.ID)
   150  	if err != nil {
   151  		return nil, errors.Wrap(err, "while parsing runtime ID as UUID")
   152  	}
   153  
   154  	runtimeEventingCfg, err := s.GetForRuntime(ctx, runtimeID)
   155  	if err != nil {
   156  		return nil, errors.Wrap(err, "while fetching eventing configuration for runtime")
   157  	}
   158  
   159  	shouldNormalize, err := s.shouldNormalizeApplicationName(ctx, tenantID, runtime)
   160  	if err != nil {
   161  		return nil, errors.Wrap(err, "while determining whether application name should be normalized in runtime eventing URL")
   162  	}
   163  
   164  	appName := app.Name
   165  	if shouldNormalize {
   166  		appName = s.appNameNormalizer.Normalize(app.Name)
   167  	}
   168  
   169  	return model.NewApplicationEventingConfiguration(runtimeEventingCfg.DefaultURL, appName)
   170  }
   171  
   172  // GetForApplication missing godoc
   173  func (s *service) GetForApplication(ctx context.Context, app model.Application) (*model.ApplicationEventingConfiguration, error) {
   174  	tenantID, err := tenant.LoadFromContext(ctx)
   175  	if err != nil {
   176  		return nil, errors.Wrapf(err, "while loading tenant from context")
   177  	}
   178  
   179  	appID, err := uuid.Parse(app.ID)
   180  	if err != nil {
   181  		return nil, errors.Wrapf(err, "while parsing application ID: %s", app.ID)
   182  	}
   183  
   184  	var defaultVerified, foundDefault, foundOldest bool
   185  	runtime, foundDefault, err := s.getDefaultRuntimeForAppEventing(ctx, tenantID, appID)
   186  	if err != nil {
   187  		return nil, errors.Wrap(err, "while getting default runtime for app eventing")
   188  	}
   189  
   190  	if foundDefault {
   191  		if defaultVerified, err = s.ensureScenariosOrDeleteLabel(ctx, tenantID, *runtime, appID); err != nil {
   192  			return nil, errors.Wrap(err, "while ensuring the scenarios assigned to the runtime and application")
   193  		}
   194  	}
   195  
   196  	if !defaultVerified {
   197  		runtime, foundOldest, err = s.getOldestRuntime(ctx, tenantID, appID)
   198  		if err != nil {
   199  			return nil, errors.Wrap(err, "while getting the oldest runtime for scenarios")
   200  		}
   201  
   202  		if foundOldest {
   203  			if err := s.setRuntimeForAppEventing(ctx, tenantID, *runtime, appID); err != nil {
   204  				return nil, errors.Wrap(err, "while setting the runtime as default for eventing for application")
   205  			}
   206  		}
   207  	}
   208  
   209  	if runtime == nil {
   210  		return model.NewEmptyApplicationEventingConfig()
   211  	}
   212  
   213  	runtimeID, err := uuid.Parse(runtime.ID)
   214  	if err != nil {
   215  		return nil, errors.Wrap(err, "while parsing runtime ID as UUID")
   216  	}
   217  
   218  	runtimeEventingCfg, err := s.GetForRuntime(ctx, runtimeID)
   219  	if err != nil {
   220  		return nil, errors.Wrap(err, "while fetching eventing configuration for runtime")
   221  	}
   222  
   223  	shouldNormalize, err := s.shouldNormalizeApplicationName(ctx, tenantID, runtime)
   224  	if err != nil {
   225  		return nil, errors.Wrap(err, "while determining whether application name should be normalized in runtime eventing URL")
   226  	}
   227  
   228  	appName := app.Name
   229  	if shouldNormalize {
   230  		appName = s.appNameNormalizer.Normalize(app.Name)
   231  	}
   232  
   233  	if app.SystemNumber != nil {
   234  		appName += "-" + *app.SystemNumber
   235  	}
   236  
   237  	return model.NewApplicationEventingConfiguration(runtimeEventingCfg.DefaultURL, appName)
   238  }
   239  
   240  // GetForRuntime missing godoc
   241  func (s *service) GetForRuntime(ctx context.Context, runtimeID uuid.UUID) (*model.RuntimeEventingConfiguration, error) {
   242  	tenantID, err := tenant.LoadFromContext(ctx)
   243  	if err != nil {
   244  		return nil, errors.Wrapf(err, "while loading tenant from context")
   245  	}
   246  
   247  	var eventingURL string
   248  	label, err := s.labelRepo.GetByKey(ctx, tenantID, model.RuntimeLabelableObject, runtimeID.String(), RuntimeEventingURLLabel)
   249  	if err != nil {
   250  		if !apperrors.IsNotFoundError(err) {
   251  			return nil, errors.Wrap(err, fmt.Sprintf("while getting the label [key=%s] for runtime [ID=%s]", RuntimeEventingURLLabel, runtimeID))
   252  		}
   253  
   254  		return model.NewRuntimeEventingConfiguration(EmptyEventingURL)
   255  	}
   256  
   257  	if label != nil {
   258  		var ok bool
   259  		if eventingURL, ok = label.Value.(string); !ok {
   260  			return nil, fmt.Errorf("unable to cast label [key=%s, runtimeID=%s] value as a string", RuntimeEventingURLLabel, runtimeID)
   261  		}
   262  	}
   263  
   264  	return model.NewRuntimeEventingConfiguration(eventingURL)
   265  }
   266  
   267  func (s *service) shouldNormalizeApplicationName(ctx context.Context, tenant string, runtime *model.Runtime) (bool, error) {
   268  	label, err := s.labelRepo.GetByKey(ctx, tenant, model.RuntimeLabelableObject, runtime.ID, isNormalizedLabel)
   269  	notFoundErr := apperrors.IsNotFoundError(err)
   270  	if !notFoundErr && err != nil {
   271  		return false, errors.Wrap(err, fmt.Sprintf("while getting the label [key=%s] for runtime [ID=%s]", isNormalizedLabel, runtime.ID))
   272  	}
   273  
   274  	return notFoundErr || label.Value == "true", nil
   275  }
   276  
   277  func (s *service) unsetForApplication(ctx context.Context, tenantID string, appID uuid.UUID) (*model.Runtime, bool, error) {
   278  	runtime, foundDefault, err := s.getDefaultRuntimeForAppEventing(ctx, tenantID, appID)
   279  	if err != nil {
   280  		return nil, false, errors.Wrap(err, "while getting default runtime for app eventing")
   281  	}
   282  
   283  	if !foundDefault {
   284  		return nil, foundDefault, nil
   285  	}
   286  
   287  	runtimeID, err := uuid.Parse(runtime.ID)
   288  	if err != nil {
   289  		return nil, foundDefault, errors.Wrap(err, "while parsing runtime ID as UUID")
   290  	}
   291  
   292  	labelKey := getDefaultEventingForAppLabelKey(appID)
   293  	err = s.deleteLabelFromRuntime(ctx, tenantID, labelKey, runtimeID)
   294  	if err != nil {
   295  		return nil, foundDefault, errors.Wrap(err, "while deleting label")
   296  	}
   297  
   298  	return runtime, foundDefault, nil
   299  }
   300  
   301  func (s *service) getDefaultRuntimeForAppEventing(ctx context.Context, tenantID string, appID uuid.UUID) (*model.Runtime, bool, error) {
   302  	labelKey := getDefaultEventingForAppLabelKey(appID)
   303  	labelFilterForRuntime := []*labelfilter.LabelFilter{labelfilter.NewForKey(labelKey)}
   304  
   305  	var cursor string
   306  	runtimesPage, err := s.runtimeRepo.List(ctx, tenantID, labelFilterForRuntime, 1, cursor)
   307  	if err != nil {
   308  		return nil, false, errors.Wrap(err, fmt.Sprintf("while fetching runtimes with label [key=%s]", labelKey))
   309  	}
   310  
   311  	if runtimesPage.TotalCount == 0 {
   312  		return nil, false, nil
   313  	}
   314  
   315  	if runtimesPage.TotalCount > 1 {
   316  		return nil, false, fmt.Errorf("got multpile runtimes labeled [key=%s] as default for eventing", labelKey)
   317  	}
   318  
   319  	runtime := runtimesPage.Data[0]
   320  
   321  	return runtime, true, nil
   322  }
   323  
   324  func (s *service) ensureScenariosOrDeleteLabel(ctx context.Context, tenantID string, runtime model.Runtime, appID uuid.UUID) (bool, error) {
   325  	runtimeID, err := uuid.Parse(runtime.ID)
   326  	if err != nil {
   327  		return false, errors.Wrap(err, "while parsing runtime ID as UUID")
   328  	}
   329  
   330  	_, belongsToScenarios, err := s.getRuntimeForApplicationScenarios(ctx, tenantID, runtimeID, appID)
   331  	if err != nil {
   332  		return false, errors.Wrap(err, fmt.Sprintf("while verifing whether runtime [ID=%s] belongs to the application scenarios", runtimeID))
   333  	}
   334  
   335  	if !belongsToScenarios {
   336  		labelKey := getDefaultEventingForAppLabelKey(appID)
   337  		if err = s.deleteLabelFromRuntime(ctx, tenantID, labelKey, runtimeID); err != nil {
   338  			return false, errors.Wrap(err, "when deleting current default runtime for the application because of scenarios mismatch")
   339  		}
   340  	}
   341  
   342  	return belongsToScenarios, nil
   343  }
   344  
   345  func (s *service) getRuntimeForApplicationScenarios(ctx context.Context, tenantID string, runtimeID, appID uuid.UUID) (*model.Runtime, bool, error) {
   346  	runtimeScenariosFilter, hasScenarios, err := s.getScenariosFilter(ctx, tenantID, appID)
   347  	if err != nil {
   348  		return nil, false, errors.Wrap(err, "while getting application scenarios")
   349  	}
   350  
   351  	if !hasScenarios {
   352  		return nil, false, nil
   353  	}
   354  
   355  	runtime, err := s.runtimeRepo.GetByFiltersAndID(ctx, tenantID, runtimeID.String(), runtimeScenariosFilter)
   356  	if err != nil {
   357  		if !apperrors.IsNotFoundError(err) {
   358  			return nil, false, errors.Wrap(err, fmt.Sprintf("while getting the runtime [ID=%s] with scenarios with filter", runtimeID))
   359  		}
   360  
   361  		return nil, false, nil
   362  	}
   363  
   364  	return runtime, true, nil
   365  }
   366  
   367  func (s *service) deleteLabelFromRuntime(ctx context.Context, tenantID, labelKey string, runtimeID uuid.UUID) error {
   368  	if err := s.labelRepo.Delete(ctx, tenantID, model.RuntimeLabelableObject, runtimeID.String(), labelKey); err != nil {
   369  		return errors.Wrap(err, fmt.Sprintf("while deleting label [key=%s, runtimeID=%s]", labelKey, runtimeID))
   370  	}
   371  
   372  	return nil
   373  }
   374  
   375  func (s *service) getOldestRuntime(ctx context.Context, tenantID string, appID uuid.UUID) (*model.Runtime, bool, error) {
   376  	runtimeScenariosFilter, hasScenarios, err := s.getScenariosFilter(ctx, tenantID, appID)
   377  	if err != nil {
   378  		return nil, false, errors.Wrap(err, "while getting application scenarios")
   379  	}
   380  
   381  	if !hasScenarios {
   382  		return nil, false, nil
   383  	}
   384  
   385  	runtime, err := s.runtimeRepo.GetOldestForFilters(ctx, tenantID, runtimeScenariosFilter)
   386  	if err != nil {
   387  		if !apperrors.IsNotFoundError(err) {
   388  			return nil, false, errors.Wrap(err, fmt.Sprintf("while getting the oldest runtime for application [ID=%s] scenarios with filter", appID))
   389  		}
   390  
   391  		return nil, false, nil
   392  	}
   393  
   394  	return runtime, true, nil
   395  }
   396  
   397  func (s *service) getScenariosFilter(ctx context.Context, tenantID string, appID uuid.UUID) ([]*labelfilter.LabelFilter, bool, error) {
   398  	appScenariosLabel, err := s.labelRepo.GetByKey(ctx, tenantID, model.ApplicationLabelableObject, appID.String(), model.ScenariosKey)
   399  	if err != nil {
   400  		if !apperrors.IsNotFoundError(err) {
   401  			return nil, false, errors.Wrap(err, fmt.Sprintf("while getting the label [key=%s] for application [ID=%s]", model.ScenariosKey, appID))
   402  		}
   403  
   404  		return nil, false, nil
   405  	}
   406  
   407  	scenarios, err := label.ValueToStringsSlice(appScenariosLabel.Value)
   408  	if err != nil {
   409  		return nil, false, errors.Wrap(err, fmt.Sprintf("while converting label [key=%s] value to a slice of strings", model.ScenariosKey))
   410  	}
   411  
   412  	scenariosQuery := BuildQueryForScenarios(scenarios)
   413  	runtimeScenariosFilter := []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, scenariosQuery)}
   414  
   415  	return runtimeScenariosFilter, true, nil
   416  }
   417  
   418  func (s *service) setRuntimeForAppEventing(ctx context.Context, tenant string, runtime model.Runtime, appID uuid.UUID) error {
   419  	defaultEventingForAppLabel := model.NewLabelForRuntime(runtime.ID, tenant, getDefaultEventingForAppLabelKey(appID), "true")
   420  	if err := s.labelRepo.Upsert(ctx, tenant, defaultEventingForAppLabel); err != nil {
   421  		return errors.Wrap(err, fmt.Sprintf("while labeling the runtime [ID=%s] as default for eventing for application [ID=%s]", runtime.ID, appID))
   422  	}
   423  
   424  	return nil
   425  }
   426  
   427  // BuildQueryForScenarios missing godoc
   428  func BuildQueryForScenarios(scenarios []string) string {
   429  	var queryBuilder strings.Builder
   430  	for idx, scenario := range scenarios {
   431  		if idx > 0 {
   432  			queryBuilder.WriteString(` || `)
   433  		}
   434  
   435  		queryBuilder.WriteString(fmt.Sprintf(`@ == "%s"`, scenario))
   436  	}
   437  	query := fmt.Sprintf(`$[*] ? ( %s )`, queryBuilder.String())
   438  
   439  	return query
   440  }
   441  
   442  func getDefaultEventingForAppLabelKey(appID uuid.UUID) string {
   443  	return fmt.Sprintf(RuntimeDefaultEventingLabelf, appID.String())
   444  }