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 }