github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/runtime/handler.go (about) 1 package runtime 2 3 import ( 4 "fmt" 5 "net/http" 6 "time" 7 8 "github.com/kyma-project/kyma-environment-broker/internal/ptr" 9 "github.com/pivotal-cf/brokerapi/v8/domain" 10 "golang.org/x/exp/slices" 11 12 "github.com/gorilla/mux" 13 "github.com/kyma-project/kyma-environment-broker/common/orchestration" 14 "github.com/kyma-project/kyma-environment-broker/common/pagination" 15 pkg "github.com/kyma-project/kyma-environment-broker/common/runtime" 16 "github.com/kyma-project/kyma-environment-broker/internal" 17 "github.com/kyma-project/kyma-environment-broker/internal/httputil" 18 "github.com/kyma-project/kyma-environment-broker/internal/storage" 19 "github.com/kyma-project/kyma-environment-broker/internal/storage/dberr" 20 "github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel" 21 ) 22 23 const numberOfUpgradeOperationsToReturn = 2 24 25 type Handler struct { 26 instancesDb storage.Instances 27 operationsDb storage.Operations 28 runtimeStatesDb storage.RuntimeStates 29 converter Converter 30 31 defaultMaxPage int 32 } 33 34 func NewHandler(instanceDb storage.Instances, operationDb storage.Operations, runtimeStatesDb storage.RuntimeStates, defaultMaxPage int, defaultRequestRegion string) *Handler { 35 return &Handler{ 36 instancesDb: instanceDb, 37 operationsDb: operationDb, 38 runtimeStatesDb: runtimeStatesDb, 39 converter: NewConverter(defaultRequestRegion), 40 defaultMaxPage: defaultMaxPage, 41 } 42 } 43 44 func (h *Handler) AttachRoutes(router *mux.Router) { 45 router.HandleFunc("/runtimes", h.getRuntimes) 46 } 47 48 func findLastDeprovisioning(operations []internal.Operation) internal.Operation { 49 for i := len(operations) - 1; i > 0; i-- { 50 o := operations[i] 51 if o.Type != internal.OperationTypeDeprovision { 52 continue 53 } 54 if o.State != domain.Succeeded { 55 continue 56 } 57 return o 58 } 59 return operations[len(operations)-1] 60 } 61 62 func recreateInstances(operations []internal.Operation) []internal.Instance { 63 byInstance := make(map[string][]internal.Operation) 64 for _, o := range operations { 65 byInstance[o.InstanceID] = append(byInstance[o.InstanceID], o) 66 } 67 var instances []internal.Instance 68 for id, op := range byInstance { 69 o := op[0] 70 last := findLastDeprovisioning(op) 71 instances = append(instances, internal.Instance{ 72 InstanceID: id, 73 GlobalAccountID: o.GlobalAccountID, 74 SubAccountID: o.SubAccountID, 75 RuntimeID: o.RuntimeID, 76 CreatedAt: o.CreatedAt, 77 ServicePlanID: o.ProvisioningParameters.PlanID, 78 DeletedAt: last.UpdatedAt, 79 InstanceDetails: last.InstanceDetails, 80 Parameters: last.ProvisioningParameters, 81 }) 82 } 83 return instances 84 } 85 86 func unionInstances(sets ...[]internal.Instance) (union []internal.Instance) { 87 m := make(map[string]internal.Instance) 88 for _, s := range sets { 89 for _, i := range s { 90 if _, exists := m[i.InstanceID]; !exists { 91 m[i.InstanceID] = i 92 } 93 } 94 } 95 for _, i := range m { 96 union = append(union, i) 97 } 98 return 99 } 100 101 func (h *Handler) listInstances(filter dbmodel.InstanceFilter) ([]internal.Instance, int, int, error) { 102 if slices.Contains(filter.States, dbmodel.InstanceDeprovisioned) { 103 // try to list instances where deletion didn't finish successfully 104 // entry in the Instances table still exists but has deletion timestamp and contains list of incomplete steps 105 deletionAttempted := true 106 filter.DeletionAttempted = &deletionAttempted 107 instances, instancesCount, instancesTotalCount, _ := h.instancesDb.List(filter) 108 109 // try to recreate instances from the operations table where entry in the instances table is gone 110 opFilter := dbmodel.OperationFilter{} 111 opFilter.InstanceFilter = &filter 112 opFilter.Page = filter.Page 113 opFilter.PageSize = filter.PageSize 114 operations, _, _, err := h.operationsDb.ListOperations(opFilter) 115 if err != nil { 116 return instances, instancesCount, instancesTotalCount, err 117 } 118 instancesFromOperations := recreateInstances(operations) 119 120 // return union of both sets of instances 121 instancesUnion := unionInstances(instances, instancesFromOperations) 122 count := len(instancesFromOperations) 123 return instancesUnion, count + instancesCount, count + instancesTotalCount, nil 124 } 125 return h.instancesDb.List(filter) 126 } 127 128 func (h *Handler) getRuntimes(w http.ResponseWriter, req *http.Request) { 129 toReturn := make([]pkg.RuntimeDTO, 0) 130 131 pageSize, page, err := pagination.ExtractPaginationConfigFromRequest(req, h.defaultMaxPage) 132 if err != nil { 133 httputil.WriteErrorResponse(w, http.StatusBadRequest, fmt.Errorf("while getting query parameters: %w", err)) 134 return 135 } 136 filter := h.getFilters(req) 137 filter.PageSize = pageSize 138 filter.Page = page 139 opDetail := getOpDetail(req) 140 kymaConfig := getBoolParam(pkg.KymaConfigParam, req) 141 clusterConfig := getBoolParam(pkg.ClusterConfigParam, req) 142 143 instances, count, totalCount, err := h.listInstances(filter) 144 if err != nil { 145 httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while fetching instances: %w", err)) 146 return 147 } 148 149 for _, instance := range instances { 150 dto, err := h.converter.NewDTO(instance) 151 if err != nil { 152 httputil.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Errorf("while converting instance to DTO: %w", err)) 153 return 154 } 155 156 switch opDetail { 157 case pkg.AllOperation: 158 err = h.setRuntimeAllOperations(instance, &dto) 159 case pkg.LastOperation: 160 err = h.setRuntimeLastOperation(instance, &dto) 161 } 162 if err != nil { 163 httputil.WriteErrorResponse(w, http.StatusInternalServerError, err) 164 return 165 } 166 167 err = h.determineStatusModifiedAt(&dto) 168 if err != nil { 169 httputil.WriteErrorResponse(w, http.StatusInternalServerError, err) 170 return 171 } 172 err = h.setRuntimeOptionalAttributes(instance, &dto, kymaConfig, clusterConfig) 173 if err != nil { 174 httputil.WriteErrorResponse(w, http.StatusInternalServerError, err) 175 return 176 } 177 178 toReturn = append(toReturn, dto) 179 } 180 181 runtimePage := pkg.RuntimesPage{ 182 Data: toReturn, 183 Count: count, 184 TotalCount: totalCount, 185 } 186 httputil.WriteResponse(w, http.StatusOK, runtimePage) 187 } 188 189 func (h *Handler) takeLastNonDryRunOperations(oprs []internal.UpgradeKymaOperation) ([]internal.UpgradeKymaOperation, int) { 190 toReturn := make([]internal.UpgradeKymaOperation, 0) 191 totalCount := 0 192 for _, op := range oprs { 193 if op.DryRun { 194 continue 195 } 196 if len(toReturn) < numberOfUpgradeOperationsToReturn { 197 toReturn = append(toReturn, op) 198 } 199 totalCount = totalCount + 1 200 } 201 return toReturn, totalCount 202 } 203 204 func (h *Handler) takeLastNonDryRunClusterOperations(oprs []internal.UpgradeClusterOperation) ([]internal.UpgradeClusterOperation, int) { 205 toReturn := make([]internal.UpgradeClusterOperation, 0) 206 totalCount := 0 207 for _, op := range oprs { 208 if op.DryRun { 209 continue 210 } 211 if len(toReturn) < numberOfUpgradeOperationsToReturn { 212 toReturn = append(toReturn, op) 213 } 214 totalCount = totalCount + 1 215 } 216 return toReturn, totalCount 217 } 218 219 func (h *Handler) determineStatusModifiedAt(dto *pkg.RuntimeDTO) error { 220 // Determine runtime modifiedAt timestamp based on the last operation of the runtime 221 last, err := h.operationsDb.GetLastOperation(dto.InstanceID) 222 if err != nil && !dberr.IsNotFound(err) { 223 return fmt.Errorf("while fetching last operation for instance %s: %w", dto.InstanceID, err) 224 } 225 if last != nil { 226 dto.Status.ModifiedAt = last.UpdatedAt 227 } 228 return nil 229 } 230 231 func (h *Handler) setRuntimeAllOperations(instance internal.Instance, dto *pkg.RuntimeDTO) error { 232 provOprs, err := h.operationsDb.ListProvisioningOperationsByInstanceID(instance.InstanceID) 233 if err != nil && !dberr.IsNotFound(err) { 234 return fmt.Errorf("while fetching provisioning operations list for instance %s: %w", instance.InstanceID, err) 235 } 236 if len(provOprs) != 0 { 237 firstProvOp := &provOprs[len(provOprs)-1] 238 lastProvOp := provOprs[0] 239 // Set AVS evaluation ID based on the data in the last provisioning operation 240 dto.AVSInternalEvaluationID = lastProvOp.InstanceDetails.Avs.AvsEvaluationInternalId 241 h.converter.ApplyProvisioningOperation(dto, firstProvOp) 242 if len(provOprs) > 1 { 243 h.converter.ApplyUnsuspensionOperations(dto, provOprs[:len(provOprs)-1]) 244 } 245 } 246 247 deprovOprs, err := h.operationsDb.ListDeprovisioningOperationsByInstanceID(instance.InstanceID) 248 if err != nil && !dberr.IsNotFound(err) { 249 return fmt.Errorf("while fetching deprovisioning operations list for instance %s: %w", instance.InstanceID, err) 250 } 251 var deprovOp *internal.DeprovisioningOperation 252 if len(deprovOprs) != 0 { 253 for _, op := range deprovOprs { 254 if !op.Temporary { 255 deprovOp = &op 256 break 257 } 258 } 259 } 260 h.converter.ApplyDeprovisioningOperation(dto, deprovOp) 261 h.converter.ApplySuspensionOperations(dto, deprovOprs) 262 263 ukOprs, err := h.operationsDb.ListUpgradeKymaOperationsByInstanceID(instance.InstanceID) 264 if err != nil && !dberr.IsNotFound(err) { 265 return fmt.Errorf("while fetching upgrade kyma operation for instance %s: %w", instance.InstanceID, err) 266 } 267 dto.KymaVersion = determineKymaVersion(provOprs, ukOprs) 268 ukOprs, totalCount := h.takeLastNonDryRunOperations(ukOprs) 269 h.converter.ApplyUpgradingKymaOperations(dto, ukOprs, totalCount) 270 271 ucOprs, err := h.operationsDb.ListUpgradeClusterOperationsByInstanceID(instance.InstanceID) 272 if err != nil && !dberr.IsNotFound(err) { 273 return fmt.Errorf("while fetching upgrade cluster operation for instance %s: %w", instance.InstanceID, err) 274 } 275 ucOprs, totalCount = h.takeLastNonDryRunClusterOperations(ucOprs) 276 h.converter.ApplyUpgradingClusterOperations(dto, ucOprs, totalCount) 277 278 uOprs, err := h.operationsDb.ListUpdatingOperationsByInstanceID(instance.InstanceID) 279 if err != nil && !dberr.IsNotFound(err) { 280 return fmt.Errorf("while fetching update operation for instance %s: %w", instance.InstanceID, err) 281 } 282 totalCount = len(uOprs) 283 if len(uOprs) > numberOfUpgradeOperationsToReturn { 284 uOprs = uOprs[0:numberOfUpgradeOperationsToReturn] 285 } 286 h.converter.ApplyUpdateOperations(dto, uOprs, totalCount) 287 288 return nil 289 } 290 291 func (h *Handler) setRuntimeLastOperation(instance internal.Instance, dto *pkg.RuntimeDTO) error { 292 lastOp, err := h.operationsDb.GetLastOperation(instance.InstanceID) 293 if err != nil { 294 return fmt.Errorf("while fetching last operation instance %s: %w", instance.InstanceID, err) 295 } 296 297 // Set AVS evaluation ID based on the data in the last operation 298 dto.AVSInternalEvaluationID = lastOp.InstanceDetails.Avs.AvsEvaluationInternalId 299 300 switch lastOp.Type { 301 case internal.OperationTypeProvision: 302 provOps, err := h.operationsDb.ListProvisioningOperationsByInstanceID(instance.InstanceID) 303 if err != nil { 304 return fmt.Errorf("while fetching provisioning operations for instance %s: %w", instance.InstanceID, err) 305 } 306 lastProvOp := &provOps[0] 307 if len(provOps) > 1 { 308 h.converter.ApplyUnsuspensionOperations(dto, []internal.ProvisioningOperation{*lastProvOp}) 309 } else { 310 h.converter.ApplyProvisioningOperation(dto, lastProvOp) 311 } 312 313 case internal.OperationTypeDeprovision: 314 deprovOp, err := h.operationsDb.GetDeprovisioningOperationByID(lastOp.ID) 315 if err != nil { 316 return fmt.Errorf("while fetching deprovisioning operation for instance %s: %w", instance.InstanceID, err) 317 } 318 if deprovOp.Temporary { 319 h.converter.ApplySuspensionOperations(dto, []internal.DeprovisioningOperation{*deprovOp}) 320 } else { 321 h.converter.ApplyDeprovisioningOperation(dto, deprovOp) 322 } 323 324 case internal.OperationTypeUpgradeKyma: 325 upgKymaOp, err := h.operationsDb.GetUpgradeKymaOperationByID(lastOp.ID) 326 if err != nil { 327 return fmt.Errorf("while fetching upgrade kyma operation for instance %s: %w", instance.InstanceID, err) 328 } 329 h.converter.ApplyUpgradingKymaOperations(dto, []internal.UpgradeKymaOperation{*upgKymaOp}, 1) 330 331 case internal.OperationTypeUpgradeCluster: 332 upgClusterOp, err := h.operationsDb.GetUpgradeClusterOperationByID(lastOp.ID) 333 if err != nil { 334 return fmt.Errorf("while fetching upgrade cluster operation for instance %s: %w", instance.InstanceID, err) 335 } 336 h.converter.ApplyUpgradingClusterOperations(dto, []internal.UpgradeClusterOperation{*upgClusterOp}, 1) 337 338 case internal.OperationTypeUpdate: 339 updOp, err := h.operationsDb.GetUpdatingOperationByID(lastOp.ID) 340 if err != nil { 341 return fmt.Errorf("while fetching update operation for instance %s: %w", instance.InstanceID, err) 342 } 343 h.converter.ApplyUpdateOperations(dto, []internal.UpdatingOperation{*updOp}, 1) 344 345 default: 346 return fmt.Errorf("unsupported operation type: %s", lastOp.Type) 347 } 348 349 return nil 350 } 351 352 func (h *Handler) setRuntimeOptionalAttributes(instance internal.Instance, dto *pkg.RuntimeDTO, kymaConfig, clusterConfig bool) error { 353 if kymaConfig || clusterConfig { 354 states, err := h.runtimeStatesDb.ListByRuntimeID(instance.RuntimeID) 355 if err != nil && !dberr.IsNotFound(err) { 356 return fmt.Errorf("while fetching runtime states for instance %s: %w", instance.InstanceID, err) 357 } 358 for _, state := range states { 359 if kymaConfig && dto.KymaConfig == nil && state.KymaConfig.Version != "" { 360 config := state.KymaConfig 361 dto.KymaConfig = &config 362 } 363 if clusterConfig && dto.ClusterConfig == nil && state.ClusterConfig.Provider != "" { 364 config := state.ClusterConfig 365 dto.ClusterConfig = &config 366 } 367 if dto.KymaConfig != nil && dto.ClusterConfig != nil { 368 break 369 } 370 } 371 } 372 373 return nil 374 } 375 376 func determineKymaVersion(pOprs []internal.ProvisioningOperation, uOprs []internal.UpgradeKymaOperation) string { 377 kymaVersion := "" 378 kymaVersionSetAt := time.Time{} 379 380 // Set kyma version from the last provisioning operation 381 if len(pOprs) != 0 { 382 kymaVersion = pOprs[0].RuntimeVersion.Version 383 kymaVersionSetAt = pOprs[0].CreatedAt 384 } 385 386 // Take the last upgrade kyma operation which 387 // - is not dry-run 388 // - is created after the last provisioning operation 389 // - has the kyma version set 390 // - has been processed, i.e. not pending, canceling or canceled 391 // Use the last provisioning kyma version if no such upgrade operation was found, or the processed upgrade happened before the last provisioning operation. 392 for _, u := range uOprs { 393 if !u.DryRun && u.CreatedAt.After(kymaVersionSetAt) && u.RuntimeVersion.Version != "" && u.State != orchestration.Pending && u.State != orchestration.Canceling && u.State != orchestration.Canceled { 394 kymaVersion = u.RuntimeVersion.Version 395 break 396 } else if u.CreatedAt.Before(kymaVersionSetAt) { 397 break 398 } 399 } 400 401 return kymaVersion 402 } 403 404 func (h *Handler) getFilters(req *http.Request) dbmodel.InstanceFilter { 405 var filter dbmodel.InstanceFilter 406 query := req.URL.Query() 407 // For optional filter, zero value (nil) is fine if not supplied 408 filter.GlobalAccountIDs = query[pkg.GlobalAccountIDParam] 409 filter.SubAccountIDs = query[pkg.SubAccountIDParam] 410 filter.InstanceIDs = query[pkg.InstanceIDParam] 411 filter.RuntimeIDs = query[pkg.RuntimeIDParam] 412 filter.Regions = query[pkg.RegionParam] 413 filter.Shoots = query[pkg.ShootParam] 414 filter.Plans = query[pkg.PlanParam] 415 if v, exists := query[pkg.ExpiredParam]; exists && v[0] == "true" { 416 filter.Expired = ptr.Bool(true) 417 } 418 states := query[pkg.StateParam] 419 if len(states) == 0 { 420 // By default if no state filters are specified, suspended/deprovisioned runtimes are still excluded. 421 filter.States = append(filter.States, dbmodel.InstanceNotDeprovisioned) 422 } else { 423 allState := false 424 for _, s := range states { 425 switch pkg.State(s) { 426 case pkg.StateSucceeded: 427 filter.States = append(filter.States, dbmodel.InstanceSucceeded) 428 case pkg.StateFailed: 429 filter.States = append(filter.States, dbmodel.InstanceFailed) 430 case pkg.StateError: 431 filter.States = append(filter.States, dbmodel.InstanceError) 432 case pkg.StateProvisioning: 433 filter.States = append(filter.States, dbmodel.InstanceProvisioning) 434 case pkg.StateDeprovisioning: 435 filter.States = append(filter.States, dbmodel.InstanceDeprovisioning) 436 case pkg.StateUpgrading: 437 filter.States = append(filter.States, dbmodel.InstanceUpgrading) 438 case pkg.StateUpdating: 439 filter.States = append(filter.States, dbmodel.InstanceUpdating) 440 case pkg.StateSuspended: 441 filter.States = append(filter.States, dbmodel.InstanceDeprovisioned) 442 case pkg.StateDeprovisioned: 443 filter.States = append(filter.States, dbmodel.InstanceDeprovisioned) 444 case pkg.StateDeprovisionIncomplete: 445 deletionAttempted := true 446 filter.DeletionAttempted = &deletionAttempted 447 case pkg.AllState: 448 allState = true 449 } 450 } 451 if allState { 452 filter.States = nil 453 } 454 } 455 456 return filter 457 } 458 459 func getOpDetail(req *http.Request) pkg.OperationDetail { 460 opDetail := pkg.AllOperation 461 opDetailParams := req.URL.Query()[pkg.OperationDetailParam] 462 for _, p := range opDetailParams { 463 opDetailParam := pkg.OperationDetail(p) 464 switch opDetailParam { 465 case pkg.AllOperation, pkg.LastOperation: 466 opDetail = opDetailParam 467 } 468 } 469 470 return opDetail 471 } 472 473 func getBoolParam(param string, req *http.Request) bool { 474 requested := false 475 params := req.URL.Query()[param] 476 for _, p := range params { 477 if p == "true" { 478 requested = true 479 break 480 } 481 } 482 483 return requested 484 }