github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/orchestration/handlers/orchestration_handler.go (about)

     1  package handlers
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	apiErrors "k8s.io/apimachinery/pkg/api/errors"
     8  
     9  	"github.com/kyma-project/kyma-environment-broker/common/pagination"
    10  
    11  	"github.com/kyma-project/kyma-environment-broker/internal/httputil"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/process"
    13  
    14  	internalError "github.com/kyma-project/kyma-environment-broker/internal/error"
    15  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    16  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dberr"
    17  	"github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel"
    18  
    19  	"github.com/gorilla/mux"
    20  	commonOrchestration "github.com/kyma-project/kyma-environment-broker/common/orchestration"
    21  
    22  	"github.com/sirupsen/logrus"
    23  )
    24  
    25  type orchestrationHandler struct {
    26  	orchestrations storage.Orchestrations
    27  	operations     storage.Operations
    28  	runtimeStates  storage.RuntimeStates
    29  
    30  	converter Converter
    31  	log       logrus.FieldLogger
    32  
    33  	canceler       *Canceler
    34  	kymaRetryer    *kymaRetryer
    35  	clusterRetryer *clusterRetryer
    36  
    37  	defaultMaxPage int
    38  }
    39  
    40  // NewOrchestrationStatusHandler exposes data about orchestrations and allows to manage them
    41  func NewOrchestrationStatusHandler(operations storage.Operations,
    42  	orchestrations storage.Orchestrations,
    43  	runtimeStates storage.RuntimeStates,
    44  	kymaQueue *process.Queue,
    45  	clusterQueue *process.Queue,
    46  	defaultMaxPage int,
    47  	log logrus.FieldLogger) *orchestrationHandler {
    48  	return &orchestrationHandler{
    49  		operations:     operations,
    50  		orchestrations: orchestrations,
    51  		runtimeStates:  runtimeStates,
    52  		log:            log,
    53  		defaultMaxPage: defaultMaxPage,
    54  		converter:      Converter{},
    55  		canceler:       NewCanceler(orchestrations, log),
    56  		kymaRetryer:    NewKymaRetryer(orchestrations, operations, kymaQueue, log),
    57  		clusterRetryer: NewClusterRetryer(orchestrations, operations, clusterQueue, log),
    58  	}
    59  }
    60  
    61  func (h *orchestrationHandler) AttachRoutes(router *mux.Router) {
    62  	router.HandleFunc("/orchestrations", h.listOrchestration).Methods(http.MethodGet)
    63  	router.HandleFunc("/orchestrations/{orchestration_id}", h.getOrchestration).Methods(http.MethodGet)
    64  	router.HandleFunc("/orchestrations/{orchestration_id}/cancel", h.cancelOrchestrationByID).Methods(http.MethodPut)
    65  	router.HandleFunc("/orchestrations/{orchestration_id}/operations", h.listOperations).Methods(http.MethodGet)
    66  	router.HandleFunc("/orchestrations/{orchestration_id}/operations/{operation_id}", h.getOperation).Methods(http.MethodGet)
    67  	router.HandleFunc("/orchestrations/{orchestration_id}/retry", h.retryOrchestrationByID).Methods(http.MethodPost)
    68  }
    69  
    70  func (h *orchestrationHandler) getOrchestration(w http.ResponseWriter, r *http.Request) {
    71  	orchestrationID := mux.Vars(r)["orchestration_id"]
    72  
    73  	o, err := h.orchestrations.GetByID(orchestrationID)
    74  	if err != nil {
    75  		h.log.Errorf("while getting orchestration %s: %v", orchestrationID, err)
    76  		httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while getting orchestration %s: %w", orchestrationID, err))
    77  		return
    78  	}
    79  
    80  	stats, err := h.operations.GetOperationStatsForOrchestration(orchestrationID)
    81  	if err != nil {
    82  		h.log.Errorf("while getting orchestration %s operation statistics: %v", orchestrationID, err)
    83  		httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while getting orchestration %s operation statistics: %w", orchestrationID, err))
    84  		return
    85  	}
    86  
    87  	response, err := h.converter.OrchestrationToDTO(o, stats)
    88  	if err != nil {
    89  		h.log.Errorf("while converting orchestration: %v", err)
    90  		httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while converting orchestration: %w", err))
    91  		return
    92  	}
    93  
    94  	httputil.WriteResponse(w, http.StatusOK, response)
    95  }
    96  
    97  func (h *orchestrationHandler) cancelOrchestrationByID(w http.ResponseWriter, r *http.Request) {
    98  	orchestrationID := mux.Vars(r)["orchestration_id"]
    99  
   100  	err := h.canceler.CancelForID(orchestrationID)
   101  	if err != nil {
   102  		h.log.Errorf("while canceling orchestration %s: %v", orchestrationID, err)
   103  		httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while canceling orchestration %s: %w", orchestrationID, err))
   104  		return
   105  	}
   106  
   107  	response := commonOrchestration.UpgradeResponse{OrchestrationID: orchestrationID}
   108  
   109  	httputil.WriteResponse(w, http.StatusOK, response)
   110  }
   111  
   112  func (h *orchestrationHandler) retryOrchestrationByID(w http.ResponseWriter, r *http.Request) {
   113  	contentType := r.Header.Get("Content-type")
   114  	if contentType != "application/x-www-form-urlencoded" {
   115  		h.log.Errorf("invalide content type %s for retrying orchestration", contentType)
   116  		httputil.WriteErrorResponse(w, http.StatusUnsupportedMediaType, fmt.Errorf("invalide content type %s for retrying orchestration", contentType))
   117  		return
   118  	}
   119  
   120  	orchestrationID := mux.Vars(r)["orchestration_id"]
   121  	operationIDs := []string{}
   122  	var immediateID string
   123  
   124  	if r.Body != nil {
   125  		if err := r.ParseForm(); err != nil {
   126  			h.log.Errorf("cannot parse form while retrying orchestration: %s: %v", orchestrationID, err)
   127  			httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("cannot parse form while retrying orchestration: %s: %w", orchestrationID, err))
   128  			return
   129  		}
   130  
   131  		operationIDs = r.Form["operation-id"]
   132  		immediateID = r.FormValue("immediate")
   133  	}
   134  
   135  	o, err := h.orchestrations.GetByID(orchestrationID)
   136  	if err != nil {
   137  		h.log.Errorf("while retrying orchestration %s: %v", orchestrationID, err)
   138  		httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while retrying orchestration %s: %w", orchestrationID, err))
   139  		return
   140  	}
   141  
   142  	filter := dbmodel.OperationFilter{
   143  		States: []string{commonOrchestration.Failed},
   144  	}
   145  
   146  	var response commonOrchestration.RetryResponse
   147  	switch o.Type {
   148  	case commonOrchestration.UpgradeKymaOrchestration:
   149  		allOps, _, _, err := h.operations.ListUpgradeKymaOperationsByOrchestrationID(o.OrchestrationID, filter)
   150  		if err != nil {
   151  			h.log.Errorf("while getting operations: %v", err)
   152  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while getting operations: %w", err))
   153  			return
   154  		}
   155  
   156  		response, err = h.kymaRetryer.orchestrationRetry(o, allOps, operationIDs, immediateID)
   157  		if err != nil {
   158  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, err)
   159  			return
   160  		}
   161  
   162  	case commonOrchestration.UpgradeClusterOrchestration:
   163  		allOps, _, _, err := h.operations.ListUpgradeClusterOperationsByOrchestrationID(o.OrchestrationID, filter)
   164  		if err != nil {
   165  			h.log.Errorf("while getting operations: %v", err)
   166  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while getting operations: %w", err))
   167  			return
   168  		}
   169  
   170  		response, err = h.clusterRetryer.orchestrationRetry(o, allOps, operationIDs)
   171  		if err != nil {
   172  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, err)
   173  			return
   174  		}
   175  
   176  	default:
   177  		httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("unsupported orchestration type: %s", o.Type))
   178  		return
   179  	}
   180  
   181  	httputil.WriteResponse(w, http.StatusAccepted, response)
   182  }
   183  
   184  func (h *orchestrationHandler) listOrchestration(w http.ResponseWriter, r *http.Request) {
   185  	pageSize, page, err := pagination.ExtractPaginationConfigFromRequest(r, h.defaultMaxPage)
   186  	if err != nil {
   187  		httputil.WriteErrorResponse(w, http.StatusBadRequest, fmt.Errorf("while getting query parameters: %w", err))
   188  		return
   189  	}
   190  	query := r.URL.Query()
   191  	filter := dbmodel.OrchestrationFilter{
   192  		Page:     page,
   193  		PageSize: pageSize,
   194  		// For optional filters, zero value (nil) is ok if not supplied
   195  		States: query[commonOrchestration.StateParam],
   196  	}
   197  
   198  	orchestrations, count, totalCount, err := h.orchestrations.List(filter)
   199  	if err != nil {
   200  		h.log.Errorf("while getting orchestrations: %v", err)
   201  		httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while getting orchestrations: %w", err))
   202  		return
   203  	}
   204  
   205  	response, err := h.converter.OrchestrationListToDTO(orchestrations, count, totalCount)
   206  	if err != nil {
   207  		h.log.Errorf("while converting orchestrations: %v", err)
   208  		httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while converting orchestrations: %w", err))
   209  		return
   210  	}
   211  
   212  	httputil.WriteResponse(w, http.StatusOK, response)
   213  }
   214  
   215  func (h *orchestrationHandler) listOperations(w http.ResponseWriter, r *http.Request) {
   216  	orchestrationID := mux.Vars(r)["orchestration_id"]
   217  	pageSize, page, err := pagination.ExtractPaginationConfigFromRequest(r, h.defaultMaxPage)
   218  	if err != nil {
   219  		httputil.WriteErrorResponse(w, http.StatusBadRequest, fmt.Errorf("while getting query parameters: %w", err))
   220  		return
   221  	}
   222  	query := r.URL.Query()
   223  	filter := dbmodel.OperationFilter{
   224  		Page:     page,
   225  		PageSize: pageSize,
   226  		// For optional filters, zero value (nil) is ok if not supplied
   227  		States: query[commonOrchestration.StateParam],
   228  	}
   229  
   230  	o, err := h.orchestrations.GetByID(orchestrationID)
   231  	if err != nil {
   232  		h.log.Errorf("while getting orchestration %s: %v", orchestrationID, err)
   233  		httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while getting orchestration %s: %w", orchestrationID, err))
   234  		return
   235  	}
   236  
   237  	var response commonOrchestration.OperationResponseList
   238  	switch o.Type {
   239  	case commonOrchestration.UpgradeKymaOrchestration:
   240  		operations, count, totalCount, err := h.operations.ListUpgradeKymaOperationsByOrchestrationID(orchestrationID, filter)
   241  		if err != nil {
   242  			h.log.Errorf("while getting operations: %v", err)
   243  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while getting operations: %w", err))
   244  			return
   245  		}
   246  		response, err = h.converter.UpgradeKymaOperationListToDTO(operations, count, totalCount)
   247  		if err != nil {
   248  			h.log.Errorf("while converting operations: %v", err)
   249  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while converting operations: %w", err))
   250  			return
   251  		}
   252  
   253  	case commonOrchestration.UpgradeClusterOrchestration:
   254  		operations, count, totalCount, err := h.operations.ListUpgradeClusterOperationsByOrchestrationID(orchestrationID, filter)
   255  		if err != nil {
   256  			h.log.Errorf("while getting operations: %v", err)
   257  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while getting operations: %w", err))
   258  			return
   259  		}
   260  		response, err = h.converter.UpgradeClusterOperationListToDTO(operations, count, totalCount)
   261  		if err != nil {
   262  			h.log.Errorf("while converting operations: %v", err)
   263  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while converting operations: %w", err))
   264  			return
   265  		}
   266  
   267  	default:
   268  		httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("unsupported orchestration type: %s", o.Type))
   269  		return
   270  	}
   271  
   272  	httputil.WriteResponse(w, http.StatusOK, response)
   273  }
   274  
   275  func (h *orchestrationHandler) getOperation(w http.ResponseWriter, r *http.Request) {
   276  	orchestrationID := mux.Vars(r)["orchestration_id"]
   277  	operationID := mux.Vars(r)["operation_id"]
   278  
   279  	o, err := h.orchestrations.GetByID(orchestrationID)
   280  	if err != nil {
   281  		h.log.Errorf("while getting orchestration %s: %v", orchestrationID, err)
   282  		httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while getting orchestration %s: %w", orchestrationID, err))
   283  		return
   284  	}
   285  
   286  	upgradeState, err := h.runtimeStates.GetByOperationID(operationID)
   287  	if err != nil && !dberr.IsNotFound(err) {
   288  		h.log.Errorf("while getting runtime state for upgrade operation %s: %v", operationID, err)
   289  	}
   290  
   291  	kymaConfig := upgradeState.GetKymaConfig()
   292  
   293  	var response commonOrchestration.OperationDetailResponse
   294  	switch o.Type {
   295  	case commonOrchestration.UpgradeKymaOrchestration:
   296  		operation, err := h.operations.GetUpgradeKymaOperationByID(operationID)
   297  		if err != nil {
   298  			h.log.Errorf("while getting upgrade operation %s: %v", operationID, err)
   299  			httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while getting upgrade operation %s: %w", operationID, err))
   300  			return
   301  		}
   302  
   303  		response, err = h.converter.UpgradeKymaOperationToDetailDTO(*operation, &kymaConfig)
   304  		if err != nil {
   305  			h.log.Errorf("while converting operation: %v", err)
   306  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while converting operation: %w", err))
   307  			return
   308  		}
   309  
   310  	case commonOrchestration.UpgradeClusterOrchestration:
   311  		operation, err := h.operations.GetUpgradeClusterOperationByID(operationID)
   312  		if err != nil {
   313  			h.log.Errorf("while getting upgrade operation %s: %v", operationID, err)
   314  			httputil.WriteErrorResponse(w, h.resolveErrorStatus(err), fmt.Errorf("while getting upgrade operation %s: %w", operationID, err))
   315  			return
   316  		}
   317  
   318  		response, err = h.converter.UpgradeClusterOperationToDetailDTO(*operation, &upgradeState.ClusterConfig)
   319  		if err != nil {
   320  			h.log.Errorf("while converting operation: %v", err)
   321  			httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while converting operation: %w", err))
   322  			return
   323  		}
   324  
   325  	default:
   326  		httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("unsupported orchestration type: %s", o.Type))
   327  		return
   328  	}
   329  
   330  	httputil.WriteResponse(w, http.StatusOK, response)
   331  }
   332  
   333  func (h *orchestrationHandler) resolveErrorStatus(err error) int {
   334  	cause := internalError.UnwrapAll(err)
   335  	switch { //TODO: rethink to validate error type
   336  	case dberr.IsNotFound(cause):
   337  		return http.StatusNotFound
   338  	case apiErrors.IsBadRequest(cause):
   339  		return http.StatusBadRequest
   340  	default:
   341  		return http.StatusInternalServerError
   342  	}
   343  }