github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/appinfo/runtime_info_test.go (about) 1 package appinfo_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 "testing" 9 "time" 10 11 "github.com/gorilla/mux" 12 "github.com/kyma-project/kyma-environment-broker/internal" 13 "github.com/kyma-project/kyma-environment-broker/internal/appinfo" 14 "github.com/kyma-project/kyma-environment-broker/internal/appinfo/automock" 15 "github.com/kyma-project/kyma-environment-broker/internal/broker" 16 "github.com/kyma-project/kyma-environment-broker/internal/fixture" 17 "github.com/kyma-project/kyma-environment-broker/internal/httputil" 18 "github.com/kyma-project/kyma-environment-broker/internal/logger" 19 "github.com/kyma-project/kyma-environment-broker/internal/storage" 20 "github.com/kyma-project/kyma-environment-broker/internal/storage/driver/memory" 21 "github.com/pivotal-cf/brokerapi/v8/domain" 22 "github.com/sebdah/goldie/v2" 23 "github.com/stretchr/testify/assert" 24 "github.com/stretchr/testify/mock" 25 "github.com/stretchr/testify/require" 26 ) 27 28 // go test ./internal/appinfo -update -run=TestRuntimeInfoHandlerSuccess 29 func TestRuntimeInfoHandlerSuccess(t *testing.T) { 30 tests := map[string]struct { 31 instances []internal.Instance 32 provisionOp []internal.ProvisioningOperation 33 deprovisionOp []internal.DeprovisioningOperation 34 }{ 35 "no instances": { 36 instances: []internal.Instance{}, 37 }, 38 "instances without operations": { 39 instances: []internal.Instance{ 40 fixInstance(1), fixInstance(2), fixInstance(2), 41 }, 42 }, 43 "instances without service and plan name should have defaults": { 44 instances: func() []internal.Instance { 45 i := fixInstance(1) 46 i.ServicePlanName = "" 47 i.ServiceName = "" 48 // selecting servicePlanName based on existing real planID 49 i.ServicePlanID = broker.GCPPlanID 50 return []internal.Instance{i} 51 }(), 52 }, 53 "instances without platform region name should have default": { 54 instances: func() []internal.Instance { 55 i := fixInstance(1) 56 // the platform_region is not specified 57 i.Parameters = internal.ProvisioningParameters{} 58 return []internal.Instance{i} 59 }(), 60 }, 61 "instances with provision operation": { 62 instances: []internal.Instance{ 63 fixInstance(1), fixInstance(2), fixInstance(3), 64 }, 65 provisionOp: []internal.ProvisioningOperation{ 66 fixProvisionOperation(1), fixProvisionOperation(2), 67 }, 68 }, 69 "instances with deprovision operation": { 70 instances: []internal.Instance{ 71 fixInstance(1), fixInstance(2), fixInstance(3), 72 }, 73 deprovisionOp: []internal.DeprovisioningOperation{ 74 fixDeprovisionOperation(1), fixDeprovisionOperation(2), 75 }, 76 }, 77 "instances with provision and deprovision operations": { 78 instances: []internal.Instance{ 79 fixInstance(1), fixInstance(2), fixInstance(3), 80 }, 81 provisionOp: []internal.ProvisioningOperation{ 82 fixProvisionOperation(1), fixProvisionOperation(2), 83 }, 84 deprovisionOp: []internal.DeprovisioningOperation{ 85 fixDeprovisionOperation(1), fixDeprovisionOperation(2), 86 }, 87 }, 88 } 89 for tn, tc := range tests { 90 t.Run(tn, func(t *testing.T) { 91 // given 92 var ( 93 fixReq = httptest.NewRequest("GET", "http://example.com/foo", nil) 94 respSpy = httptest.NewRecorder() 95 writer = httputil.NewResponseWriter(logger.NewLogDummy(), true) 96 memStorage = newInMemoryStorage(t, tc.instances, tc.provisionOp, tc.deprovisionOp) 97 ) 98 99 handler := appinfo.NewRuntimeInfoHandler(memStorage.Instances(), memStorage.Operations(), broker.PlansConfig{}, "default-region", writer) 100 101 // when 102 handler.ServeHTTP(respSpy, fixReq) 103 104 // then 105 assert.Equal(t, http.StatusOK, respSpy.Result().StatusCode) 106 assert.Equal(t, "application/json", respSpy.Result().Header.Get("Content-Type")) 107 108 assertJSONWithGoldenFile(t, respSpy.Body.Bytes()) 109 }) 110 } 111 } 112 113 func TestRuntimeInfoHandlerFailures(t *testing.T) { 114 // given 115 var ( 116 fixReq = httptest.NewRequest("GET", "http://example.com/foo", nil) 117 respSpy = httptest.NewRecorder() 118 writer = httputil.NewResponseWriter(logger.NewLogDummy(), true) 119 expBody = `{ 120 "status": 500, 121 "requestId": "", 122 "message": "Something went very wrong. Please try again.", 123 "details": "while fetching all instances: ups.. internal info" 124 }` 125 ) 126 127 storageMock := &automock.InstanceFinder{} 128 defer storageMock.AssertExpectations(t) 129 storageMock.On("FindAllJoinedWithOperations", mock.Anything).Return(nil, fmt.Errorf("ups.. internal info")) 130 handler := appinfo.NewRuntimeInfoHandler(storageMock, nil, broker.PlansConfig{}, "", writer) 131 132 // when 133 handler.ServeHTTP(respSpy, fixReq) 134 135 // then 136 assert.Equal(t, http.StatusInternalServerError, respSpy.Result().StatusCode) 137 assert.Equal(t, "application/json", respSpy.Result().Header.Get("Content-Type")) 138 139 assert.JSONEq(t, expBody, respSpy.Body.String()) 140 } 141 142 func TestRuntimeInfoHandlerOperationRecognition(t *testing.T) { 143 t.Run("should distinguish between provisioning & unsuspension operations", func(t *testing.T) { 144 // given 145 operations := memory.NewOperation() 146 instances := memory.NewInstance(operations) 147 148 testInstance1 := fixture.FixInstance("instance-1") 149 testInstance2 := fixture.FixInstance("instance-2") 150 151 err := instances.Insert(testInstance1) 152 require.NoError(t, err) 153 err = instances.Insert(testInstance2) 154 require.NoError(t, err) 155 156 provisioningOpId1 := "provisioning-op-1" 157 provisioningOpId2 := "provisioning-op-2" 158 unsuspensionOpId1 := "unsuspension-op-1" 159 unsuspensionOpId2 := "unsuspension-op-2" 160 provisioningOpDesc1 := "succeeded provisioning operation 1" 161 provisioningOpDesc2 := "succeeded provisioning operation 2" 162 unsuspensionOpDesc1 := "succeeded unsuspension operation 1" 163 unsuspensionOpDesc2 := "succeeded unsuspension operation 2" 164 165 err = operations.InsertProvisioningOperation(internal.ProvisioningOperation{ 166 Operation: internal.Operation{ 167 ID: provisioningOpId1, 168 Version: 0, 169 CreatedAt: time.Now(), 170 UpdatedAt: time.Now().Add(5 * time.Minute), 171 Type: internal.OperationTypeProvision, 172 InstanceID: testInstance1.InstanceID, 173 State: domain.Succeeded, 174 Description: provisioningOpDesc1, 175 }, 176 }) 177 require.NoError(t, err) 178 179 err = operations.InsertProvisioningOperation(internal.ProvisioningOperation{ 180 Operation: internal.Operation{ 181 ID: unsuspensionOpId1, 182 Version: 0, 183 CreatedAt: time.Now().Add(1 * time.Hour), 184 UpdatedAt: time.Now().Add(1 * time.Hour).Add(5 * time.Minute), 185 Type: internal.OperationTypeProvision, 186 InstanceID: testInstance1.InstanceID, 187 State: domain.Succeeded, 188 Description: unsuspensionOpDesc1, 189 }, 190 }) 191 require.NoError(t, err) 192 193 err = operations.InsertProvisioningOperation(internal.ProvisioningOperation{ 194 Operation: internal.Operation{ 195 ID: unsuspensionOpId2, 196 Version: 0, 197 CreatedAt: time.Now().Add(1 * time.Hour), 198 UpdatedAt: time.Now().Add(1 * time.Hour).Add(5 * time.Minute), 199 Type: internal.OperationTypeProvision, 200 InstanceID: testInstance2.InstanceID, 201 State: domain.Succeeded, 202 Description: unsuspensionOpDesc2, 203 }, 204 }) 205 require.NoError(t, err) 206 207 err = operations.InsertProvisioningOperation(internal.ProvisioningOperation{ 208 Operation: internal.Operation{ 209 ID: provisioningOpId2, 210 Version: 0, 211 CreatedAt: time.Now(), 212 UpdatedAt: time.Now().Add(5 * time.Minute), 213 Type: internal.OperationTypeProvision, 214 InstanceID: testInstance2.InstanceID, 215 State: domain.Succeeded, 216 Description: provisioningOpDesc2, 217 }, 218 }) 219 require.NoError(t, err) 220 221 req, err := http.NewRequest("GET", "/info/runtimes", nil) 222 require.NoError(t, err) 223 224 responseWriter := httputil.NewResponseWriter(logger.NewLogDummy(), true) 225 runtimesInfoHandler := appinfo.NewRuntimeInfoHandler(instances, operations, broker.PlansConfig{}, "", responseWriter) 226 227 rr := httptest.NewRecorder() 228 router := mux.NewRouter() 229 router.Handle("/info/runtimes", runtimesInfoHandler) 230 231 // when 232 runtimesInfoHandler.ServeHTTP(rr, req) 233 234 // then 235 require.Equal(t, http.StatusOK, rr.Code) 236 237 var out []*appinfo.RuntimeDTO 238 239 err = json.Unmarshal(rr.Body.Bytes(), &out) 240 require.NoError(t, err) 241 242 assert.Equal(t, 2, len(out)) 243 assert.Equal(t, testInstance1.InstanceID, out[0].ServiceInstanceID) 244 assert.Equal(t, testInstance2.InstanceID, out[1].ServiceInstanceID) 245 assert.Equal(t, provisioningOpDesc1, out[0].Status.Provisioning.Description) 246 assert.Equal(t, provisioningOpDesc2, out[1].Status.Provisioning.Description) 247 248 }) 249 250 t.Run("should distinguish between deprovisioning & suspension operations", func(t *testing.T) { 251 // given 252 operations := memory.NewOperation() 253 instances := memory.NewInstance(operations) 254 255 testInstance1 := fixture.FixInstance("instance-1") 256 testInstance2 := fixture.FixInstance("instance-2") 257 258 err := instances.Insert(testInstance1) 259 require.NoError(t, err) 260 err = instances.Insert(testInstance2) 261 require.NoError(t, err) 262 263 deprovisioningOpId1 := "deprovisioning-op-1" 264 deprovisioningOpId2 := "deprovisioning-op-2" 265 suspensionOpId1 := "suspension-op-1" 266 suspensionOpId2 := "suspension-op-2" 267 deprovisioningOpDesc1 := "succeeded deprovisioning operation 1" 268 deprovisioningOpDesc2 := "succeeded deprovisioning operation 2" 269 suspensionOpDesc1 := "succeeded suspension operation 1" 270 suspensionOpDesc2 := "succeeded suspension operation 2" 271 272 err = operations.InsertDeprovisioningOperation(internal.DeprovisioningOperation{ 273 Operation: internal.Operation{ 274 ID: suspensionOpId1, 275 Version: 0, 276 CreatedAt: time.Now(), 277 UpdatedAt: time.Now().Add(5 * time.Minute), 278 Type: internal.OperationTypeDeprovision, 279 InstanceID: testInstance1.InstanceID, 280 State: domain.Succeeded, 281 Description: suspensionOpDesc1, 282 Temporary: true, 283 }, 284 }) 285 require.NoError(t, err) 286 287 err = operations.InsertDeprovisioningOperation(internal.DeprovisioningOperation{ 288 Operation: internal.Operation{ 289 ID: deprovisioningOpId1, 290 Version: 0, 291 CreatedAt: time.Now().Add(1 * time.Hour), 292 UpdatedAt: time.Now().Add(1 * time.Hour).Add(5 * time.Minute), 293 Type: internal.OperationTypeDeprovision, 294 InstanceID: testInstance1.InstanceID, 295 State: domain.Succeeded, 296 Description: deprovisioningOpDesc1, 297 }, 298 }) 299 require.NoError(t, err) 300 301 err = operations.InsertDeprovisioningOperation(internal.DeprovisioningOperation{ 302 Operation: internal.Operation{ 303 ID: deprovisioningOpId2, 304 Version: 0, 305 CreatedAt: time.Now().Add(1 * time.Hour), 306 UpdatedAt: time.Now().Add(1 * time.Hour).Add(5 * time.Minute), 307 Type: internal.OperationTypeDeprovision, 308 InstanceID: testInstance2.InstanceID, 309 State: domain.Succeeded, 310 Description: deprovisioningOpDesc2, 311 }, 312 }) 313 require.NoError(t, err) 314 315 err = operations.InsertDeprovisioningOperation(internal.DeprovisioningOperation{ 316 Operation: internal.Operation{ 317 ID: suspensionOpId2, 318 Version: 0, 319 CreatedAt: time.Now(), 320 UpdatedAt: time.Now().Add(5 * time.Minute), 321 Type: internal.OperationTypeProvision, 322 InstanceID: testInstance2.InstanceID, 323 State: domain.Succeeded, 324 Description: suspensionOpDesc2, 325 Temporary: true, 326 }, 327 }) 328 require.NoError(t, err) 329 330 req, err := http.NewRequest("GET", "/info/runtimes", nil) 331 require.NoError(t, err) 332 333 responseWriter := httputil.NewResponseWriter(logger.NewLogDummy(), true) 334 runtimesInfoHandler := appinfo.NewRuntimeInfoHandler(instances, operations, broker.PlansConfig{}, "", responseWriter) 335 336 rr := httptest.NewRecorder() 337 router := mux.NewRouter() 338 router.Handle("/info/runtimes", runtimesInfoHandler) 339 340 // when 341 runtimesInfoHandler.ServeHTTP(rr, req) 342 343 // then 344 require.Equal(t, http.StatusOK, rr.Code) 345 346 var out []*appinfo.RuntimeDTO 347 348 err = json.Unmarshal(rr.Body.Bytes(), &out) 349 require.NoError(t, err) 350 351 assert.Equal(t, 2, len(out)) 352 assert.Equal(t, testInstance1.InstanceID, out[0].ServiceInstanceID) 353 assert.Equal(t, testInstance2.InstanceID, out[1].ServiceInstanceID) 354 assert.Equal(t, deprovisioningOpDesc1, out[0].Status.Deprovisioning.Description) 355 assert.Equal(t, deprovisioningOpDesc2, out[1].Status.Deprovisioning.Description) 356 357 }) 358 359 t.Run("should recognize prov & deprov ops among suspend/unsuspend operations", func(t *testing.T) { 360 // given 361 operations := memory.NewOperation() 362 instances := memory.NewInstance(operations) 363 364 testInstance1 := fixture.FixInstance("instance-1") 365 366 err := instances.Insert(testInstance1) 367 require.NoError(t, err) 368 369 provisioningOpId := "provisioning-op" 370 deprovisioningOpId := "deprovisioning-op" 371 suspensionOpId1 := "suspension-op-1" 372 suspensionOpId2 := "suspension-op-2" 373 unsuspensionOpId1 := "unsuspension-op-1" 374 unsuspensionOpId2 := "unsuspension-op-2" 375 provisioningOpDesc := "succeeded provisioning operation" 376 deprovisioningOpDesc := "succeeded deprovisioning operation" 377 suspensionOpDesc1 := "failed suspension operation 1" 378 suspensionOpDesc2 := "succeeded suspension operation 2" 379 unsuspensionOpDesc1 := "failed unsuspension operation 1" 380 unsuspensionOpDesc2 := "succeeded unsuspension operation 2" 381 382 err = operations.InsertProvisioningOperation(internal.ProvisioningOperation{ 383 Operation: internal.Operation{ 384 ID: provisioningOpId, 385 Version: 0, 386 CreatedAt: time.Now(), 387 UpdatedAt: time.Now().Add(5 * time.Minute), 388 Type: internal.OperationTypeProvision, 389 InstanceID: testInstance1.InstanceID, 390 State: domain.Succeeded, 391 Description: provisioningOpDesc, 392 }, 393 }) 394 require.NoError(t, err) 395 396 err = operations.InsertDeprovisioningOperation(internal.DeprovisioningOperation{ 397 Operation: internal.Operation{ 398 ID: suspensionOpId1, 399 Version: 0, 400 CreatedAt: time.Now().Add(1 * time.Hour), 401 UpdatedAt: time.Now().Add(1 * time.Hour).Add(5 * time.Minute), 402 Type: internal.OperationTypeDeprovision, 403 InstanceID: testInstance1.InstanceID, 404 State: domain.Failed, 405 Description: suspensionOpDesc1, 406 Temporary: true, 407 }, 408 }) 409 require.NoError(t, err) 410 411 err = operations.InsertDeprovisioningOperation(internal.DeprovisioningOperation{ 412 Operation: internal.Operation{ 413 ID: suspensionOpId2, 414 Version: 0, 415 CreatedAt: time.Now().Add(2 * time.Hour), 416 UpdatedAt: time.Now().Add(2 * time.Hour).Add(5 * time.Minute), 417 Type: internal.OperationTypeDeprovision, 418 InstanceID: testInstance1.InstanceID, 419 State: domain.Succeeded, 420 Description: suspensionOpDesc2, 421 Temporary: true, 422 }, 423 }) 424 require.NoError(t, err) 425 426 err = operations.InsertProvisioningOperation(internal.ProvisioningOperation{ 427 Operation: internal.Operation{ 428 ID: unsuspensionOpId1, 429 Version: 0, 430 CreatedAt: time.Now().Add(3 * time.Hour), 431 UpdatedAt: time.Now().Add(3 * time.Hour).Add(5 * time.Minute), 432 Type: internal.OperationTypeProvision, 433 InstanceID: testInstance1.InstanceID, 434 State: domain.Failed, 435 Description: unsuspensionOpDesc1, 436 }, 437 }) 438 require.NoError(t, err) 439 440 err = operations.InsertProvisioningOperation(internal.ProvisioningOperation{ 441 Operation: internal.Operation{ 442 ID: unsuspensionOpId2, 443 Version: 0, 444 CreatedAt: time.Now().Add(4 * time.Hour), 445 UpdatedAt: time.Now().Add(4 * time.Hour).Add(5 * time.Minute), 446 Type: internal.OperationTypeProvision, 447 InstanceID: testInstance1.InstanceID, 448 State: domain.Succeeded, 449 Description: unsuspensionOpDesc2, 450 }, 451 }) 452 require.NoError(t, err) 453 454 err = operations.InsertDeprovisioningOperation(internal.DeprovisioningOperation{ 455 Operation: internal.Operation{ 456 ID: deprovisioningOpId, 457 Version: 0, 458 CreatedAt: time.Now().Add(5 * time.Hour), 459 UpdatedAt: time.Now().Add(5 * time.Hour).Add(5 * time.Minute), 460 Type: internal.OperationTypeDeprovision, 461 InstanceID: testInstance1.InstanceID, 462 State: domain.Succeeded, 463 Description: deprovisioningOpDesc, 464 }, 465 }) 466 require.NoError(t, err) 467 468 req, err := http.NewRequest("GET", "/info/runtimes", nil) 469 require.NoError(t, err) 470 471 responseWriter := httputil.NewResponseWriter(logger.NewLogDummy(), true) 472 runtimesInfoHandler := appinfo.NewRuntimeInfoHandler(instances, operations, broker.PlansConfig{}, "", responseWriter) 473 474 rr := httptest.NewRecorder() 475 router := mux.NewRouter() 476 router.Handle("/info/runtimes", runtimesInfoHandler) 477 478 // when 479 runtimesInfoHandler.ServeHTTP(rr, req) 480 481 // then 482 require.Equal(t, http.StatusOK, rr.Code) 483 484 var out []*appinfo.RuntimeDTO 485 486 err = json.Unmarshal(rr.Body.Bytes(), &out) 487 require.NoError(t, err) 488 489 assert.Equal(t, 1, len(out)) 490 assert.Equal(t, testInstance1.InstanceID, out[0].ServiceInstanceID) 491 assert.Equal(t, provisioningOpDesc, out[0].Status.Provisioning.Description) 492 assert.Equal(t, deprovisioningOpDesc, out[0].Status.Deprovisioning.Description) 493 494 }) 495 } 496 497 func assertJSONWithGoldenFile(t *testing.T, gotRawJSON []byte) { 498 t.Helper() 499 g := goldie.New(t, goldie.WithNameSuffix(".golden.json")) 500 501 var jsonGoType interface{} 502 require.NoError(t, json.Unmarshal(gotRawJSON, &jsonGoType)) 503 g.AssertJson(t, t.Name(), jsonGoType) 504 } 505 506 func fixTime() time.Time { 507 return time.Date(2020, 04, 21, 0, 0, 23, 42, time.UTC) 508 } 509 510 func fixInstance(idx int) internal.Instance { 511 return internal.Instance{ 512 InstanceID: fmt.Sprintf("InstanceID field. IDX: %d", idx), 513 RuntimeID: fmt.Sprintf("RuntimeID field. IDX: %d", idx), 514 GlobalAccountID: fmt.Sprintf("GlobalAccountID field. IDX: %d", idx), 515 SubAccountID: fmt.Sprintf("SubAccountID field. IDX: %d", idx), 516 ServiceID: fmt.Sprintf("ServiceID field. IDX: %d", idx), 517 ServiceName: fmt.Sprintf("ServiceName field. IDX: %d", idx), 518 ServicePlanID: fmt.Sprintf("ServicePlanID field. IDX: %d", idx), 519 ServicePlanName: fmt.Sprintf("ServicePlanName field. IDX: %d", idx), 520 DashboardURL: fmt.Sprintf("DashboardURL field. IDX: %d", idx), 521 Parameters: internal.ProvisioningParameters{ 522 PlatformRegion: fmt.Sprintf("region-value-idx-%d", idx), 523 }, 524 CreatedAt: fixTime().Add(time.Duration(idx) * time.Second), 525 UpdatedAt: fixTime().Add(time.Duration(idx) * time.Minute), 526 DeletedAt: fixTime().Add(time.Duration(idx) * time.Hour), 527 } 528 } 529 530 func newInMemoryStorage(t *testing.T, 531 instances []internal.Instance, 532 provisionOp []internal.ProvisioningOperation, 533 deprovisionOp []internal.DeprovisioningOperation) storage.BrokerStorage { 534 535 t.Helper() 536 memStorage := storage.NewMemoryStorage() 537 for _, i := range instances { 538 require.NoError(t, memStorage.Instances().Insert(i)) 539 } 540 for _, op := range provisionOp { 541 require.NoError(t, memStorage.Operations().InsertProvisioningOperation(op)) 542 } 543 for _, op := range deprovisionOp { 544 require.NoError(t, memStorage.Operations().InsertDeprovisioningOperation(op)) 545 } 546 547 return memStorage 548 } 549 550 func fixProvisionOperation(idx int) internal.ProvisioningOperation { 551 o := internal.ProvisioningOperation{ 552 Operation: fixSucceededOperation(internal.OperationTypeProvision, idx), 553 } 554 o.Type = internal.OperationTypeProvision 555 return o 556 } 557 func fixDeprovisionOperation(idx int) internal.DeprovisioningOperation { 558 return internal.DeprovisioningOperation{ 559 Operation: fixSucceededOperation(internal.OperationTypeDeprovision, idx), 560 } 561 } 562 563 func fixSucceededOperation(operationType internal.OperationType, idx int) internal.Operation { 564 return internal.Operation{ 565 ID: fmt.Sprintf("Operation %v ID field. IDX: %d", operationType, idx), 566 Version: 0, 567 CreatedAt: fixTime().Add(time.Duration(idx) * 24 * time.Hour), 568 UpdatedAt: fixTime().Add(time.Duration(idx) * 48 * time.Hour), 569 InstanceID: fmt.Sprintf("InstanceID field. IDX: %d", idx), 570 ProvisionerOperationID: fmt.Sprintf("ProvisionerOperationID field. IDX: %d", idx), 571 State: domain.Succeeded, 572 Description: fmt.Sprintf("esc for succeeded op.. IDX: %d", idx), 573 Type: operationType, 574 } 575 }