github.com/kyma-project/kyma-environment-broker@v0.0.1/common/orchestration/client_test.go (about) 1 package orchestration 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "net/http/httptest" 10 "reflect" 11 "sort" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/kyma-project/kyma-environment-broker/common/pagination" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 "golang.org/x/exp/slices" 21 "golang.org/x/oauth2" 22 ) 23 24 type FakeTokenSource string 25 26 var fixToken FakeTokenSource = "fake-token-1234" 27 28 func (t FakeTokenSource) Token() (*oauth2.Token, error) { 29 return &oauth2.Token{ 30 AccessToken: string(t), 31 Expiry: time.Now().Add(time.Duration(12 * time.Hour)), 32 }, nil 33 } 34 35 var orch1 = fixStatusResponse("orchestration1") 36 var orch2 = fixStatusResponse("orchestration2") 37 var orch3 = fixStatusResponse("orchestration3") 38 var orch4 = fixStatusResponse("orchestration4") 39 var orchs = []StatusResponse{orch1, orch2, orch3, orch4} 40 var operations = []OperationResponse{ 41 fixOperationResponse("operation1", orch1.OrchestrationID, "in progress"), 42 fixOperationResponse("operation2", orch1.OrchestrationID, "pending"), 43 fixOperationResponse("operation3", orch1.OrchestrationID, "in progress"), 44 fixOperationResponse("operation4", orch1.OrchestrationID, "pending"), 45 } 46 47 var operationsWithFailedState = []OperationResponse{ 48 fixOperationResponse("operation1", orch1.OrchestrationID, "pending"), 49 fixOperationResponse("operation2", orch1.OrchestrationID, "in progress"), 50 fixOperationResponse("operation3", orch1.OrchestrationID, "failed"), 51 fixOperationResponse("operation4", orch1.OrchestrationID, "in progress"), 52 } 53 54 func TestClient_ListOrchestrations(t *testing.T) { 55 t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) { 56 // given 57 called := 0 58 params := ListParameters{ 59 PageSize: 2, 60 States: []string{"failed", "in progress"}, 61 } 62 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 63 called++ 64 assert.Equal(t, http.MethodGet, r.Method) 65 assert.Equal(t, "/orchestrations", r.URL.Path) 66 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 67 query := r.URL.Query() 68 assert.ElementsMatch(t, []string{strconv.Itoa(called)}, query[pagination.PageParam]) 69 assert.ElementsMatch(t, []string{strconv.Itoa(params.PageSize)}, query[pagination.PageSizeParam]) 70 assert.ElementsMatch(t, params.States, query[StateParam]) 71 72 err := respondStatusList(w, orchs[(called-1)*params.PageSize:called*params.PageSize], 4) 73 require.NoError(t, err) 74 })) 75 defer ts.Close() 76 client := NewClient(context.TODO(), ts.URL, fixToken) 77 78 // when 79 srl, err := client.ListOrchestrations(params) 80 81 // then 82 require.NoError(t, err) 83 assert.Equal(t, 2, called) 84 assert.Equal(t, 4, srl.Count) 85 assert.Equal(t, 4, srl.TotalCount) 86 assert.Len(t, srl.Data, 4) 87 assert.Equal(t, orch1.OrchestrationID, srl.Data[0].OrchestrationID) 88 assert.Equal(t, orch2.OrchestrationID, srl.Data[1].OrchestrationID) 89 assert.Equal(t, orch3.OrchestrationID, srl.Data[2].OrchestrationID) 90 assert.Equal(t, orch4.OrchestrationID, srl.Data[3].OrchestrationID) 91 }) 92 } 93 94 func TestClient_GetOrchestration(t *testing.T) { 95 t.Run("test_URL__NoError_path", func(t *testing.T) { 96 // given 97 called := 0 98 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 99 called++ 100 assert.Equal(t, http.MethodGet, r.Method) 101 assert.Equal(t, fmt.Sprintf("/orchestrations/%s", orch1.OrchestrationID), r.URL.Path) 102 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 103 104 err := respondStatus(w, orch1) 105 require.NoError(t, err) 106 })) 107 defer ts.Close() 108 client := NewClient(context.TODO(), ts.URL, fixToken) 109 110 // when 111 sr, err := client.GetOrchestration(orch1.OrchestrationID) 112 113 // then 114 require.NoError(t, err) 115 assert.Equal(t, 1, called) 116 assert.Equal(t, orch1.OrchestrationID, sr.OrchestrationID) 117 }) 118 } 119 120 func TestClient_ListOperationsWithoutFailed(t *testing.T) { 121 t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) { 122 // given 123 called := 0 124 params := ListParameters{ 125 PageSize: 2, 126 States: []string{"pending", "in progress"}, 127 } 128 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 129 called++ 130 assert.Equal(t, http.MethodGet, r.Method) 131 assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations", orch1.OrchestrationID), r.URL.Path) 132 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 133 query := r.URL.Query() 134 135 assert.ElementsMatch(t, []string{strconv.Itoa(called)}, query[pagination.PageParam]) 136 assert.ElementsMatch(t, []string{strconv.Itoa(params.PageSize)}, query[pagination.PageSizeParam]) 137 assert.ElementsMatch(t, params.States, query[StateParam]) 138 139 err := respondOperationList(w, operations[(called-1)*params.PageSize:called*params.PageSize], 4) 140 require.NoError(t, err) 141 })) 142 defer ts.Close() 143 client := NewClient(context.TODO(), ts.URL, fixToken) 144 145 // when 146 orl, err := client.ListOperations(orch1.OrchestrationID, params) 147 148 // then 149 require.NoError(t, err) 150 assert.Equal(t, 2, called) 151 assert.Equal(t, 4, orl.Count) 152 assert.Equal(t, 4, orl.TotalCount) 153 assert.Len(t, orl.Data, 4) 154 for i := 0; i < 4; i++ { 155 assert.Equal(t, orch1.OrchestrationID, orl.Data[i].OrchestrationID) 156 assert.Equal(t, operations[i].OperationID, orl.Data[i].OperationID) 157 } 158 }) 159 } 160 161 func TestClient_ListOperationsWithFailed(t *testing.T) { 162 t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) { 163 // given 164 called := 0 165 params := ListParameters{ 166 PageSize: 2, 167 States: []string{"failed"}, 168 } 169 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 170 called++ 171 assert.Equal(t, http.MethodGet, r.Method) 172 assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations", orch1.OrchestrationID), r.URL.Path) 173 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 174 query := r.URL.Query() 175 assert.ElementsMatch(t, params.States, query[StateParam]) 176 err := respondOperationList(w, operations[(called-1)*params.PageSize:called*params.PageSize], 2) 177 require.NoError(t, err) 178 })) 179 defer ts.Close() 180 client := NewClient(context.TODO(), ts.URL, fixToken) 181 // when 182 orl, err := client.ListOperations(orch1.OrchestrationID, params) 183 // then 184 require.NoError(t, err) 185 assert.Equal(t, 1, called) 186 assert.Equal(t, 2, orl.Count) 187 assert.Equal(t, 2, orl.TotalCount) 188 assert.Len(t, orl.Data, 2) 189 for i := 0; i < 2; i++ { 190 assert.Equal(t, orch1.OrchestrationID, orl.Data[i].OrchestrationID) 191 assert.Equal(t, operations[i].OperationID, orl.Data[i].OperationID) 192 } 193 }) 194 } 195 196 func TestClient_ListOperations(t *testing.T) { 197 t.Run("test_URL_params_pagination__NoError_path", func(t *testing.T) { 198 // given 199 called := 0 200 params := ListParameters{ 201 PageSize: 2, 202 States: []string{"failed", "in progress", "pending"}, 203 } 204 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 205 called++ 206 assert.Equal(t, http.MethodGet, r.Method) 207 assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations", orch1.OrchestrationID), r.URL.Path) 208 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 209 query := r.URL.Query() 210 var err error 211 assert.ElementsMatch(t, []string{strconv.Itoa(called)}, query[pagination.PageParam]) 212 assert.ElementsMatch(t, []string{strconv.Itoa(params.PageSize)}, query[pagination.PageSizeParam]) 213 if called == 2 { 214 assert.ElementsMatch(t, []string{"in progress", "pending"}, query[StateParam]) 215 } else { 216 assert.ElementsMatch(t, params.States, query[StateParam]) 217 } 218 219 err = respondOperationListWithFailed(w, operationsWithFailedState[(called-1)*params.PageSize:called*params.PageSize], query[StateParam], 4) 220 require.NoError(t, err) 221 })) 222 defer ts.Close() 223 client := NewClient(context.TODO(), ts.URL, fixToken) 224 // when 225 orl, err := client.ListOperations(orch1.OrchestrationID, params) 226 // then 227 require.NoError(t, err) 228 229 assert.Equal(t, 2, called) 230 assert.Equal(t, 4, orl.Count) 231 assert.Equal(t, 4, orl.TotalCount) 232 assert.Len(t, orl.Data, 4) 233 var opS, orlD []string 234 for i := 0; i < 4; i++ { 235 opS = append(opS, operationsWithFailedState[i].OperationID) 236 orlD = append(orlD, orl.Data[i].OperationID) 237 } 238 sort.Strings(opS) 239 sort.Strings(orlD) 240 for i := 0; i < 4; i++ { 241 assert.Equal(t, orch1.OrchestrationID, orl.Data[i].OrchestrationID) 242 assert.Equal(t, opS, orlD) 243 } 244 }) 245 } 246 247 func TestClient_GetOperation(t *testing.T) { 248 t.Run("test_URL__NoError_path", func(t *testing.T) { 249 // given 250 called := 0 251 oper := fixOperationDetailResponse("operation1", orch1.OrchestrationID) 252 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 253 called++ 254 assert.Equal(t, http.MethodGet, r.Method) 255 assert.Equal(t, fmt.Sprintf("/orchestrations/%s/operations/%s", orch1.OrchestrationID, oper.OperationID), r.URL.Path) 256 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 257 258 err := respondOperationDetail(w, oper) 259 require.NoError(t, err) 260 })) 261 defer ts.Close() 262 client := NewClient(context.TODO(), ts.URL, fixToken) 263 264 // when 265 od, err := client.GetOperation(orch1.OrchestrationID, oper.OperationID) 266 267 // then 268 require.NoError(t, err) 269 assert.Equal(t, 1, called) 270 assert.Equal(t, orch1.OrchestrationID, od.OrchestrationID) 271 assert.Equal(t, oper.OperationID, od.OperationID) 272 }) 273 } 274 275 func TestClient_UpgradeKyma(t *testing.T) { 276 t.Run("test_URL_request_body_NoError_path", func(t *testing.T) { 277 // given 278 called := 0 279 params := Parameters{ 280 Targets: TargetSpec{ 281 Include: []RuntimeTarget{ 282 { 283 Target: TargetAll, 284 }, 285 }, 286 Exclude: []RuntimeTarget{ 287 { 288 GlobalAccount: "GA", 289 }, 290 }, 291 }, 292 Strategy: StrategySpec{ 293 Type: ParallelStrategy, 294 Schedule: time.Now().Format(time.RFC3339), 295 MaintenanceWindow: true, 296 Parallel: ParallelStrategySpec{ 297 Workers: 2, 298 }, 299 }, 300 } 301 orchestrationID := orch1.OrchestrationID 302 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 303 called++ 304 assert.Equal(t, http.MethodPost, r.Method) 305 assert.Equal(t, "/upgrade/kyma", r.URL.Path) 306 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 307 reqBody := Parameters{} 308 err := json.NewDecoder(r.Body).Decode(&reqBody) 309 require.NoError(t, err) 310 assert.True(t, reflect.DeepEqual(params, reqBody)) 311 312 err = respondUpgrade(w, orchestrationID) 313 require.NoError(t, err) 314 })) 315 defer ts.Close() 316 client := NewClient(context.TODO(), ts.URL, fixToken) 317 318 // when 319 ur, err := client.UpgradeKyma(params) 320 321 // then 322 require.NoError(t, err) 323 assert.Equal(t, 1, called) 324 assert.Equal(t, orchestrationID, ur.OrchestrationID) 325 }) 326 } 327 328 func TestClient_CancelOrchestration(t *testing.T) { 329 t.Run("test_URL__NoError_path", func(t *testing.T) { 330 // given 331 called := 0 332 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 333 called++ 334 assert.Equal(t, http.MethodPut, r.Method) 335 assert.Equal(t, fmt.Sprintf("/orchestrations/%s/cancel", orch1.OrchestrationID), r.URL.Path) 336 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 337 338 err := respondStatus(w, orch1) 339 require.NoError(t, err) 340 })) 341 defer ts.Close() 342 client := NewClient(context.TODO(), ts.URL, fixToken) 343 344 // when 345 err := client.CancelOrchestration(orch1.OrchestrationID) 346 347 // then 348 require.NoError(t, err) 349 assert.Equal(t, 1, called) 350 }) 351 } 352 353 func TestClient_RetryOrchestration(t *testing.T) { 354 t.Run("test_URL_NoError_path", func(t *testing.T) { 355 // given 356 called := 0 357 operationIDs := []string{"operation_id_0", "operation_id_1"} 358 ids := []string{} 359 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 360 called++ 361 assert.Equal(t, http.MethodPost, r.Method) 362 assert.Equal(t, fmt.Sprintf("/orchestrations/%s/retry", orch1.OrchestrationID), r.URL.Path) 363 assert.Equal(t, fmt.Sprintf("Bearer %s", fixToken), r.Header.Get("Authorization")) 364 assert.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type")) 365 366 for _, id := range operationIDs { 367 ids = append(ids, "operation-id="+id) 368 } 369 370 buf := new(bytes.Buffer) 371 buf.ReadFrom(r.Body) 372 body := buf.String() 373 assert.Equal(t, strings.Join(operationIDs, "&"), body) 374 375 err := respondRetry(w, orch1.OrchestrationID, operationIDs) 376 require.NoError(t, err) 377 378 })) 379 defer ts.Close() 380 client := NewClient(context.TODO(), ts.URL, fixToken) 381 expectedRr := RetryResponse{ 382 OrchestrationID: orch1.OrchestrationID, 383 RetryShoots: []string{"Shoot-instance-A", "Shoot-instance-B"}, 384 Msg: "retry operations are queued for processing", 385 } 386 387 // when 388 rr, err := client.RetryOrchestration(orch1.OrchestrationID, operationIDs, false) 389 390 // then 391 require.NoError(t, err) 392 assert.Equal(t, 1, called) 393 assert.Equal(t, expectedRr, rr) 394 395 // when 396 operationIDs = nil 397 398 expectedRr.RetryShoots = []string{"Shoot-instance-C", "Shoot-instance-D"} 399 rr, err = client.RetryOrchestration(orch1.OrchestrationID, operationIDs, false) 400 401 // then 402 require.NoError(t, err) 403 assert.Equal(t, 2, called) 404 assert.Equal(t, expectedRr, rr) 405 }) 406 } 407 408 func fixStatusResponse(id string) StatusResponse { 409 return StatusResponse{ 410 OrchestrationID: id, 411 State: "succeeded", 412 Description: id, 413 CreatedAt: time.Now(), 414 UpdatedAt: time.Now(), 415 Parameters: Parameters{}, 416 OperationStats: map[string]int{ 417 "succeeded": 5, 418 }, 419 } 420 } 421 422 func fixOperationResponse(id, orchestrationID, state string) OperationResponse { 423 return OperationResponse{ 424 OperationID: id, 425 RuntimeID: id, 426 OrchestrationID: orchestrationID, 427 State: state, 428 } 429 } 430 431 func fixOperationDetailResponse(id, orchestrationID string) OperationDetailResponse { 432 return OperationDetailResponse{ 433 OperationResponse: fixOperationResponse(id, orchestrationID, ""), 434 } 435 } 436 437 func respondStatusList(w http.ResponseWriter, statuses []StatusResponse, totalCount int) error { 438 srl := StatusResponseList{ 439 Data: statuses, 440 Count: len(statuses), 441 TotalCount: totalCount, 442 } 443 data, err := json.Marshal(srl) 444 if err != nil { 445 w.WriteHeader(http.StatusInternalServerError) 446 return err 447 } 448 449 w.Header().Set("Content-Type", "application/json") 450 w.WriteHeader(http.StatusOK) 451 _, err = w.Write(data) 452 return err 453 } 454 455 func respondStatus(w http.ResponseWriter, status StatusResponse) error { 456 data, err := json.Marshal(status) 457 if err != nil { 458 w.WriteHeader(http.StatusInternalServerError) 459 return err 460 } 461 462 w.Header().Set("Content-Type", "application/json") 463 w.WriteHeader(http.StatusOK) 464 _, err = w.Write(data) 465 return err 466 } 467 468 func respondOperationList(w http.ResponseWriter, operations []OperationResponse, totalCount int) error { 469 orl := OperationResponseList{ 470 Data: operations, 471 Count: len(operations), 472 TotalCount: totalCount, 473 } 474 data, err := json.Marshal(orl) 475 if err != nil { 476 w.WriteHeader(http.StatusInternalServerError) 477 return err 478 } 479 480 w.Header().Set("Content-Type", "application/json") 481 w.WriteHeader(http.StatusOK) 482 _, err = w.Write(data) 483 return err 484 } 485 486 func respondOperationListWithFailed(w http.ResponseWriter, operations []OperationResponse, queryState []string, totalCount int) error { 487 var operationR []OperationResponse 488 if slices.Contains(queryState, "failed") { 489 for i := 0; i < totalCount; i++ { 490 if operationsWithFailedState[i].State == "failed" && i >= len(operations) { 491 operationR = append(operationR, operationsWithFailedState[i]) 492 } 493 } 494 } 495 for i, _ := range operations { 496 if operations[i].State != "failed" { 497 operationR = append(operationR, operations[i]) 498 } 499 } 500 orl := OperationResponseList{ 501 Data: operationR, 502 Count: len(operationR), 503 TotalCount: totalCount, 504 } 505 data, err := json.Marshal(orl) 506 if err != nil { 507 w.WriteHeader(http.StatusInternalServerError) 508 return err 509 } 510 511 w.Header().Set("Content-Type", "application/json") 512 w.WriteHeader(http.StatusOK) 513 _, err = w.Write(data) 514 return err 515 } 516 517 func respondOperationDetail(w http.ResponseWriter, operation OperationDetailResponse) error { 518 data, err := json.Marshal(operation) 519 if err != nil { 520 w.WriteHeader(http.StatusInternalServerError) 521 return err 522 } 523 524 w.Header().Set("Content-Type", "application/json") 525 w.WriteHeader(http.StatusOK) 526 _, err = w.Write(data) 527 return err 528 } 529 530 func respondUpgrade(w http.ResponseWriter, orchestrationID string) error { 531 ur := UpgradeResponse{ 532 OrchestrationID: orchestrationID, 533 } 534 data, err := json.Marshal(ur) 535 if err != nil { 536 w.WriteHeader(http.StatusInternalServerError) 537 return err 538 } 539 540 w.Header().Set("Content-Type", "application/json") 541 w.WriteHeader(http.StatusAccepted) 542 _, err = w.Write(data) 543 return err 544 } 545 546 func respondRetry(w http.ResponseWriter, orchestrationID string, operationIDs []string) error { 547 rr := RetryResponse{ 548 OrchestrationID: orchestrationID, 549 } 550 551 if len(operationIDs) == 0 { 552 rr.RetryShoots = []string{"Shoot-instance-C", "Shoot-instance-D"} 553 rr.Msg = "retry operations are queued for processing" 554 } else { 555 rr.RetryShoots = []string{"Shoot-instance-A", "Shoot-instance-B"} 556 rr.Msg = "retry operations are queued for processing" 557 } 558 559 data, err := json.Marshal(rr) 560 if err != nil { 561 w.WriteHeader(http.StatusInternalServerError) 562 return err 563 } 564 565 w.Header().Set("Content-Type", "application/json") 566 w.WriteHeader(http.StatusAccepted) 567 _, err = w.Write(data) 568 return err 569 }