github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/appinfo/runtime_info.go (about)

     1  package appinfo
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"time"
     7  
     8  	"github.com/kyma-project/kyma-environment-broker/internal"
     9  	"github.com/kyma-project/kyma-environment-broker/internal/broker"
    10  	"github.com/kyma-project/kyma-environment-broker/internal/httputil"
    11  	"github.com/kyma-project/kyma-environment-broker/internal/ptr"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    13  	"github.com/kyma-project/kyma-environment-broker/internal/storage/predicate"
    14  )
    15  
    16  //go:generate mockery --name=InstanceFinder --output=automock --outpkg=automock --case=underscore
    17  
    18  type (
    19  	InstanceFinder interface {
    20  		FindAllJoinedWithOperations(prct ...predicate.Predicate) ([]internal.InstanceWithOperation, error)
    21  	}
    22  
    23  	LastOperationFinder interface {
    24  		GetLastOperation(instanceID string) (*internal.Operation, error)
    25  	}
    26  
    27  	ResponseWriter interface {
    28  		InternalServerError(rw http.ResponseWriter, r *http.Request, err error, context string)
    29  	}
    30  )
    31  
    32  type RuntimeInfoHandler struct {
    33  	instanceFinder          InstanceFinder
    34  	lastOperationFinder     LastOperationFinder
    35  	respWriter              ResponseWriter
    36  	plansConfig             broker.PlansConfig
    37  	defaultSubaccountRegion string
    38  }
    39  
    40  func NewRuntimeInfoHandler(instanceFinder InstanceFinder, lastOpFinder LastOperationFinder, plansConfig broker.PlansConfig, region string, respWriter ResponseWriter) *RuntimeInfoHandler {
    41  	return &RuntimeInfoHandler{
    42  		instanceFinder:          instanceFinder,
    43  		lastOperationFinder:     lastOpFinder,
    44  		respWriter:              respWriter,
    45  		plansConfig:             plansConfig,
    46  		defaultSubaccountRegion: region,
    47  	}
    48  }
    49  
    50  func (h *RuntimeInfoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    51  	allInstances, err := h.instanceFinder.FindAllJoinedWithOperations(predicate.SortAscByCreatedAt())
    52  	if err != nil {
    53  		h.respWriter.InternalServerError(w, r, err, "while fetching all instances")
    54  		return
    55  	}
    56  
    57  	dto, err := h.mapToDTO(allInstances)
    58  	if err != nil {
    59  		h.respWriter.InternalServerError(w, r, err, "while mapping instance model to dto")
    60  	}
    61  
    62  	if err := httputil.JSONEncode(w, dto); err != nil {
    63  		h.respWriter.InternalServerError(w, r, err, "while encoding response to JSON")
    64  		return
    65  	}
    66  }
    67  
    68  func (h *RuntimeInfoHandler) mapToDTO(instances []internal.InstanceWithOperation) ([]*RuntimeDTO, error) {
    69  	items := make([]*RuntimeDTO, 0, len(instances))
    70  	indexer := map[string]int{}
    71  	firstProvOpCreationTimePerInstance := map[string]time.Time{}
    72  	lastDeprovOpCreationTimesPerInstance := map[string]time.Time{}
    73  
    74  	for _, inst := range instances {
    75  		region := h.getRegionOrDefault(inst)
    76  
    77  		idx, found := indexer[inst.InstanceID]
    78  		if !found {
    79  			// Determine runtime modifiedAt timestamp based on the last operation of the runtime
    80  			lastOp, err := h.lastOperationFinder.GetLastOperation(inst.InstanceID)
    81  			if err != nil && !dberr.IsNotFound(err) {
    82  				return nil, fmt.Errorf("while getting last operation for instance %s: %w", inst.InstanceID, err)
    83  			}
    84  			updatedAt := inst.UpdatedAt
    85  			if lastOp != nil {
    86  				updatedAt = lastOp.UpdatedAt
    87  			}
    88  			items = append(items, &RuntimeDTO{
    89  				RuntimeID:         inst.RuntimeID,
    90  				SubAccountID:      inst.SubAccountID,
    91  				SubAccountRegion:  region,
    92  				ServiceInstanceID: inst.InstanceID,
    93  				GlobalAccountID:   inst.GlobalAccountID,
    94  				ServiceClassID:    inst.ServiceID,
    95  				ServiceClassName:  svcNameOrDefault(inst),
    96  				ServicePlanID:     inst.ServicePlanID,
    97  				ServicePlanName:   h.planNameOrDefault(inst),
    98  				Status: StatusDTO{
    99  					CreatedAt: getIfNotZero(inst.CreatedAt),
   100  					UpdatedAt: getIfNotZero(updatedAt),
   101  					DeletedAt: getIfNotZero(inst.DeletedAt),
   102  				},
   103  			})
   104  			idx = len(items) - 1
   105  			indexer[inst.InstanceID] = idx
   106  		}
   107  
   108  		// TODO: consider to merge the rows in sql query
   109  		opStatus := &OperationStatusDTO{
   110  			State:       inst.State.String,
   111  			Description: inst.Description.String,
   112  		}
   113  		switch internal.OperationType(inst.Type.String) {
   114  		case internal.OperationTypeProvision:
   115  			opCreationTime, exists := firstProvOpCreationTimePerInstance[inst.InstanceID]
   116  			if !exists {
   117  				firstProvOpCreationTimePerInstance[inst.InstanceID] = inst.OpCreatedAt
   118  				items[idx].Status.Provisioning = opStatus
   119  			}
   120  			if inst.OpCreatedAt.Before(opCreationTime) {
   121  				firstProvOpCreationTimePerInstance[inst.InstanceID] = inst.OpCreatedAt
   122  				items[idx].Status.Provisioning = opStatus
   123  			}
   124  		case internal.OperationTypeDeprovision:
   125  			if !inst.IsSuspensionOp {
   126  				opCreationTime, exists := lastDeprovOpCreationTimesPerInstance[inst.InstanceID]
   127  				if !exists {
   128  					lastDeprovOpCreationTimesPerInstance[inst.InstanceID] = inst.OpCreatedAt
   129  					items[idx].Status.Deprovisioning = opStatus
   130  				}
   131  				if inst.OpCreatedAt.After(opCreationTime) {
   132  					lastDeprovOpCreationTimesPerInstance[inst.InstanceID] = inst.OpCreatedAt
   133  					items[idx].Status.Deprovisioning = opStatus
   134  				}
   135  			}
   136  		}
   137  	}
   138  
   139  	return items, nil
   140  }
   141  
   142  func (h *RuntimeInfoHandler) getRegionOrDefault(inst internal.InstanceWithOperation) string {
   143  	if inst.Parameters.PlatformRegion == "" {
   144  		return h.defaultSubaccountRegion
   145  	}
   146  	return inst.Parameters.PlatformRegion
   147  }
   148  
   149  func svcNameOrDefault(inst internal.InstanceWithOperation) string {
   150  	if inst.ServiceName != "" {
   151  		return inst.ServiceName
   152  	}
   153  	return broker.KymaServiceName
   154  }
   155  
   156  func (h *RuntimeInfoHandler) planNameOrDefault(inst internal.InstanceWithOperation) string {
   157  	if inst.ServicePlanName != "" {
   158  		return inst.ServicePlanName
   159  	}
   160  	return broker.Plans(h.plansConfig, "", false, false, true, true)[inst.ServicePlanID].Name
   161  }
   162  
   163  func getIfNotZero(in time.Time) *time.Time {
   164  	if in.IsZero() {
   165  		return nil
   166  	}
   167  	return ptr.Time(in)
   168  }