github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/nsadapter/handler/exposed_systems_handler.go (about)

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/google/uuid"
    11  
    12  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    13  
    14  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    15  	"github.com/kyma-incubator/compass/components/director/internal/model"
    16  	"github.com/kyma-incubator/compass/components/director/internal/nsadapter/httputil"
    17  	"github.com/kyma-incubator/compass/components/director/internal/nsadapter/nsmodel"
    18  	"github.com/kyma-incubator/compass/components/director/pkg/httputils"
    19  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    20  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    21  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  const (
    26  	deltaReportType      = "delta"
    27  	fullReportType       = "full"
    28  	reportTypeQueryParam = "reportType"
    29  	notSubaccountMarker  = "not subaccount"
    30  )
    31  
    32  //go:generate mockery --exported --name=applicationService --output=automock --outpkg=automock --case=underscore --disable-version-string
    33  type applicationService interface {
    34  	CreateFromTemplate(ctx context.Context, in model.ApplicationRegisterInput, appTemplateID *string) (string, error)
    35  	Upsert(ctx context.Context, in model.ApplicationRegisterInput) error
    36  	Update(ctx context.Context, id string, in model.ApplicationUpdateInput) error
    37  	GetSccSystem(ctx context.Context, sccSubaccount, locationID, virtualHost string) (*model.Application, error)
    38  	ListBySCC(ctx context.Context, filter *labelfilter.LabelFilter) ([]*model.ApplicationWithLabel, error)
    39  	SetLabel(ctx context.Context, label *model.LabelInput) error
    40  	GetLabel(ctx context.Context, applicationID string, key string) (*model.Label, error)
    41  	ListSCCs(ctx context.Context) ([]*model.SccMetadata, error)
    42  }
    43  
    44  //go:generate mockery --exported --name=applicationConverter --output=automock --outpkg=automock --case=underscore --disable-version-string
    45  type applicationConverter interface {
    46  	CreateInputJSONToModel(ctx context.Context, in string) (model.ApplicationRegisterInput, error)
    47  }
    48  
    49  //go:generate mockery --exported --name=applicationTemplateService --output=automock --outpkg=automock --case=underscore --disable-version-string
    50  type applicationTemplateService interface {
    51  	Get(ctx context.Context, id string) (*model.ApplicationTemplate, error)
    52  	PrepareApplicationCreateInputJSON(appTemplate *model.ApplicationTemplate, values model.ApplicationFromTemplateInputValues) (string, error)
    53  }
    54  
    55  //go:generate mockery --exported --name=tenantService --output=automock --outpkg=automock --case=underscore --disable-version-string
    56  type tenantService interface {
    57  	ListsByExternalIDs(ctx context.Context, ids []string) ([]*model.BusinessTenantMapping, error)
    58  }
    59  
    60  // NewHandler returns new ns-adapter handler
    61  func NewHandler(appSvc applicationService, appConverter applicationConverter, appTemplateSvc applicationTemplateService, tntSvc tenantService, transact persistence.Transactioner) *Handler {
    62  	return &Handler{appSvc: appSvc, appConverter: appConverter, appTemplateSvc: appTemplateSvc, tntSvc: tntSvc, transact: transact}
    63  }
    64  
    65  // Handler implements handler interface
    66  type Handler struct {
    67  	appSvc         applicationService
    68  	appConverter   applicationConverter
    69  	appTemplateSvc applicationTemplateService
    70  	tntSvc         tenantService
    71  	transact       persistence.Transactioner
    72  }
    73  
    74  // Description - Bulk create-or-update operation on exposed on-premise systems. This handler supports two types of reports - full and delta.
    75  // This handler takes a list of fully described SCCs together with the exposed systems.
    76  // It creates new application for every exposed system for which CMP isn't aware of, and updates the metadata for the ones it is.
    77  // - In case of full report: If there is SCC which was not reported, all exposed systems of this SCC are marked as unreachable.
    78  // - In case of delta report: If there are missing exposed systems for a particular SCC, these systems are marked as unreachable.
    79  // URL          - /api/v1/notifications
    80  // Query Params - reportType=[full, delta]
    81  // HTTP Method  - PUT
    82  // Content-Type - application/json
    83  // HTTP Codes:
    84  // 204 No Content:
    85  // - In case of delta report: if all systems are processed successfully
    86  // - In case of full report: if the request was processed
    87  // 200 OK:
    88  // - In case of delta report: if update/create failed for some on-premise systems
    89  // 400 Bad Request:
    90  // - missing or invalid required report type query parameter
    91  // - failed to parse request body
    92  // - validating request body failed
    93  // 500 Internal Server Error:
    94  // - In case internal issue occurred. Example: db communication failed
    95  func (a *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    96  	ctx := req.Context()
    97  	logger := log.C(ctx)
    98  
    99  	defer func() {
   100  		if err := req.Body.Close(); err != nil {
   101  			logger.Error("Got error on closing request body", err)
   102  		}
   103  	}()
   104  
   105  	reportHandlers := map[string]func(context.Context, []*nsmodel.SCC, []httputil.Detail, nsmodel.Report, http.ResponseWriter){
   106  		fullReportType:  a.fullReportHandler,
   107  		deltaReportType: a.deltaReportHandler,
   108  	}
   109  
   110  	reportType := req.URL.Query().Get(reportTypeQueryParam)
   111  	logger.Infof("New report of type %q received", reportType)
   112  
   113  	reportHandler, found := reportHandlers[reportType]
   114  	if !found {
   115  		httputil.RespondWithError(ctx, rw, http.StatusBadRequest, httputil.Error{
   116  			Code:    http.StatusBadRequest,
   117  			Message: "the query parameter 'reportType' is missing or invalid",
   118  		})
   119  		return
   120  	}
   121  
   122  	var reportData nsmodel.Report
   123  	err := json.NewDecoder(req.Body).Decode(&reportData)
   124  	if err != nil {
   125  		logger.Warnf("Got error on parsing Request Body: %v\n", err)
   126  		httputil.RespondWithError(ctx, rw, http.StatusBadRequest, httputil.Error{
   127  			Code:    http.StatusBadRequest,
   128  			Message: "failed to parse request body",
   129  		})
   130  		return
   131  	}
   132  
   133  	if err := reportData.Validate(); err != nil {
   134  		logger.Warnf("Got error while validating Request Body: %v\n", err)
   135  		httputil.RespondWithError(ctx, rw, http.StatusBadRequest, httputil.Error{
   136  			Code:    http.StatusBadRequest,
   137  			Message: err.Error(),
   138  		})
   139  		return
   140  	}
   141  
   142  	sccs := make([]*nsmodel.SCC, 0, len(reportData.Value))
   143  	externalIDs := make([]string, 0, len(reportData.Value))
   144  	for _, scc := range reportData.Value {
   145  		// New object with the same data is created and added to the sccs slice instead of adding &scc to the slice
   146  		// because otherwise the slice is populated with copies of the last scc`s address
   147  		s := &nsmodel.SCC{
   148  			ExternalSubaccountID: scc.ExternalSubaccountID,
   149  			InternalSubaccountID: scc.InternalSubaccountID,
   150  			LocationID:           scc.LocationID,
   151  			ExposedSystems:       scc.ExposedSystems,
   152  		}
   153  		externalIDs = append(externalIDs, scc.ExternalSubaccountID)
   154  		sccs = append(sccs, s)
   155  	}
   156  
   157  	tenants, err := a.listTenantsByExternalIDs(ctx, externalIDs)
   158  	if err != nil {
   159  		logger.Warnf("Got error while listing subaccounts: %v\n", err)
   160  		httputil.RespondWithError(ctx, rw, http.StatusInternalServerError, httputil.Error{
   161  			Code:    http.StatusInternalServerError,
   162  			Message: "Update failed",
   163  		})
   164  		return
   165  	}
   166  	mapExternalToInternal(ctx, tenants, sccs)
   167  	details := make([]httputil.Detail, 0)
   168  	filteredSccs := filterSccsByInternalID(ctx, sccs, &details)
   169  
   170  	logger.Infof("Starting processing of %q report.", reportType)
   171  	reportHandler(ctx, filteredSccs, details, reportData, rw)
   172  }
   173  
   174  func (a *Handler) deltaReportHandler(ctx context.Context, filteredSccs []*nsmodel.SCC, details []httputil.Detail, _ nsmodel.Report, rw http.ResponseWriter) {
   175  	a.processDelta(ctx, filteredSccs, &details)
   176  	if len(details) == 0 {
   177  		httputils.RespondWithBody(ctx, rw, http.StatusNoContent, struct{}{})
   178  		return
   179  	}
   180  	httputil.RespondWithError(ctx, rw, http.StatusOK, httputil.DetailedError{
   181  		Code:    http.StatusOK,
   182  		Message: "Update/create failed for some on-premise systems",
   183  		Details: details,
   184  	})
   185  }
   186  
   187  func (a *Handler) fullReportHandler(ctx context.Context, filteredSccs []*nsmodel.SCC, details []httputil.Detail, reportData nsmodel.Report, rw http.ResponseWriter) {
   188  	a.processDelta(ctx, filteredSccs, &details)
   189  	a.handleUnreachableScc(ctx, reportData)
   190  	httputils.RespondWithBody(ctx, rw, http.StatusNoContent, struct{}{})
   191  }
   192  
   193  func (a *Handler) listTenantsByExternalIDs(ctx context.Context, ids []string) ([]*model.BusinessTenantMapping, error) {
   194  	if len(ids) == 0 {
   195  		return make([]*model.BusinessTenantMapping, 0), nil
   196  	}
   197  
   198  	tx, err := a.transact.Begin()
   199  	if err != nil {
   200  		log.C(ctx).Warn(errors.Wrapf(err, "while openning transaction"))
   201  		return nil, err
   202  	}
   203  	defer a.transact.RollbackUnlessCommitted(ctx, tx)
   204  
   205  	ctxWithTransaction := persistence.SaveToContext(ctx, tx)
   206  	tenants, err := a.tntSvc.ListsByExternalIDs(ctxWithTransaction, ids)
   207  	if err != nil {
   208  		log.C(ctx).Warn(errors.Wrapf(err, "while listing tenants by external ids"))
   209  		return nil, err
   210  	}
   211  
   212  	if err := tx.Commit(); err != nil {
   213  		log.C(ctx).Warn(errors.Wrapf(err, "while committing transaction"))
   214  		return nil, err
   215  	}
   216  
   217  	return tenants, nil
   218  }
   219  
   220  func (a *Handler) listSCCs(ctx context.Context) ([]*model.SccMetadata, error) {
   221  	tx, err := a.transact.Begin()
   222  	if err != nil {
   223  		log.C(ctx).Warn(errors.Wrapf(err, "while openning transaction"))
   224  		return nil, err
   225  	}
   226  	defer a.transact.RollbackUnlessCommitted(ctx, tx)
   227  
   228  	ctxWithTransaction := persistence.SaveToContext(ctx, tx)
   229  	sccs, err := a.appSvc.ListSCCs(ctxWithTransaction)
   230  	if err != nil {
   231  		log.C(ctx).Warn(errors.Wrapf(err, "while listing all sccs"))
   232  		return nil, err
   233  	}
   234  
   235  	if err := tx.Commit(); err != nil {
   236  		log.C(ctx).Warn(errors.Wrapf(err, "while committing transaction"))
   237  		return nil, err
   238  	}
   239  
   240  	return sccs, nil
   241  }
   242  
   243  func (a *Handler) handleUnreachableScc(ctx context.Context, reportData nsmodel.Report) {
   244  	sccs, err := a.listSCCs(ctx)
   245  	if err != nil {
   246  		log.C(ctx).Warn(errors.Wrapf(err, "while listing sccs"))
   247  		return
   248  	}
   249  
   250  	if len(sccs) == len(reportData.Value) {
   251  		return
   252  	}
   253  
   254  	sccsFromNs := make([]*model.SccMetadata, 0, len(reportData.Value))
   255  	for _, scc := range reportData.Value {
   256  		sccsFromNs = append(sccsFromNs, &model.SccMetadata{
   257  			Subaccount: scc.ExternalSubaccountID,
   258  			LocationID: scc.LocationID,
   259  		})
   260  	}
   261  
   262  	sccsToMarkAsUnreachable := difference(sccs, sccsFromNs)
   263  
   264  	externalSubaccounts := make([]string, 0, len(sccsToMarkAsUnreachable))
   265  	for _, scc := range sccsToMarkAsUnreachable {
   266  		externalSubaccounts = append(externalSubaccounts, scc.Subaccount)
   267  	}
   268  
   269  	internalSubaccounts, err := a.listTenantsByExternalIDs(ctx, externalSubaccounts)
   270  	if err != nil {
   271  		log.C(ctx).Warn(errors.Wrapf(err, "while listing subaccounts"))
   272  		return
   273  	}
   274  
   275  	externalToInternalSub := make(map[string]string, len(internalSubaccounts))
   276  	for _, subaccount := range internalSubaccounts {
   277  		externalToInternalSub[subaccount.ExternalTenant] = subaccount.ID
   278  	}
   279  
   280  	for _, scc := range sccsToMarkAsUnreachable {
   281  		scc.InternalSubaccountID = externalToInternalSub[scc.Subaccount]
   282  	}
   283  
   284  	for _, scc := range sccsToMarkAsUnreachable {
   285  		ctxWithSubaccount := tenant.SaveToContext(ctx, scc.InternalSubaccountID, scc.Subaccount)
   286  		appsWithLabels, ok := a.listAppsByScc(ctxWithSubaccount, scc.Subaccount, scc.LocationID)
   287  		if ok {
   288  			for _, appWithLabels := range appsWithLabels {
   289  				a.markSystemAsUnreachable(ctxWithSubaccount, appWithLabels.App)
   290  			}
   291  		}
   292  	}
   293  }
   294  
   295  func (a *Handler) processDelta(ctx context.Context, sccs []*nsmodel.SCC, details *[]httputil.Detail) {
   296  	for _, scc := range sccs {
   297  		ctxWithTenant := tenant.SaveToContext(ctx, scc.InternalSubaccountID, scc.ExternalSubaccountID)
   298  		if ok := a.handleSccSystems(ctxWithTenant, *scc); !ok {
   299  			addErrorDetailsMsg(details, scc, "Creation failed")
   300  		}
   301  	}
   302  }
   303  
   304  func (a *Handler) handleSccSystems(ctx context.Context, scc nsmodel.SCC) bool {
   305  	successfulUpsert := a.upsertSccSystems(ctx, scc)
   306  	successfulMark := a.markAsUnreachable(ctx, scc)
   307  	return successfulUpsert && successfulMark
   308  }
   309  
   310  func (a *Handler) upsertSccSystems(ctx context.Context, scc nsmodel.SCC) bool {
   311  	success := true
   312  	for _, system := range scc.ExposedSystems {
   313  		if len(system.TemplateID) == 0 {
   314  			log.C(ctx).Infof("Skipping processing of system with unsupported type %s", system.SystemType)
   315  			continue
   316  		}
   317  
   318  		tx, err := a.transact.Begin()
   319  		if err != nil {
   320  			log.C(ctx).Warn(errors.Wrapf(err, "while openning transaction"))
   321  			return false
   322  		}
   323  		ctxWithTransaction := persistence.SaveToContext(ctx, tx)
   324  
   325  		var txSucceeded bool
   326  		if system.SystemNumber != "" {
   327  			txSucceeded = a.upsertWithSystemNumber(ctxWithTransaction, scc, system)
   328  		} else {
   329  			txSucceeded = a.upsert(ctxWithTransaction, scc, system)
   330  		}
   331  
   332  		if txSucceeded {
   333  			if err := tx.Commit(); err != nil {
   334  				txSucceeded = false
   335  				log.C(ctx).Warn(errors.Wrapf(err, "while committing transaction"))
   336  			}
   337  		}
   338  
   339  		a.transact.RollbackUnlessCommitted(ctx, tx)
   340  		success = success && txSucceeded
   341  	}
   342  	return success
   343  }
   344  
   345  func (a *Handler) prepareAppInput(ctx context.Context, scc nsmodel.SCC, system nsmodel.System) (*model.ApplicationRegisterInput, error) {
   346  	template, err := a.appTemplateSvc.Get(ctx, system.TemplateID)
   347  	if err != nil {
   348  		return nil, errors.Wrapf(err, "while getting application template with id: %s", system.TemplateID)
   349  	}
   350  
   351  	values := model.ApplicationFromTemplateInputValues{
   352  		{
   353  			Placeholder: "name",
   354  			Value:       "on-premise-system" + uuid.New().String(),
   355  		},
   356  		{
   357  			Placeholder: "description",
   358  			Value:       system.Description,
   359  		},
   360  		{
   361  			Placeholder: "subaccount",
   362  			Value:       scc.ExternalSubaccountID,
   363  		},
   364  		{
   365  			Placeholder: "location-id",
   366  			Value:       scc.LocationID,
   367  		},
   368  		{
   369  			Placeholder: "system-type",
   370  			Value:       system.SystemType,
   371  		},
   372  		{
   373  			Placeholder: "host",
   374  			Value:       system.Host,
   375  		},
   376  		{
   377  			Placeholder: "protocol",
   378  			Value:       system.Protocol,
   379  		},
   380  		{
   381  			Placeholder: "system-number",
   382  			Value:       system.SystemNumber,
   383  		},
   384  		{
   385  			Placeholder: "system-status",
   386  			Value:       system.Status,
   387  		},
   388  	}
   389  
   390  	appInputJSON, err := a.appTemplateSvc.PrepareApplicationCreateInputJSON(template, values)
   391  	if err != nil {
   392  		return nil, errors.Wrapf(err, "while preparing application create input from template with id:%s", system.TemplateID)
   393  	}
   394  
   395  	appInput, err := a.appConverter.CreateInputJSONToModel(ctx, appInputJSON)
   396  	if err != nil {
   397  		return nil, errors.Wrapf(err, "while preparing application create input from json")
   398  	}
   399  
   400  	if appInput.SystemNumber != nil && *appInput.SystemNumber == "" {
   401  		appInput.SystemNumber = nil
   402  	}
   403  
   404  	return &appInput, nil
   405  }
   406  
   407  func (a *Handler) upsertWithSystemNumber(ctx context.Context, scc nsmodel.SCC, system nsmodel.System) bool {
   408  	appInput, err := a.prepareAppInput(ctx, scc, system)
   409  	if err != nil {
   410  		log.C(ctx).Warn(errors.Wrapf(err, "while upserting Application"))
   411  		return false
   412  	}
   413  
   414  	if err := a.appSvc.Upsert(ctx, *appInput); err != nil {
   415  		log.C(ctx).Warn(errors.Wrapf(err, "while upserting Application"))
   416  		return false
   417  	}
   418  
   419  	return true
   420  }
   421  
   422  func (a *Handler) upsert(ctx context.Context, scc nsmodel.SCC, system nsmodel.System) bool {
   423  	app, err := a.appSvc.GetSccSystem(ctx, scc.ExternalSubaccountID, scc.LocationID, system.Host)
   424  
   425  	if err != nil && isNotFoundError(err) {
   426  		return a.createAppFromTemplate(ctx, scc, system)
   427  	}
   428  
   429  	if err != nil {
   430  		log.C(ctx).Warn(errors.Wrapf(err, "while getting Application"))
   431  		return false
   432  	}
   433  
   434  	return a.updateSystem(ctx, system, app)
   435  }
   436  
   437  func (a *Handler) createAppFromTemplate(ctx context.Context, scc nsmodel.SCC, system nsmodel.System) bool {
   438  	appInput, err := a.prepareAppInput(ctx, scc, system)
   439  	if err != nil {
   440  		log.C(ctx).Warn(errors.Wrapf(err, "while creating Application"))
   441  		return false
   442  	}
   443  
   444  	if _, err := a.appSvc.CreateFromTemplate(ctx, *appInput, str.Ptr(system.TemplateID)); err != nil {
   445  		log.C(ctx).Warn(errors.Wrapf(err, "while creating Application"))
   446  		return false
   447  	}
   448  	return true
   449  }
   450  
   451  func (a *Handler) updateSystem(ctx context.Context, system nsmodel.System, app *model.Application) bool {
   452  	if err := a.appSvc.Update(ctx, app.ID, nsmodel.ToAppUpdateInput(system)); err != nil {
   453  		log.C(ctx).Warn(errors.Wrapf(err, "while updating Application with id %s", app.ID))
   454  		return false
   455  	}
   456  
   457  	if err := a.appSvc.SetLabel(ctx, &model.LabelInput{
   458  		Key:        "systemType",
   459  		Value:      system.SystemType,
   460  		ObjectID:   app.ID,
   461  		ObjectType: model.ApplicationLabelableObject,
   462  	}); err != nil {
   463  		log.C(ctx).Warn(errors.Wrapf(err, "while setting 'systemType' label for Application with id %s", app.ID))
   464  		return false
   465  	}
   466  
   467  	if err := a.appSvc.SetLabel(ctx, &model.LabelInput{
   468  		Key:        "systemProtocol",
   469  		Value:      system.Protocol,
   470  		ObjectID:   app.ID,
   471  		ObjectType: model.ApplicationLabelableObject,
   472  	}); err != nil {
   473  		log.C(ctx).Warn(errors.Wrapf(err, "while setting 'systemProtocol' label for Application with id %s", app.ID))
   474  		return false
   475  	}
   476  
   477  	return true
   478  }
   479  
   480  func (a *Handler) markAsUnreachable(ctx context.Context, scc nsmodel.SCC) bool {
   481  	apps, ok := a.listAppsByScc(ctx, scc.ExternalSubaccountID, scc.LocationID)
   482  	if !ok {
   483  		return false
   484  	}
   485  
   486  	success := true
   487  	unreachable := filterUnreachable(apps, scc.ExposedSystems)
   488  	for _, system := range unreachable {
   489  		success = a.markSystemAsUnreachable(ctx, system) && success
   490  	}
   491  	return success
   492  }
   493  
   494  func (a *Handler) markSystemAsUnreachable(ctx context.Context, system *model.Application) bool {
   495  	tx, err := a.transact.Begin()
   496  	if err != nil {
   497  		log.C(ctx).Warn(errors.Wrapf(err, "while openning transaction"))
   498  		return false
   499  	}
   500  	defer a.transact.RollbackUnlessCommitted(ctx, tx)
   501  
   502  	ctxWithTransaction := persistence.SaveToContext(ctx, tx)
   503  
   504  	if err := a.appSvc.Update(ctxWithTransaction, system.ID, model.ApplicationUpdateInput{SystemStatus: str.Ptr("unreachable")}); err != nil {
   505  		log.C(ctx).Warn(errors.Wrapf(err, "while marking application with id %s as unreachable", system.ID))
   506  		return false
   507  	}
   508  
   509  	if err := tx.Commit(); err != nil {
   510  		log.C(ctx).Warn(errors.Wrapf(err, "while committing transaction"))
   511  		return false
   512  	}
   513  
   514  	return true
   515  }
   516  
   517  func (a *Handler) listAppsByScc(ctx context.Context, subaccount, locationID string) ([]*model.ApplicationWithLabel, bool) {
   518  	tx, err := a.transact.Begin()
   519  	if err != nil {
   520  		log.C(ctx).Warn(errors.Wrapf(err, "while openning transaction"))
   521  		return nil, false
   522  	}
   523  	defer a.transact.RollbackUnlessCommitted(ctx, tx)
   524  
   525  	ctxWithTransaction := persistence.SaveToContext(ctx, tx)
   526  	apps, err := a.appSvc.ListBySCC(ctxWithTransaction, labelfilter.NewForKeyWithQuery("scc", fmt.Sprintf("{\"LocationID\":\"%s\", \"Subaccount\":\"%s\"}", locationID, subaccount)))
   527  	if err != nil {
   528  		log.C(ctx).Warn(errors.Wrapf(err, "while listing all applications for scc with subaccount %s and location id %s", subaccount, locationID))
   529  		return nil, false
   530  	}
   531  
   532  	if err := tx.Commit(); err != nil {
   533  		log.C(ctx).Warn(errors.Wrapf(err, "while committing transaction"))
   534  		return nil, false
   535  	}
   536  
   537  	return apps, true
   538  }
   539  
   540  func filterUnreachable(apps []*model.ApplicationWithLabel, systems []nsmodel.System) []*model.Application {
   541  	hostToSystem := make(map[string]interface{}, len(systems))
   542  
   543  	for _, s := range systems {
   544  		hostToSystem[s.Host] = struct{}{}
   545  	}
   546  	unreachable := make([]*model.Application, 0, len(apps))
   547  
   548  	for _, a := range apps {
   549  		result := a.SccLabel.Value.(map[string]interface{})["Host"]
   550  		_, ok := hostToSystem[result.(string)]
   551  		if !ok {
   552  			unreachable = append(unreachable, a.App)
   553  		}
   554  	}
   555  	return unreachable
   556  }
   557  
   558  func difference(a, b []*model.SccMetadata) (diff []*model.SccMetadata) {
   559  	m := make(map[model.SccMetadata]bool)
   560  
   561  	for _, item := range b {
   562  		m[*item] = true
   563  	}
   564  
   565  	for _, item := range a {
   566  		if _, ok := m[*item]; !ok {
   567  			diff = append(diff, item)
   568  		}
   569  	}
   570  	return
   571  }
   572  
   573  func addErrorDetailsMsg(details *[]httputil.Detail, scc *nsmodel.SCC, message string) {
   574  	*details = append(*details, httputil.Detail{
   575  		Message:    message,
   576  		Subaccount: scc.ExternalSubaccountID,
   577  		LocationID: scc.LocationID,
   578  	})
   579  }
   580  
   581  func mapExternalToInternal(ctx context.Context, tenants []*model.BusinessTenantMapping, sccs []*nsmodel.SCC) {
   582  	externalToInternalTenants := make(map[string]*model.BusinessTenantMapping, len(tenants))
   583  	for _, t := range tenants {
   584  		externalToInternalTenants[t.ExternalTenant] = t
   585  	}
   586  
   587  	for _, scc := range sccs {
   588  		t, exist := externalToInternalTenants[scc.ExternalSubaccountID]
   589  		if !exist {
   590  			continue
   591  		}
   592  		if t.Type == "subaccount" {
   593  			scc.InternalSubaccountID = t.ID
   594  		} else {
   595  			log.C(ctx).Warnf("Got tenant with id: %s which is not a subaccount", t.ID)
   596  			scc.InternalSubaccountID = notSubaccountMarker
   597  		}
   598  	}
   599  }
   600  
   601  func isNotFoundError(err error) bool {
   602  	return strings.Contains(err.Error(), "Object not found")
   603  }
   604  
   605  func filterSccsByInternalID(ctx context.Context, sccs []*nsmodel.SCC, details *[]httputil.Detail) []*nsmodel.SCC {
   606  	filteredSccs := make([]*nsmodel.SCC, 0, len(sccs))
   607  	for _, scc := range sccs {
   608  		if scc.InternalSubaccountID == "" {
   609  			log.C(ctx).Warnf("Got SCC with external subaccount id: %s which has not associated internal tenant id", scc.ExternalSubaccountID)
   610  			addErrorDetailsMsg(details, scc, "Subaccount not found")
   611  		} else if scc.InternalSubaccountID == notSubaccountMarker {
   612  			addErrorDetailsMsg(details, scc, "Provided id is not subaccount")
   613  		} else {
   614  			filteredSccs = append(filteredSccs, scc)
   615  		}
   616  	}
   617  	return filteredSccs
   618  }