github.com/Axway/agent-sdk@v1.1.101/pkg/transaction/metric/metricscollector_test.go (about) 1 package metric 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "net/http" 8 "net/http/httptest" 9 "os" 10 "strings" 11 "sync" 12 "testing" 13 "time" 14 15 "github.com/Axway/agent-sdk/pkg/agent" 16 apiv1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1" 17 management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1" 18 defs "github.com/Axway/agent-sdk/pkg/apic/definitions" 19 "github.com/Axway/agent-sdk/pkg/cmd" 20 "github.com/Axway/agent-sdk/pkg/config" 21 "github.com/Axway/agent-sdk/pkg/traceability" 22 "github.com/Axway/agent-sdk/pkg/transaction/models" 23 "github.com/Axway/agent-sdk/pkg/util/healthcheck" 24 "github.com/stretchr/testify/assert" 25 ) 26 27 var ( 28 apiDetails1 = models.APIDetails{ 29 ID: "111", 30 Name: "111", 31 Revision: 1, 32 TeamID: teamID, 33 APIServiceInstance: "", 34 Stage: "", 35 Version: "", 36 } 37 apiDetails2 = models.APIDetails{ 38 ID: "111", 39 Name: "111", 40 Revision: 1, 41 TeamID: teamID, 42 APIServiceInstance: "", 43 Stage: "", 44 Version: "", 45 } 46 traceStatus = healthcheck.OK 47 ) 48 49 func getFutureTime() time.Time { 50 return time.Now().Add(10 * time.Minute) 51 } 52 53 func createCentralCfg(url, env string) *config.CentralConfiguration { 54 55 cfg := config.NewCentralConfig(config.TraceabilityAgent).(*config.CentralConfiguration) 56 cfg.URL = url 57 cfg.SingleURL = url 58 cfg.TenantID = "123456" 59 cfg.Environment = env 60 cfg.APICDeployment = "test" 61 authCfg := cfg.Auth.(*config.AuthConfiguration) 62 authCfg.URL = url + "/auth" 63 authCfg.Realm = "Broker" 64 authCfg.ClientID = "serviceaccount_1234" 65 authCfg.PrivateKey = "../../transaction/testdata/private_key.pem" 66 authCfg.PublicKey = "../../transaction/testdata/public_key" 67 usgCfg := cfg.UsageReporting.(*config.UsageReportingConfiguration) 68 usgCfg.Publish = true 69 metricCfg := cfg.MetricReporting.(*config.MetricReportingConfiguration) 70 metricCfg.Publish = true 71 // metricCfg.Schedule = "1 * * * * * *" 72 return cfg 73 } 74 75 var accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ0ZXN0IiwiaWF0IjoxNjE0NjA0NzE0LCJleHAiOjE2NDYxNDA3MTQsImF1ZCI6InRlc3RhdWQiLCJzdWIiOiIxMjM0NTYiLCJvcmdfZ3VpZCI6IjEyMzQtMTIzNC0xMjM0LTEyMzQifQ.5Uqt0oFhMgmI-sLQKPGkHwknqzlTxv-qs9I_LmZ18LQ" 76 77 var teamID = "team123" 78 79 type testHTTPServer struct { 80 lighthouseEventCount int 81 transactionCount int 82 transactionVolume int 83 failUsageEvent bool 84 failUsageResponse *UsageResponse 85 server *httptest.Server 86 reportCount int 87 givenGranularity int 88 eventTimestamp ISO8601Time 89 } 90 91 func (s *testHTTPServer) startServer() { 92 s.server = httptest.NewServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 93 if strings.Contains(req.RequestURI, "/auth") { 94 token := "{\"access_token\":\"" + accessToken + "\",\"expires_in\": 12235677}" 95 resp.Write([]byte(token)) 96 } 97 if strings.Contains(req.RequestURI, "/lighthouse") { 98 if s.failUsageEvent { 99 if s.failUsageResponse != nil { 100 b, _ := json.Marshal(*s.failUsageResponse) 101 resp.WriteHeader(s.failUsageResponse.StatusCode) 102 resp.Write(b) 103 return 104 } 105 resp.WriteHeader(http.StatusBadRequest) 106 return 107 } 108 s.lighthouseEventCount++ 109 req.ParseMultipartForm(1 << 20) 110 for _, fileHeaders := range req.MultipartForm.File { 111 for _, fileHeader := range fileHeaders { 112 file, err := fileHeader.Open() 113 if err != nil { 114 return 115 } 116 body, _ := io.ReadAll(file) 117 var usageEvent UsageEvent 118 json.Unmarshal(body, &usageEvent) 119 fmt.Printf("\n\n %+v \n\n", usageEvent) 120 for _, report := range usageEvent.Report { 121 for usageType, usage := range report.Usage { 122 if strings.Index(usageType, "Transactions") > 0 { 123 s.transactionCount += int(usage) 124 } else if strings.Index(usageType, "Volume") > 0 { 125 s.transactionVolume += int(usage) 126 } 127 } 128 s.reportCount++ 129 } 130 s.givenGranularity = usageEvent.Granularity 131 s.eventTimestamp = usageEvent.Timestamp 132 } 133 } 134 } 135 resp.WriteHeader(202) 136 })) 137 } 138 139 func (s *testHTTPServer) closeServer() { 140 if s.server != nil { 141 s.server.Close() 142 } 143 } 144 145 func (s *testHTTPServer) resetConfig() { 146 s.lighthouseEventCount = 0 147 s.transactionCount = 0 148 s.transactionVolume = 0 149 s.failUsageEvent = false 150 s.givenGranularity = 0 151 s.reportCount = 0 152 } 153 154 func (s *testHTTPServer) resetOffline(myCollector Collector) { 155 events := myCollector.(*collector).reports.loadEvents() 156 events.Report = make(map[string]UsageReport) 157 myCollector.(*collector).reports.updateEvents(events) 158 s.resetConfig() 159 } 160 161 func cleanUpCachedMetricFile() { 162 os.RemoveAll("./cache") 163 } 164 165 func generateMockReports(transactionPerReport []int) UsageEvent { 166 jsonStructure := `{"envId":"267bd671-e5e2-4679-bcc3-bbe7b70f30fd","timestamp":"2024-02-14T10:30:00+02:00","granularity":3600000,"schemaId":"http://127.0.0.1:53493/lighthouse/api/v1/report.schema.json","report":{},"meta":{"AgentName":"","AgentVersion":""}}` 167 var mockEvent UsageEvent 168 json.Unmarshal([]byte(jsonStructure), &mockEvent) 169 startDate := time.Time(mockEvent.Timestamp) 170 nextTime := func(i int) string { 171 next := startDate.Add(time.Hour * time.Duration(-i-1)) 172 return next.Format(ISO8601) 173 } 174 for i, transaction := range transactionPerReport { 175 mockEvent.Report[nextTime(i)] = UsageReport{ 176 Product: "Azure", 177 Usage: map[string]int64{"Azure.Transactions": int64(transaction)}, 178 } 179 } 180 return mockEvent 181 } 182 183 func cleanUpReportfiles() { 184 os.RemoveAll("./reports") 185 } 186 187 func createRI(group, kind, id, name string, subRes map[string]interface{}) *apiv1.ResourceInstance { 188 return &apiv1.ResourceInstance{ 189 ResourceMeta: apiv1.ResourceMeta{ 190 Metadata: apiv1.Metadata{ 191 ID: id, 192 }, 193 GroupVersionKind: apiv1.GroupVersionKind{ 194 GroupKind: apiv1.GroupKind{ 195 Group: group, 196 Kind: kind, 197 }, 198 }, 199 SubResources: subRes, 200 Name: name, 201 }, 202 } 203 } 204 205 func createAPIServiceInstance(id, name string, apiID string) *apiv1.ResourceInstance { 206 sub := map[string]interface{}{ 207 defs.XAgentDetails: map[string]interface{}{ 208 defs.AttrExternalAPIID: apiID, 209 }, 210 } 211 return createRI(management.APIServiceInstanceGVK().Group, management.APIServiceInstanceGVK().Kind, id, name, sub) 212 } 213 214 func createManagedApplication(id, name, consumerOrgID string) *apiv1.ResourceInstance { 215 var marketplaceSubRes map[string]interface{} 216 if consumerOrgID != "" { 217 marketplaceSubRes = map[string]interface{}{ 218 "marketplace": management.ManagedApplicationMarketplace{ 219 Name: name, 220 Resource: management.ManagedApplicationMarketplaceResource{ 221 Owner: &apiv1.Owner{ 222 Organization: apiv1.Organization{ 223 ID: consumerOrgID, 224 }, 225 }, 226 }, 227 }, 228 } 229 } 230 return createRI(management.ManagedApplicationGVK().Group, management.ManagedApplicationGVK().Kind, id, name, marketplaceSubRes) 231 } 232 233 func createAccessRequest(id, name, appName, instanceID, instanceName, subscriptionName string) *apiv1.ResourceInstance { 234 ar := &management.AccessRequest{ 235 ResourceMeta: apiv1.ResourceMeta{ 236 Metadata: apiv1.Metadata{ 237 ID: id, 238 References: []apiv1.Reference{ 239 { 240 Group: management.APIServiceInstanceGVK().Group, 241 Kind: management.APIServiceInstanceGVK().Kind, 242 ID: instanceID, 243 Name: instanceName, 244 }, 245 }, 246 }, 247 Name: name, 248 }, 249 Spec: management.AccessRequestSpec{ 250 ManagedApplication: appName, 251 ApiServiceInstance: instanceName, 252 }, 253 References: []interface{}{ 254 management.AccessRequestReferencesSubscription{ 255 Kind: defs.Subscription, 256 Name: "catalog/" + subscriptionName, 257 }, 258 }, 259 } 260 ri, _ := ar.AsInstance() 261 return ri 262 } 263 264 func runTestHealthcheck() { 265 // register a healthcheck 266 healthcheck.RegisterHealthcheck("Traceability", traceability.HealthCheckEndpoint, 267 func(name string) *healthcheck.Status { 268 return &healthcheck.Status{Result: traceStatus} 269 }, 270 ) 271 healthcheck.RunChecks() 272 } 273 274 func TestMetricCollector(t *testing.T) { 275 defer cleanUpCachedMetricFile() 276 s := &testHTTPServer{} 277 defer s.closeServer() 278 s.startServer() 279 traceability.SetDataDirPath(".") 280 281 cfg := createCentralCfg(s.server.URL, "demo") 282 cfg.UsageReporting.(*config.UsageReportingConfiguration).URL = s.server.URL + "/lighthouse" 283 cfg.MetricReporting.(*config.MetricReportingConfiguration).Publish = true 284 cfg.SetEnvironmentID("267bd671-e5e2-4679-bcc3-bbe7b70f30fd") 285 cmd.BuildDataPlaneType = "Azure" 286 agent.Initialize(cfg) 287 288 cm := agent.GetCacheManager() 289 cm.AddAPIServiceInstance(createAPIServiceInstance("inst-1", "instance-1", "111")) 290 291 cm.AddManagedApplication(createManagedApplication("app-1", "managed-app-1", "")) 292 cm.AddManagedApplication(createManagedApplication("app-2", "managed-app-2", "test-consumer-org")) 293 294 cm.AddAccessRequest(createAccessRequest("ac-1", "access-req-1", "managed-app-1", "inst-1", "instance-1", "subscription-1")) 295 cm.AddAccessRequest(createAccessRequest("ac-2", "access-req-2", "managed-app-2", "inst-1", "instance-1", "subscription-2")) 296 297 myCollector := createMetricCollector() 298 metricCollector := myCollector.(*collector) 299 300 testCases := []struct { 301 name string 302 loopCount int 303 retryBatchCount int 304 apiTransactionCount []int 305 failUsageEventOnServer []bool 306 failUsageResponseOnServer []*UsageResponse 307 expectedLHEvents []int 308 expectedTransactionCount []int 309 trackVolume bool 310 expectedTransactionVolume []int 311 expectedMetricEventsAcked int 312 appName string 313 publishPrior bool 314 hcStatus healthcheck.StatusLevel 315 }{ 316 // Success case with no app detail 317 { 318 name: "WithUsage", 319 loopCount: 1, 320 retryBatchCount: 0, 321 apiTransactionCount: []int{5}, 322 failUsageEventOnServer: []bool{false}, 323 failUsageResponseOnServer: []*UsageResponse{nil}, 324 expectedLHEvents: []int{1}, 325 expectedTransactionCount: []int{5}, 326 trackVolume: false, 327 expectedTransactionVolume: []int{0}, 328 expectedMetricEventsAcked: 1, // API metric + no Provider subscription metric 329 }, 330 { 331 name: "WithUsageWithPriorPublish", 332 loopCount: 1, 333 retryBatchCount: 0, 334 apiTransactionCount: []int{5}, 335 failUsageEventOnServer: []bool{false}, 336 failUsageResponseOnServer: []*UsageResponse{nil}, 337 expectedLHEvents: []int{1}, 338 expectedTransactionCount: []int{5}, 339 trackVolume: false, 340 expectedTransactionVolume: []int{0}, 341 expectedMetricEventsAcked: 1, // API metric + no Provider subscription metric 342 publishPrior: true, 343 }, 344 // Success case 345 { 346 name: "WithUsage", 347 loopCount: 1, 348 retryBatchCount: 0, 349 apiTransactionCount: []int{5}, 350 failUsageEventOnServer: []bool{false}, 351 failUsageResponseOnServer: []*UsageResponse{nil}, 352 expectedLHEvents: []int{1}, 353 expectedTransactionCount: []int{5}, 354 trackVolume: false, 355 expectedTransactionVolume: []int{0}, 356 expectedMetricEventsAcked: 1, // API metric + Provider subscription metric 357 appName: "managed-app-1", 358 }, 359 // Success case with consumer metric event 360 { 361 name: "WithUsage", 362 loopCount: 1, 363 retryBatchCount: 0, 364 apiTransactionCount: []int{5}, 365 failUsageEventOnServer: []bool{false}, 366 failUsageResponseOnServer: []*UsageResponse{nil}, 367 expectedLHEvents: []int{1}, 368 expectedTransactionCount: []int{5}, 369 trackVolume: false, 370 expectedTransactionVolume: []int{0}, 371 expectedMetricEventsAcked: 1, // API metric + Provider + Consumer subscription metric 372 appName: "managed-app-2", 373 }, 374 // Success case with no usage report 375 { 376 name: "WithUsageNoUsageReport", 377 loopCount: 1, 378 retryBatchCount: 0, 379 apiTransactionCount: []int{0}, 380 failUsageEventOnServer: []bool{false}, 381 failUsageResponseOnServer: []*UsageResponse{nil}, 382 expectedLHEvents: []int{0}, 383 expectedTransactionCount: []int{0}, 384 trackVolume: false, 385 expectedTransactionVolume: []int{0}, 386 expectedMetricEventsAcked: 0, 387 }, 388 // Test case with failing request to LH, the subsequent successful request should contain the total count since initial failure 389 { 390 name: "WithUsageWithFailure", 391 loopCount: 3, 392 retryBatchCount: 0, 393 apiTransactionCount: []int{5, 10, 12}, 394 failUsageEventOnServer: []bool{false, true, false, false}, 395 failUsageResponseOnServer: []*UsageResponse{ 396 nil, { 397 Description: "Regular failure", 398 StatusCode: 400, 399 Success: false, 400 }, 401 nil, 402 nil, 403 }, 404 expectedLHEvents: []int{1, 1, 2}, 405 expectedTransactionCount: []int{5, 5, 17}, 406 trackVolume: true, 407 expectedTransactionVolume: []int{50, 50, 170}, 408 expectedMetricEventsAcked: 1, 409 appName: "unknown", 410 }, 411 // Test case with failing request to LH, no subsequent request triggered. 412 { 413 name: "WithUsageWithFailureWithSpecificDescription", 414 loopCount: 3, 415 retryBatchCount: 0, 416 apiTransactionCount: []int{1, 1, 1}, 417 failUsageEventOnServer: []bool{true, true, false}, 418 failUsageResponseOnServer: []*UsageResponse{ 419 { 420 Description: "The file exceeds the maximum upload size of 454545", 421 StatusCode: 400, 422 Success: false, 423 }, 424 { 425 Description: "Environment ID not found", 426 StatusCode: 404, 427 Success: false, 428 }, 429 nil, 430 }, 431 expectedLHEvents: []int{0, 0, 1}, 432 expectedTransactionCount: []int{0, 0, 1}, 433 trackVolume: true, 434 expectedTransactionVolume: []int{0, 0, 10}, 435 expectedMetricEventsAcked: 1, 436 appName: "unknown", 437 }, 438 // Retry limit hit 439 { 440 name: "WithUsageAndFailedMetric", 441 loopCount: 1, 442 retryBatchCount: 4, 443 apiTransactionCount: []int{5}, 444 failUsageEventOnServer: []bool{false}, 445 failUsageResponseOnServer: []*UsageResponse{nil}, 446 expectedLHEvents: []int{1}, 447 expectedTransactionCount: []int{5}, 448 trackVolume: false, 449 expectedTransactionVolume: []int{0}, 450 expectedMetricEventsAcked: 0, 451 }, 452 // Traceability healthcheck failing, nothing reported 453 { 454 name: "WithUsageTraceabilityNotConnected", 455 loopCount: 1, 456 retryBatchCount: 0, 457 apiTransactionCount: []int{0}, 458 failUsageEventOnServer: []bool{false}, 459 failUsageResponseOnServer: []*UsageResponse{nil}, 460 expectedLHEvents: []int{0}, 461 expectedTransactionCount: []int{0}, 462 trackVolume: false, 463 expectedTransactionVolume: []int{0}, 464 expectedMetricEventsAcked: 0, // API metric + Provider subscription metric 465 appName: "managed-app-1", 466 hcStatus: healthcheck.FAIL, 467 }, 468 } 469 470 for _, test := range testCases { 471 t.Run(test.name, func(t *testing.T) { 472 if test.hcStatus != "" { 473 traceStatus = test.hcStatus 474 } 475 runTestHealthcheck() 476 477 cfg.SetAxwayManaged(test.trackVolume) 478 setupMockClient(test.retryBatchCount) 479 for l := 0; l < test.loopCount; l++ { 480 fmt.Printf("\n\nTransaction Info: %+v\n\n", test.apiTransactionCount[l]) 481 for i := 0; i < test.apiTransactionCount[l]; i++ { 482 metricDetail := Detail{ 483 APIDetails: apiDetails1, 484 StatusCode: "200", 485 Duration: 10, 486 Bytes: 10, 487 AppDetails: models.AppDetails{ 488 ID: "111", 489 Name: test.appName, 490 }, 491 } 492 metricCollector.AddMetricDetail(metricDetail) 493 } 494 s.failUsageEvent = test.failUsageEventOnServer[l] 495 s.failUsageResponse = test.failUsageResponseOnServer[l] 496 if test.publishPrior { 497 metricCollector.usagePublisher.Execute() 498 metricCollector.Execute() 499 } else { 500 metricCollector.Execute() 501 metricCollector.usagePublisher.Execute() 502 } 503 assert.Equal(t, test.expectedMetricEventsAcked, myMockClient.(*MockClient).eventsAcked) 504 } 505 s.resetConfig() 506 }) 507 } 508 } 509 510 func TestConcurrentMetricCollectorEvents(t *testing.T) { 511 // this test has no assertions it is to ensure concurrent map writs do not occur while collecting metrics 512 defer cleanUpCachedMetricFile() 513 s := &testHTTPServer{} 514 defer s.closeServer() 515 s.startServer() 516 traceability.SetDataDirPath(".") 517 518 cfg := createCentralCfg(s.server.URL, "demo") 519 cfg.UsageReporting.(*config.UsageReportingConfiguration).URL = s.server.URL + "/lighthouse" 520 cfg.MetricReporting.(*config.MetricReportingConfiguration).Publish = true 521 cfg.SetEnvironmentID("267bd671-e5e2-4679-bcc3-bbe7b70f30fd") 522 cmd.BuildDataPlaneType = "Azure" 523 agent.Initialize(cfg) 524 myCollector := createMetricCollector() 525 metricCollector := myCollector.(*collector) 526 traceStatus = healthcheck.OK 527 runTestHealthcheck() 528 529 apiDetails := []models.APIDetails{ 530 {ID: "000", Name: "000", Revision: 1, TeamID: teamID}, 531 {ID: "111", Name: "111", Revision: 1, TeamID: teamID}, 532 {ID: "222", Name: "222", Revision: 1, TeamID: teamID}, 533 {ID: "333", Name: "333", Revision: 1, TeamID: teamID}, 534 {ID: "444", Name: "444", Revision: 1, TeamID: teamID}, 535 {ID: "555", Name: "555", Revision: 1, TeamID: teamID}, 536 {ID: "666", Name: "666", Revision: 1, TeamID: teamID}, 537 {ID: "777", Name: "777", Revision: 1, TeamID: teamID}, 538 {ID: "888", Name: "888", Revision: 1, TeamID: teamID}, 539 {ID: "999", Name: "999", Revision: 1, TeamID: teamID}, 540 } 541 appDetails := []models.AppDetails{ 542 {ID: "000", Name: "app0"}, 543 {ID: "111", Name: "app1"}, 544 {ID: "222", Name: "app2"}, 545 {ID: "333", Name: "app3"}, 546 {ID: "444", Name: "app4"}, 547 {ID: "555", Name: "app5"}, 548 {ID: "666", Name: "app6"}, 549 {ID: "777", Name: "app7"}, 550 {ID: "888", Name: "app8"}, 551 {ID: "999", Name: "app9"}, 552 } 553 554 codes := []string{"200", "201", "202", "204", "205", "206", "300", "301", "400", "401", "403", "404", "500"} 555 556 details := []Detail{} 557 558 // load a bunch of different api details 559 for _, api := range apiDetails { 560 for _, app := range appDetails { 561 for _, code := range codes { 562 details = append(details, Detail{APIDetails: api, AppDetails: app, StatusCode: code}) 563 } 564 } 565 } 566 567 // add all metrics via go routines 568 wg := sync.WaitGroup{} 569 transactionCount := 100 570 wg.Add(len(details) * transactionCount) 571 572 for j := range details { 573 for i := 0; i < transactionCount; i++ { 574 go func(dets Detail) { 575 defer wg.Done() 576 metricCollector.AddMetricDetail(dets) 577 }(details[j]) 578 } 579 } 580 581 wg.Wait() 582 } 583 584 func TestMetricCollectorUsageAggregation(t *testing.T) { 585 defer cleanUpCachedMetricFile() 586 s := &testHTTPServer{} 587 defer s.closeServer() 588 s.startServer() 589 traceability.SetDataDirPath(".") 590 591 cfg := createCentralCfg(s.server.URL, "demo") 592 cfg.UsageReporting.(*config.UsageReportingConfiguration).URL = s.server.URL + "/lighthouse" 593 cfg.MetricReporting.(*config.MetricReportingConfiguration).Publish = true 594 cfg.SetEnvironmentID("267bd671-e5e2-4679-bcc3-bbe7b70f30fd") 595 cmd.BuildDataPlaneType = "Azure" 596 agent.Initialize(cfg) 597 598 traceStatus = healthcheck.OK 599 runTestHealthcheck() 600 601 testCases := []struct { 602 name string 603 transactionsPerReport []int 604 expectedTransactionCount int 605 expectedTransactionVolume int 606 expectedGranularity int 607 expectedReportCount int 608 }{ 609 { 610 name: "FourReports", 611 transactionsPerReport: []int{3, 4, 5, 6}, 612 expectedTransactionCount: 18, 613 expectedGranularity: 4 * int(time.Hour/time.Millisecond), 614 }, 615 { 616 name: "SevenReports", 617 transactionsPerReport: []int{1, 2, 3, 4, 5, 6, 7}, 618 expectedTransactionCount: 28, 619 expectedGranularity: 7 * int(time.Hour/time.Millisecond), 620 }, 621 { 622 name: "OneReport", 623 transactionsPerReport: []int{1}, 624 expectedTransactionCount: 1, 625 expectedGranularity: 1 * int(time.Hour/time.Millisecond), 626 }, 627 } 628 629 for _, test := range testCases { 630 t.Run(test.name, func(t *testing.T) { 631 cfg.SetAxwayManaged(false) 632 setupMockClient(0) 633 myCollector := createMetricCollector() 634 metricCollector := myCollector.(*collector) 635 metricCollector.usagePublisher.schedule = "* * * * *" 636 metricCollector.usagePublisher.report.currTimeFunc = getFutureTime 637 638 mockReports := generateMockReports(test.transactionsPerReport) 639 b, _ := json.Marshal(mockReports) 640 metricCollector.reports.reportCache.Set("lighthouse_events", string(b)) 641 now = func() time.Time { 642 return time.Time(mockReports.Timestamp) 643 } 644 metricCollector.usagePublisher.Execute() 645 assert.Equal(t, test.expectedTransactionCount, s.transactionCount) 646 assert.Equal(t, 1, s.reportCount) 647 assert.Equal(t, test.expectedGranularity, s.givenGranularity) 648 assert.Equal(t, ISO8601Time(now()), s.eventTimestamp) 649 s.resetConfig() 650 }) 651 } 652 cleanUpReportfiles() 653 } 654 655 func TestMetricCollectorCache(t *testing.T) { 656 defer cleanUpCachedMetricFile() 657 s := &testHTTPServer{} 658 defer s.closeServer() 659 s.startServer() 660 661 traceStatus = healthcheck.OK 662 runTestHealthcheck() 663 664 testCases := []struct { 665 name string 666 trackVolume bool 667 }{ 668 { 669 name: "UsageOnly", 670 trackVolume: false, 671 }, 672 { 673 name: "UsageAndVolume", 674 trackVolume: true, 675 }, 676 } 677 678 for _, test := range testCases { 679 t.Run(test.name, func(t *testing.T) { 680 cfg := createCentralCfg(s.server.URL, "demo") 681 cfg.UsageReporting.(*config.UsageReportingConfiguration).URL = s.server.URL + "/lighthouse" 682 cfg.SetEnvironmentID("267bd671-e5e2-4679-bcc3-bbe7b70f30fd") 683 cfg.SetAxwayManaged(test.trackVolume) 684 cmd.BuildDataPlaneType = "Azure" 685 agent.Initialize(cfg) 686 687 traceability.SetDataDirPath(".") 688 myCollector := createMetricCollector() 689 metricCollector := myCollector.(*collector) 690 metricCollector.usagePublisher.schedule = "* * * * *" 691 metricCollector.usagePublisher.report.currTimeFunc = getFutureTime 692 693 metricCollector.AddMetric(apiDetails1, "200", 5, 10, "") 694 metricCollector.AddMetric(apiDetails1, "200", 10, 10, "") 695 metricCollector.Execute() 696 metricCollector.usagePublisher.Execute() 697 metricCollector.AddMetric(apiDetails1, "401", 15, 10, "") 698 metricCollector.AddMetric(apiDetails2, "200", 20, 10, "") 699 metricCollector.AddMetric(apiDetails2, "200", 10, 10, "") 700 701 // No event generation/publish, store the cache 702 metricCollector.storage.save() 703 // Validate only one usage report sent with first 2 transactions 704 assert.Equal(t, 1, s.lighthouseEventCount) 705 assert.Equal(t, 2, s.transactionCount) 706 if test.trackVolume { 707 assert.Equal(t, 20, s.transactionVolume) 708 } 709 s.resetConfig() 710 711 // Recreate the collector that loads the stored metrics, so 3 transactions 712 myCollector = createMetricCollector() 713 metricCollector = myCollector.(*collector) 714 metricCollector.usagePublisher.schedule = "* * * * *" 715 metricCollector.usagePublisher.report.currTimeFunc = getFutureTime 716 717 metricCollector.AddMetric(apiDetails1, "200", 5, 10, "") 718 metricCollector.AddMetric(apiDetails1, "200", 10, 10, "") 719 metricCollector.AddMetric(apiDetails1, "401", 15, 10, "") 720 metricCollector.AddMetric(apiDetails2, "200", 20, 10, "") 721 metricCollector.AddMetric(apiDetails2, "200", 10, 10, "") 722 723 metricCollector.Execute() 724 metricCollector.usagePublisher.Execute() 725 // Validate only one usage report sent with 3 previous transactions and 5 new transactions 726 assert.Equal(t, 1, s.lighthouseEventCount) 727 assert.Equal(t, 8, s.transactionCount) 728 if test.trackVolume { 729 assert.Equal(t, 80, s.transactionVolume) 730 } 731 732 s.resetConfig() 733 // Recreate the collector that loads the stored metrics, 0 transactions 734 myCollector = createMetricCollector() 735 metricCollector = myCollector.(*collector) 736 metricCollector.usagePublisher.schedule = "* * * * *" 737 metricCollector.usagePublisher.report.currTimeFunc = getFutureTime 738 739 metricCollector.Execute() 740 // Validate only no usage report sent as no previous or new transactions 741 assert.Equal(t, 0, s.lighthouseEventCount) 742 assert.Equal(t, 0, s.transactionCount) 743 if test.trackVolume { 744 assert.Equal(t, 0, s.transactionVolume) 745 } 746 }) 747 } 748 } 749 750 func TestOfflineMetricCollector(t *testing.T) { 751 defer cleanUpCachedMetricFile() 752 s := &testHTTPServer{} 753 defer s.closeServer() 754 s.startServer() 755 traceability.SetDataDirPath(".") 756 757 traceStatus = healthcheck.OK 758 runTestHealthcheck() 759 760 cfg := createCentralCfg(s.server.URL, "demo") 761 cfg.UsageReporting.(*config.UsageReportingConfiguration).URL = s.server.URL + "/lighthouse" 762 cfg.EnvironmentID = "267bd671-e5e2-4679-bcc3-bbe7b70f30fd" 763 cmd.BuildDataPlaneType = "Azure" 764 usgCfg := cfg.UsageReporting.(*config.UsageReportingConfiguration) 765 usgCfg.Offline = true 766 agent.Initialize(cfg) 767 768 testCases := []struct { 769 name string 770 loopCount int 771 apiTransactionCount []int 772 generateReport bool 773 }{ 774 { 775 name: "NoReports", 776 loopCount: 0, 777 apiTransactionCount: []int{}, 778 generateReport: true, 779 }, 780 { 781 name: "OneReport", 782 loopCount: 1, 783 apiTransactionCount: []int{10}, 784 generateReport: true, 785 }, 786 { 787 name: "ThreeReports", 788 loopCount: 3, 789 apiTransactionCount: []int{5, 10, 2}, 790 generateReport: true, 791 }, 792 { 793 name: "ThreeReportsNoUsage", 794 loopCount: 3, 795 apiTransactionCount: []int{0, 0, 0}, 796 generateReport: true, 797 }, 798 { 799 name: "SixReports", 800 loopCount: 6, 801 apiTransactionCount: []int{5, 10, 2, 0, 3, 9}, 802 generateReport: true, 803 }, 804 } 805 806 for testNum, test := range testCases { 807 t.Run(test.name, func(t *testing.T) { 808 startDate := time.Date(2021, 1, 31, 12, 30, 0, 0, time.Local) 809 setupMockClient(0) 810 testLoops := 0 811 now = func() time.Time { 812 next := startDate.Add(time.Hour * time.Duration(testLoops)) 813 fmt.Println(next.Format(ISO8601)) 814 return next 815 } 816 817 validateEvents := func(report UsageEvent) { 818 for j := 0; j < test.loopCount; j++ { 819 reportKey := startDate.Add(time.Duration(j-1) * time.Hour).Format(ISO8601) 820 assert.Equal(t, cmd.BuildDataPlaneType, report.Report[reportKey].Product) 821 assert.Equal(t, test.apiTransactionCount[j], int(report.Report[reportKey].Usage[cmd.BuildDataPlaneType+".Transactions"])) 822 } 823 // validate granularity when reports not empty 824 if test.loopCount != 0 { 825 assert.Equal(t, int(time.Hour.Milliseconds()), report.Granularity) 826 cfg.UsageReporting.(*config.UsageReportingConfiguration).URL = s.server.URL + "/lighthouse" 827 assert.Equal(t, cfg.UsageReporting.GetURL()+schemaPath, report.SchemaID) 828 assert.Equal(t, cfg.GetEnvironmentID(), report.EnvID) 829 } 830 } 831 832 myCollector := createMetricCollector() 833 metricCollector := myCollector.(*collector) 834 835 reportGenerator := metricCollector.reports 836 publisher := metricCollector.usagePublisher 837 for testLoops < test.loopCount { 838 for i := 0; i < test.apiTransactionCount[testLoops]; i++ { 839 metricCollector.AddMetric(apiDetails1, "200", 10, 10, "") 840 } 841 metricCollector.Execute() 842 testLoops++ 843 } 844 845 // Get the usage reports from the cache and validate 846 events := myCollector.(*collector).reports.loadEvents() 847 validateEvents(events) 848 849 // generate the report file 850 publisher.Execute() 851 852 expectedFile := reportGenerator.generateReportPath(ISO8601Time(startDate), testNum-1) 853 if test.loopCount == 0 { 854 // no report expected, end the test here 855 expectedFile = reportGenerator.generateReportPath(ISO8601Time(startDate), 0) 856 assert.NoFileExists(t, expectedFile) 857 return 858 } 859 860 // validate the file exists and open it 861 assert.FileExists(t, expectedFile) 862 data, err := os.ReadFile(expectedFile) 863 assert.Nil(t, err) 864 865 // unmarshall it 866 var reportEvents UsageEvent 867 err = json.Unmarshal(data, &reportEvents) 868 assert.Nil(t, err) 869 assert.NotNil(t, reportEvents) 870 871 // validate event in generated reports 872 validateEvents(reportEvents) 873 874 s.resetOffline(myCollector) 875 }) 876 } 877 cleanUpReportfiles() 878 }