diff --git a/storage/store/database/database.go b/storage/store/database/database.go index 940c14c4..b2d89d73 100644 --- a/storage/store/database/database.go +++ b/storage/store/database/database.go @@ -169,8 +169,8 @@ func (s *Store) GetAllServiceStatuses(params *paging.ServiceStatusParams) map[st } // GetServiceStatus returns the service status for a given service name in the given group -func (s *Store) GetServiceStatus(groupName, serviceName string, parameters *paging.ServiceStatusParams) *core.ServiceStatus { - return s.GetServiceStatusByKey(util.ConvertGroupAndServiceToKey(groupName, serviceName), parameters) +func (s *Store) GetServiceStatus(groupName, serviceName string, params *paging.ServiceStatusParams) *core.ServiceStatus { + return s.GetServiceStatusByKey(util.ConvertGroupAndServiceToKey(groupName, serviceName), params) } // GetServiceStatusByKey returns the service status for a given key @@ -468,9 +468,9 @@ func (s *Store) getServiceStatusByKey(tx *sql.Tx, key string, parameters *paging } if parameters.IncludeUptime { now := time.Now() - serviceStatus.Uptime.LastHour, _, _ = s.getServiceUptime(tx, serviceID, now.Add(-time.Hour), now) - serviceStatus.Uptime.LastTwentyFourHours, _, _ = s.getServiceUptime(tx, serviceID, now.Add(-24*time.Hour), now) - serviceStatus.Uptime.LastSevenDays, _, _ = s.getServiceUptime(tx, serviceID, now.Add(-7*24*time.Hour), now) + serviceStatus.Uptime.LastHour, _, err = s.getServiceUptime(tx, serviceID, now.Add(-time.Hour), now) + serviceStatus.Uptime.LastTwentyFourHours, _, err = s.getServiceUptime(tx, serviceID, now.Add(-24*time.Hour), now) + serviceStatus.Uptime.LastSevenDays, _, err = s.getServiceUptime(tx, serviceID, now.Add(-7*24*time.Hour), now) } return serviceStatus, nil } diff --git a/storage/store/database/database_test.go b/storage/store/database/database_test.go index 6b6ef69d..2dc43bcc 100644 --- a/storage/store/database/database_test.go +++ b/storage/store/database/database_test.go @@ -1,7 +1,6 @@ package database import ( - "fmt" "testing" "time" @@ -14,7 +13,7 @@ var ( secondCondition = core.Condition("[RESPONSE_TIME] < 500") thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h") - timestamp = time.Now() + now = time.Now() testService = core.Service{ Name: "name", @@ -36,7 +35,7 @@ var ( Errors: nil, Connected: true, Success: true, - Timestamp: timestamp, + Timestamp: now, Duration: 150 * time.Millisecond, CertificateExpiration: 10 * time.Hour, ConditionResults: []*core.ConditionResult{ @@ -61,7 +60,7 @@ var ( Errors: []string{"error-1", "error-2"}, Connected: true, Success: false, - Timestamp: timestamp, + Timestamp: now, Duration: 750 * time.Millisecond, CertificateExpiration: 10 * time.Hour, ConditionResults: []*core.ConditionResult{ @@ -95,58 +94,6 @@ func TestNewStore(t *testing.T) { } } -func TestStore_Insert(t *testing.T) { - store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_Insert.db") - defer store.Close() - store.Insert(&testService, &testSuccessfulResult) - store.Insert(&testService, &testUnsuccessfulResult) - - key := fmt.Sprintf("%s_%s", testService.Group, testService.Name) - serviceStatus := store.GetServiceStatusByKey(key, paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) - if serviceStatus == nil { - t.Fatalf("Store should've had key '%s', but didn't", key) - } - if len(serviceStatus.Results) != 2 { - t.Fatalf("Service '%s' should've had 2 results, but actually returned %d", serviceStatus.Name, len(serviceStatus.Results)) - } - for i, r := range serviceStatus.Results { - expectedResult := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()).Results[i] - if r.HTTPStatus != expectedResult.HTTPStatus { - t.Errorf("Result at index %d should've had a HTTPStatus of %d, but was actually %d", i, expectedResult.HTTPStatus, r.HTTPStatus) - } - if r.DNSRCode != expectedResult.DNSRCode { - t.Errorf("Result at index %d should've had a DNSRCode of %s, but was actually %s", i, expectedResult.DNSRCode, r.DNSRCode) - } - if r.Hostname != expectedResult.Hostname { - t.Errorf("Result at index %d should've had a Hostname of %s, but was actually %s", i, expectedResult.Hostname, r.Hostname) - } - if r.IP != expectedResult.IP { - t.Errorf("Result at index %d should've had a IP of %s, but was actually %s", i, expectedResult.IP, r.IP) - } - if r.Connected != expectedResult.Connected { - t.Errorf("Result at index %d should've had a Connected value of %t, but was actually %t", i, expectedResult.Connected, r.Connected) - } - if r.Duration != expectedResult.Duration { - t.Errorf("Result at index %d should've had a Duration of %s, but was actually %s", i, expectedResult.Duration.String(), r.Duration.String()) - } - if len(r.Errors) != len(expectedResult.Errors) { - t.Errorf("Result at index %d should've had %d errors, but actually had %d errors", i, len(expectedResult.Errors), len(r.Errors)) - } - if len(r.ConditionResults) != len(expectedResult.ConditionResults) { - t.Errorf("Result at index %d should've had %d ConditionResults, but actually had %d ConditionResults", i, len(expectedResult.ConditionResults), len(r.ConditionResults)) - } - if r.Success != expectedResult.Success { - t.Errorf("Result at index %d should've had a Success of %t, but was actually %t", i, expectedResult.Success, r.Success) - } - if r.Timestamp != expectedResult.Timestamp { - t.Errorf("Result at index %d should've had a Timestamp of %s, but was actually %s", i, expectedResult.Timestamp.String(), r.Timestamp.String()) - } - if r.CertificateExpiration != expectedResult.CertificateExpiration { - t.Errorf("Result at index %d should've had a CertificateExpiration of %s, but was actually %s", i, expectedResult.CertificateExpiration.String(), r.CertificateExpiration.String()) - } - } -} - func TestStore_InsertCleansUpOldUptimeEntriesProperly(t *testing.T) { store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_InsertCleansUpOldUptimeEntriesProperly.db") defer store.Close() @@ -220,165 +167,3 @@ func TestStore_InsertCleansUpEventsAndResultsProperly(t *testing.T) { } store.Clear() } - -func TestStore_GetServiceStatus(t *testing.T) { - store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_GetServiceStatus.db") - defer store.Close() - firstResult := testSuccessfulResult - firstResult.Timestamp = timestamp.Add(-time.Minute) - secondResult := testUnsuccessfulResult - secondResult.Timestamp = timestamp - store.Insert(&testService, &firstResult) - store.Insert(&testService, &secondResult) - - serviceStatus := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) - if serviceStatus == nil { - t.Fatalf("serviceStatus shouldn't have been nil") - } - if serviceStatus.Uptime == nil { - t.Fatalf("serviceStatus.Uptime shouldn't have been nil") - } - if len(serviceStatus.Results) != 2 { - t.Fatalf("serviceStatus.Results should've had 2 entries") - } - if serviceStatus.Results[0].Timestamp.After(serviceStatus.Results[1].Timestamp) { - t.Error("The result at index 0 should've been older than the result at index 1") - } - if serviceStatus.Uptime.LastHour != 0.5 { - t.Errorf("serviceStatus.Uptime.LastHour should've been 0.5") - } - if serviceStatus.Uptime.LastTwentyFourHours != 0.5 { - t.Errorf("serviceStatus.Uptime.LastTwentyFourHours should've been 0.5") - } - if serviceStatus.Uptime.LastSevenDays != 0.5 { - t.Errorf("serviceStatus.Uptime.LastSevenDays should've been 0.5") - } -} - -func TestStore_GetServiceStatusForMissingStatusReturnsNil(t *testing.T) { - store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_GetServiceStatusForMissingStatusReturnsNil.db") - defer store.Close() - store.Insert(&testService, &testSuccessfulResult) - - serviceStatus := store.GetServiceStatus("nonexistantgroup", "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) - if serviceStatus != nil { - t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, testService.Name) - } - serviceStatus = store.GetServiceStatus(testService.Group, "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) - if serviceStatus != nil { - t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, "nonexistantname") - } - serviceStatus = store.GetServiceStatus("nonexistantgroup", testService.Name, paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) - if serviceStatus != nil { - t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", "nonexistantgroup", testService.Name) - } -} - -func TestStore_GetServiceStatusPage1IsHasMoreRecentResultsThanPage2(t *testing.T) { - store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_GetServiceStatusPage1IsHasMoreRecentResultsThanPage2.db") - defer store.Close() - firstResult := testSuccessfulResult - firstResult.Timestamp = timestamp.Add(-time.Minute) - secondResult := testUnsuccessfulResult - secondResult.Timestamp = timestamp - store.Insert(&testService, &firstResult) - store.Insert(&testService, &secondResult) - - serviceStatusPage1 := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, 1)) - if serviceStatusPage1 == nil { - t.Fatalf("serviceStatusPage1 shouldn't have been nil") - } - if len(serviceStatusPage1.Results) != 1 { - t.Fatalf("serviceStatusPage1 should've had 1 result") - } - serviceStatusPage2 := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(2, 1)) - if serviceStatusPage2 == nil { - t.Fatalf("serviceStatusPage2 shouldn't have been nil") - } - if len(serviceStatusPage2.Results) != 1 { - t.Fatalf("serviceStatusPage2 should've had 1 result") - } - // Compare the timestamp of both pages - if !serviceStatusPage1.Results[0].Timestamp.After(serviceStatusPage2.Results[0].Timestamp) { - t.Errorf("The result from the first page should've been more recent than the results from the second page") - } -} - -func TestStore_GetServiceStatusByKey(t *testing.T) { - store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_GetServiceStatusByKey.db") - defer store.Close() - store.Insert(&testService, &testSuccessfulResult) - store.Insert(&testService, &testUnsuccessfulResult) - - serviceStatus := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) - if serviceStatus == nil { - t.Fatalf("serviceStatus shouldn't have been nil") - } - if serviceStatus.Name != testService.Name { - t.Fatalf("serviceStatus.Name should've been %s, got %s", testService.Name, serviceStatus.Name) - } - if serviceStatus.Group != testService.Group { - t.Fatalf("serviceStatus.Group should've been %s, got %s", testService.Group, serviceStatus.Group) - } - if serviceStatus.Uptime == nil { - t.Fatalf("serviceStatus.Uptime shouldn't have been nil") - } - if serviceStatus.Uptime.LastHour != 0.5 { - t.Errorf("serviceStatus.Uptime.LastHour should've been 0.5") - } - if serviceStatus.Uptime.LastTwentyFourHours != 0.5 { - t.Errorf("serviceStatus.Uptime.LastTwentyFourHours should've been 0.5") - } - if serviceStatus.Uptime.LastSevenDays != 0.5 { - t.Errorf("serviceStatus.Uptime.LastSevenDays should've been 0.5") - } -} - -func TestStore_GetAllServiceStatuses(t *testing.T) { - store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_GetAllServiceStatuses.db") - defer store.Close() - firstResult := &testSuccessfulResult - secondResult := &testUnsuccessfulResult - store.Insert(&testService, firstResult) - store.Insert(&testService, secondResult) - // Can't be bothered dealing with timezone issues on the worker that runs the automated tests - firstResult.Timestamp = time.Time{} - secondResult.Timestamp = time.Time{} - serviceStatuses := store.GetAllServiceStatuses(paging.NewServiceStatusParams().WithResults(1, 20)) - if len(serviceStatuses) != 1 { - t.Fatal("expected 1 service status") - } - actual, exists := serviceStatuses[testService.Key()] - if !exists { - t.Fatal("expected service status to exist") - } - if len(actual.Results) != 2 { - t.Error("expected 2 results, got", len(actual.Results)) - } - if len(actual.Events) != 0 { - t.Error("expected 0 events, got", len(actual.Events)) - } -} - -func TestStore_DeleteAllServiceStatusesNotInKeys(t *testing.T) { - store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_DeleteAllServiceStatusesNotInKeys.db") - defer store.Close() - firstService := core.Service{Name: "service-1", Group: "group"} - secondService := core.Service{Name: "service-2", Group: "group"} - result := &testSuccessfulResult - store.Insert(&firstService, result) - store.Insert(&secondService, result) - if store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()) == nil { - t.Fatal("firstService should exist") - } - if store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) == nil { - t.Fatal("secondService should exist") - } - store.DeleteAllServiceStatusesNotInKeys([]string{firstService.Key()}) - if store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()) == nil { - t.Error("secondService should've been deleted") - } - if store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) != nil { - t.Error("firstService should still exist") - } -} diff --git a/storage/store/memory/memory_test.go b/storage/store/memory/memory_test.go index 69ba7787..8447291e 100644 --- a/storage/store/memory/memory_test.go +++ b/storage/store/memory/memory_test.go @@ -88,10 +88,9 @@ func TestStore_Insert(t *testing.T) { if store.cache.Count() != 1 { t.Fatalf("expected 1 ServiceStatus, got %d", store.cache.Count()) } - key := testService.Key() - serviceStatus := store.GetServiceStatusByKey(key, paging.NewServiceStatusParams()) + serviceStatus := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, 20)) if serviceStatus == nil { - t.Fatalf("Store should've had key '%s', but didn't", key) + t.Fatalf("Store should've had key '%s', but didn't", testService.Key()) } if len(serviceStatus.Results) != 2 { t.Fatalf("Service '%s' should've had 2 results, but actually returned %d", serviceStatus.Name, len(serviceStatus.Results)) @@ -134,148 +133,6 @@ func TestStore_Insert(t *testing.T) { } } -func TestStore_GetServiceStatus(t *testing.T) { - store, _ := NewStore("") - store.Insert(&testService, &testSuccessfulResult) - store.Insert(&testService, &testUnsuccessfulResult) - - serviceStatus := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams()) - if serviceStatus == nil { - t.Fatalf("serviceStatus shouldn't have been nil") - } - if serviceStatus.Uptime == nil { - t.Fatalf("serviceStatus.Uptime shouldn't have been nil") - } - if serviceStatus.Uptime.LastHour != 0.5 { - t.Errorf("serviceStatus.Uptime.LastHour should've been 0.5") - } - if serviceStatus.Uptime.LastTwentyFourHours != 0.5 { - t.Errorf("serviceStatus.Uptime.LastTwentyFourHours should've been 0.5") - } - if serviceStatus.Uptime.LastSevenDays != 0.5 { - t.Errorf("serviceStatus.Uptime.LastSevenDays should've been 0.5") - } -} - -func TestStore_GetServiceStatusForMissingStatusReturnsNil(t *testing.T) { - store, _ := NewStore("") - store.Insert(&testService, &testSuccessfulResult) - - serviceStatus := store.GetServiceStatus("nonexistantgroup", "nonexistantname", paging.NewServiceStatusParams()) - if serviceStatus != nil { - t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, testService.Name) - } - serviceStatus = store.GetServiceStatus(testService.Group, "nonexistantname", paging.NewServiceStatusParams()) - if serviceStatus != nil { - t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, "nonexistantname") - } - serviceStatus = store.GetServiceStatus("nonexistantgroup", testService.Name, paging.NewServiceStatusParams()) - if serviceStatus != nil { - t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", "nonexistantgroup", testService.Name) - } -} - -func TestStore_GetServiceStatusByKey(t *testing.T) { - store, _ := NewStore("") - store.Insert(&testService, &testSuccessfulResult) - store.Insert(&testService, &testUnsuccessfulResult) - - serviceStatus := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams()) - if serviceStatus == nil { - t.Fatalf("serviceStatus shouldn't have been nil") - } - if serviceStatus.Uptime == nil { - t.Fatalf("serviceStatus.Uptime shouldn't have been nil") - } - if serviceStatus.Uptime.LastHour != 0.5 { - t.Errorf("serviceStatus.Uptime.LastHour should've been 0.5") - } - if serviceStatus.Uptime.LastTwentyFourHours != 0.5 { - t.Errorf("serviceStatus.Uptime.LastTwentyFourHours should've been 0.5") - } - if serviceStatus.Uptime.LastSevenDays != 0.5 { - t.Errorf("serviceStatus.Uptime.LastSevenDays should've been 0.5") - } -} - -func TestStore_GetAllServiceStatusesWithResults(t *testing.T) { - store, _ := NewStore("") - firstResult := &testSuccessfulResult - secondResult := &testUnsuccessfulResult - store.Insert(&testService, firstResult) - store.Insert(&testService, secondResult) - // Can't be bothered dealing with timezone issues on the worker that runs the automated tests - firstResult.Timestamp = time.Time{} - secondResult.Timestamp = time.Time{} - serviceStatuses := store.GetAllServiceStatuses(paging.NewServiceStatusParams().WithResults(1, 20)) - if len(serviceStatuses) != 1 { - t.Fatal("expected 1 service status") - } - actual, exists := serviceStatuses[testService.Key()] - if !exists { - t.Fatal("expected service status to exist") - } - if len(actual.Results) != 2 { - t.Error("expected 2 results, got", len(actual.Results)) - } - if len(actual.Events) != 0 { - t.Error("expected 0 events, got", len(actual.Events)) - } -} - -func TestStore_GetAllServiceStatusesWithResultsAndEvents(t *testing.T) { - store, _ := NewStore("") - firstResult := &testSuccessfulResult - secondResult := &testUnsuccessfulResult - store.Insert(&testService, firstResult) - store.Insert(&testService, secondResult) - // Can't be bothered dealing with timezone issues on the worker that runs the automated tests - firstResult.Timestamp = time.Time{} - secondResult.Timestamp = time.Time{} - serviceStatuses := store.GetAllServiceStatuses(paging.NewServiceStatusParams().WithResults(1, 20).WithEvents(1, 50)) - if len(serviceStatuses) != 1 { - t.Fatal("expected 1 service status") - } - actual, exists := serviceStatuses[testService.Key()] - if !exists { - t.Fatal("expected service status to exist") - } - if len(actual.Results) != 2 { - t.Error("expected 2 results, got", len(actual.Results)) - } - if len(actual.Events) != 2 { - t.Error("expected 2 events, got", len(actual.Events)) - } -} - -func TestStore_DeleteAllServiceStatusesNotInKeys(t *testing.T) { - store, _ := NewStore("") - firstService := core.Service{Name: "service-1", Group: "group"} - secondService := core.Service{Name: "service-2", Group: "group"} - result := &testSuccessfulResult - store.Insert(&firstService, result) - store.Insert(&secondService, result) - if store.cache.Count() != 2 { - t.Errorf("expected cache to have 2 keys, got %d", store.cache.Count()) - } - if store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()) == nil { - t.Fatal("firstService should exist") - } - if store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) == nil { - t.Fatal("secondService should exist") - } - store.DeleteAllServiceStatusesNotInKeys([]string{firstService.Key()}) - if store.cache.Count() != 1 { - t.Fatalf("expected cache to have 1 keys, got %d", store.cache.Count()) - } - if store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()) == nil { - t.Error("secondService should've been deleted") - } - if store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) != nil { - t.Error("firstService should still exist") - } -} - func TestStore_Save(t *testing.T) { files := []string{ "", diff --git a/storage/store/memory/util.go b/storage/store/memory/util.go index 421ae024..e1ac9667 100644 --- a/storage/store/memory/util.go +++ b/storage/store/memory/util.go @@ -61,15 +61,8 @@ func AddResult(ss *core.ServiceStatus, result *core.Result) { } if len(ss.Results) > 0 { // Check if there's any change since the last result - // OR there's only 1 event, which only happens when there's a start event - if ss.Results[len(ss.Results)-1].Success != result.Success || len(ss.Events) == 1 { - event := &core.Event{Timestamp: result.Timestamp} - if result.Success { - event.Type = core.EventHealthy - } else { - event.Type = core.EventUnhealthy - } - ss.Events = append(ss.Events, event) + if ss.Results[len(ss.Results)-1].Success != result.Success { + ss.Events = append(ss.Events, generateEventBasedOnResultSuccess(result)) if len(ss.Events) > core.MaximumNumberOfEvents { // Doing ss.Events[1:] would usually be sufficient, but in the case where for some reason, the slice has // more than one extra element, we can get rid of all of them at once and thus returning the slice to a @@ -77,6 +70,9 @@ func AddResult(ss *core.ServiceStatus, result *core.Result) { ss.Events = ss.Events[len(ss.Events)-core.MaximumNumberOfEvents:] } } + } else { + // This is the first result, so we need to add the first healthy/unhealthy event + ss.Events = append(ss.Events, generateEventBasedOnResultSuccess(result)) } ss.Results = append(ss.Results, result) if len(ss.Results) > core.MaximumNumberOfResults { @@ -87,3 +83,13 @@ func AddResult(ss *core.ServiceStatus, result *core.Result) { } processUptimeAfterResult(ss.Uptime, result) } + +func generateEventBasedOnResultSuccess(result *core.Result) *core.Event { + event := &core.Event{Timestamp: result.Timestamp} + if result.Success { + event.Type = core.EventHealthy + } else { + event.Type = core.EventUnhealthy + } + return event +} diff --git a/storage/store/store_bench_test.go b/storage/store/store_bench_test.go index c3f802d2..27d13d24 100644 --- a/storage/store/store_bench_test.go +++ b/storage/store/store_bench_test.go @@ -10,74 +10,6 @@ import ( "github.com/TwinProduction/gatus/storage/store/paging" ) -var ( - firstCondition = core.Condition("[STATUS] == 200") - secondCondition = core.Condition("[RESPONSE_TIME] < 500") - thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h") - - testService = core.Service{ - Name: "name", - Group: "group", - URL: "https://example.org/what/ever", - Method: "GET", - Body: "body", - Interval: 30 * time.Second, - Conditions: []*core.Condition{&firstCondition, &secondCondition, &thirdCondition}, - Alerts: nil, - Insecure: false, - NumberOfFailuresInARow: 0, - NumberOfSuccessesInARow: 0, - } - testSuccessfulResult = core.Result{ - Hostname: "example.org", - IP: "127.0.0.1", - HTTPStatus: 200, - Errors: nil, - Connected: true, - Success: true, - Duration: 150 * time.Millisecond, - CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ - { - Condition: "[STATUS] == 200", - Success: true, - }, - { - Condition: "[RESPONSE_TIME] < 500", - Success: true, - }, - { - Condition: "[CERTIFICATE_EXPIRATION] < 72h", - Success: true, - }, - }, - } - testUnsuccessfulResult = core.Result{ - Hostname: "example.org", - IP: "127.0.0.1", - HTTPStatus: 200, - Errors: []string{"error-1", "error-2"}, - Connected: true, - Success: false, - Duration: 750 * time.Millisecond, - CertificateExpiration: 10 * time.Hour, - ConditionResults: []*core.ConditionResult{ - { - Condition: "[STATUS] == 200", - Success: true, - }, - { - Condition: "[RESPONSE_TIME] < 500", - Success: false, - }, - { - Condition: "[CERTIFICATE_EXPIRATION] < 72h", - Success: false, - }, - }, - } -) - func BenchmarkStore_GetAllServiceStatuses(b *testing.B) { memoryStore, err := memory.NewStore("") if err != nil { @@ -207,3 +139,63 @@ func BenchmarkStore_Insert(b *testing.B) { }) } } + +func BenchmarkStore_GetServiceStatusByKey(b *testing.B) { + memoryStore, err := memory.NewStore("") + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetServiceStatusByKey.db") + if err != nil { + b.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + Parallel bool + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + Parallel: false, + }, + //{ + // Name: "memory-parallel", + // Store: memoryStore, + // Parallel: true, + //}, + { + Name: "database", + Store: databaseStore, + Parallel: false, + }, + //{ + // Name: "database-parallel", + // Store: databaseStore, + // Parallel: true, + //}, + } + for _, scenario := range scenarios { + for i := 0; i < 10; i++ { + scenario.Store.Insert(&testService, &testSuccessfulResult) + scenario.Store.Insert(&testService, &testUnsuccessfulResult) + } + b.Run(scenario.Name, func(b *testing.B) { + if scenario.Parallel { + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, 20)) + } + }) + } else { + for n := 0; n < b.N; n++ { + scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, 20)) + } + } + b.ReportAllocs() + }) + scenario.Store.Clear() + } +} diff --git a/storage/store/store_test.go b/storage/store/store_test.go new file mode 100644 index 00000000..a04e5354 --- /dev/null +++ b/storage/store/store_test.go @@ -0,0 +1,476 @@ +package store + +import ( + "testing" + "time" + + "github.com/TwinProduction/gatus/core" + "github.com/TwinProduction/gatus/storage/store/database" + "github.com/TwinProduction/gatus/storage/store/memory" + "github.com/TwinProduction/gatus/storage/store/paging" +) + +var ( + firstCondition = core.Condition("[STATUS] == 200") + secondCondition = core.Condition("[RESPONSE_TIME] < 500") + thirdCondition = core.Condition("[CERTIFICATE_EXPIRATION] < 72h") + + now = time.Now() + + testService = core.Service{ + Name: "name", + Group: "group", + URL: "https://example.org/what/ever", + Method: "GET", + Body: "body", + Interval: 30 * time.Second, + Conditions: []*core.Condition{&firstCondition, &secondCondition, &thirdCondition}, + Alerts: nil, + Insecure: false, + NumberOfFailuresInARow: 0, + NumberOfSuccessesInARow: 0, + } + testSuccessfulResult = core.Result{ + Timestamp: now, + Success: true, + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: nil, + Connected: true, + Duration: 150 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*core.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: true, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: true, + }, + }, + } + testUnsuccessfulResult = core.Result{ + Timestamp: now, + Success: false, + Hostname: "example.org", + IP: "127.0.0.1", + HTTPStatus: 200, + Errors: []string{"error-1", "error-2"}, + Connected: true, + Duration: 750 * time.Millisecond, + CertificateExpiration: 10 * time.Hour, + ConditionResults: []*core.ConditionResult{ + { + Condition: "[STATUS] == 200", + Success: true, + }, + { + Condition: "[RESPONSE_TIME] < 500", + Success: false, + }, + { + Condition: "[CERTIFICATE_EXPIRATION] < 72h", + Success: false, + }, + }, + } +) + +func TestStore_GetServiceStatusByKey(t *testing.T) { + memoryStore, err := memory.NewStore("") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", t.TempDir()+"/TestStore_GetServiceStatusByKey.db") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "database", + Store: databaseStore, + }, + } + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-time.Minute) + secondResult := testUnsuccessfulResult + secondResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testService, &firstResult) + scenario.Store.Insert(&testService, &secondResult) + + serviceStatus := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) + if serviceStatus == nil { + t.Fatalf("serviceStatus shouldn't have been nil") + } + if serviceStatus.Name != testService.Name { + t.Fatalf("serviceStatus.Name should've been %s, got %s", testService.Name, serviceStatus.Name) + } + if serviceStatus.Group != testService.Group { + t.Fatalf("serviceStatus.Group should've been %s, got %s", testService.Group, serviceStatus.Group) + } + if len(serviceStatus.Results) != 2 { + t.Fatalf("serviceStatus.Results should've had 2 entries") + } + if serviceStatus.Results[0].Timestamp.After(serviceStatus.Results[1].Timestamp) { + t.Error("The result at index 0 should've been older than the result at index 1") + } + if serviceStatus.Uptime == nil { + t.Fatalf("serviceStatus.Uptime shouldn't have been nil") + } + if serviceStatus.Uptime.LastHour != 0.5 { + t.Errorf("serviceStatus.Uptime.LastHour should've been 0.5, got %f", serviceStatus.Uptime.LastHour) + } + if serviceStatus.Uptime.LastTwentyFourHours != 0.5 { + t.Errorf("serviceStatus.Uptime.LastTwentyFourHours should've been 0.5, got %f", serviceStatus.Uptime.LastTwentyFourHours) + } + if serviceStatus.Uptime.LastSevenDays != 0.5 { + t.Errorf("serviceStatus.Uptime.LastSevenDays should've been 0.5, got %f", serviceStatus.Uptime.LastSevenDays) + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetServiceStatusForMissingStatusReturnsNil(t *testing.T) { + memoryStore, err := memory.NewStore("") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", t.TempDir()+"/TestStore_GetServiceStatusForMissingStatusReturnsNil.db") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "database", + Store: databaseStore, + }, + } + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testService, &testSuccessfulResult) + serviceStatus := scenario.Store.GetServiceStatus("nonexistantgroup", "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) + if serviceStatus != nil { + t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, testService.Name) + } + serviceStatus = scenario.Store.GetServiceStatus(testService.Group, "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) + if serviceStatus != nil { + t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", testService.Group, "nonexistantname") + } + serviceStatus = scenario.Store.GetServiceStatus("nonexistantgroup", testService.Name, paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) + if serviceStatus != nil { + t.Errorf("Returned service status for group '%s' and name '%s' not nil after inserting the service into the store", "nonexistantgroup", testService.Name) + } + }) + } +} + +func TestStore_GetAllServiceStatuses(t *testing.T) { + memoryStore, err := memory.NewStore("") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", t.TempDir()+"/TestStore_GetAllServiceStatuses.db") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "database", + Store: databaseStore, + }, + } + firstResult := testSuccessfulResult + secondResult := testUnsuccessfulResult + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testService, &firstResult) + scenario.Store.Insert(&testService, &secondResult) + // Can't be bothered dealing with timezone issues on the worker that runs the automated tests + serviceStatuses := scenario.Store.GetAllServiceStatuses(paging.NewServiceStatusParams().WithResults(1, 20)) + if len(serviceStatuses) != 1 { + t.Fatal("expected 1 service status") + } + actual, exists := serviceStatuses[testService.Key()] + if !exists { + t.Fatal("expected service status to exist") + } + if len(actual.Results) != 2 { + t.Error("expected 2 results, got", len(actual.Results)) + } + if len(actual.Events) != 0 { + t.Error("expected 0 events, got", len(actual.Events)) + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetAllServiceStatusesWithResultsAndEvents(t *testing.T) { + memoryStore, err := memory.NewStore("") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", t.TempDir()+"/TestStore_GetAllServiceStatusesWithResultsAndEvents.db") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "database", + Store: databaseStore, + }, + } + firstResult := testSuccessfulResult + secondResult := testUnsuccessfulResult + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testService, &firstResult) + scenario.Store.Insert(&testService, &secondResult) + // Can't be bothered dealing with timezone issues on the worker that runs the automated tests + serviceStatuses := scenario.Store.GetAllServiceStatuses(paging.NewServiceStatusParams().WithResults(1, 20).WithEvents(1, 50)) + if len(serviceStatuses) != 1 { + t.Fatal("expected 1 service status") + } + actual, exists := serviceStatuses[testService.Key()] + if !exists { + t.Fatal("expected service status to exist") + } + if len(actual.Results) != 2 { + t.Error("expected 2 results, got", len(actual.Results)) + } + if len(actual.Events) != 3 { + t.Error("expected 3 events, got", len(actual.Events)) + } + scenario.Store.Clear() + }) + } +} + +func TestStore_GetServiceStatusPage1IsHasMoreRecentResultsThanPage2(t *testing.T) { + memoryStore, err := memory.NewStore("") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", t.TempDir()+"/TestStore_GetServiceStatusPage1IsHasMoreRecentResultsThanPage2.db") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "database", + Store: databaseStore, + }, + } + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-time.Minute) + secondResult := testUnsuccessfulResult + secondResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testService, &firstResult) + scenario.Store.Insert(&testService, &secondResult) + serviceStatusPage1 := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, 1)) + if serviceStatusPage1 == nil { + t.Fatalf("serviceStatusPage1 shouldn't have been nil") + } + if len(serviceStatusPage1.Results) != 1 { + t.Fatalf("serviceStatusPage1 should've had 1 result") + } + serviceStatusPage2 := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(2, 1)) + if serviceStatusPage2 == nil { + t.Fatalf("serviceStatusPage2 shouldn't have been nil") + } + if len(serviceStatusPage2.Results) != 1 { + t.Fatalf("serviceStatusPage2 should've had 1 result") + } + // Compare the timestamp of both pages + if !serviceStatusPage1.Results[0].Timestamp.After(serviceStatusPage2.Results[0].Timestamp) { + t.Errorf("The result from the first page should've been more recent than the results from the second page") + } + scenario.Store.Clear() + }) + } +} + +func TestStore_Insert(t *testing.T) { + memoryStore, err := memory.NewStore("") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", t.TempDir()+"/TestStore_Insert.db") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "database", + Store: databaseStore, + }, + } + firstResult := testSuccessfulResult + firstResult.Timestamp = now.Add(-time.Minute) + secondResult := testUnsuccessfulResult + secondResult.Timestamp = now + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&testService, &testSuccessfulResult) + scenario.Store.Insert(&testService, &testUnsuccessfulResult) + + serviceStatus := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()) + if serviceStatus == nil { + t.Fatalf("Store should've had key '%s', but didn't", testService.Key()) + } + if len(serviceStatus.Results) != 2 { + t.Fatalf("Service '%s' should've had 2 results, but actually returned %d", serviceStatus.Name, len(serviceStatus.Results)) + } + for i, r := range serviceStatus.Results { + expectedResult := scenario.Store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithEvents(1, core.MaximumNumberOfEvents).WithResults(1, core.MaximumNumberOfResults).WithUptime()).Results[i] + if r.HTTPStatus != expectedResult.HTTPStatus { + t.Errorf("Result at index %d should've had a HTTPStatus of %d, but was actually %d", i, expectedResult.HTTPStatus, r.HTTPStatus) + } + if r.DNSRCode != expectedResult.DNSRCode { + t.Errorf("Result at index %d should've had a DNSRCode of %s, but was actually %s", i, expectedResult.DNSRCode, r.DNSRCode) + } + if r.Hostname != expectedResult.Hostname { + t.Errorf("Result at index %d should've had a Hostname of %s, but was actually %s", i, expectedResult.Hostname, r.Hostname) + } + if r.IP != expectedResult.IP { + t.Errorf("Result at index %d should've had a IP of %s, but was actually %s", i, expectedResult.IP, r.IP) + } + if r.Connected != expectedResult.Connected { + t.Errorf("Result at index %d should've had a Connected value of %t, but was actually %t", i, expectedResult.Connected, r.Connected) + } + if r.Duration != expectedResult.Duration { + t.Errorf("Result at index %d should've had a Duration of %s, but was actually %s", i, expectedResult.Duration.String(), r.Duration.String()) + } + if len(r.Errors) != len(expectedResult.Errors) { + t.Errorf("Result at index %d should've had %d errors, but actually had %d errors", i, len(expectedResult.Errors), len(r.Errors)) + } + if len(r.ConditionResults) != len(expectedResult.ConditionResults) { + t.Errorf("Result at index %d should've had %d ConditionResults, but actually had %d ConditionResults", i, len(expectedResult.ConditionResults), len(r.ConditionResults)) + } + if r.Success != expectedResult.Success { + t.Errorf("Result at index %d should've had a Success of %t, but was actually %t", i, expectedResult.Success, r.Success) + } + if r.Timestamp != expectedResult.Timestamp { + t.Errorf("Result at index %d should've had a Timestamp of %s, but was actually %s", i, expectedResult.Timestamp.String(), r.Timestamp.String()) + } + if r.CertificateExpiration != expectedResult.CertificateExpiration { + t.Errorf("Result at index %d should've had a CertificateExpiration of %s, but was actually %s", i, expectedResult.CertificateExpiration.String(), r.CertificateExpiration.String()) + } + } + }) + } +} + +func TestStore_DeleteAllServiceStatusesNotInKeys(t *testing.T) { + memoryStore, err := memory.NewStore("") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + databaseStore, err := database.NewStore("sqlite", t.TempDir()+"/TestStore_DeleteAllServiceStatusesNotInKeys.db") + if err != nil { + t.Fatal("failed to create store:", err.Error()) + } + defer databaseStore.Close() + type Scenario struct { + Name string + Store Store + } + scenarios := []Scenario{ + { + Name: "memory", + Store: memoryStore, + }, + { + Name: "database", + Store: databaseStore, + }, + } + firstService := core.Service{Name: "service-1", Group: "group"} + secondService := core.Service{Name: "service-2", Group: "group"} + result := &testSuccessfulResult + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + scenario.Store.Insert(&firstService, result) + scenario.Store.Insert(&secondService, result) + if scenario.Store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()) == nil { + t.Fatal("firstService should exist") + } + if scenario.Store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) == nil { + t.Fatal("secondService should exist") + } + scenario.Store.DeleteAllServiceStatusesNotInKeys([]string{firstService.Key()}) + if scenario.Store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()) == nil { + t.Error("secondService should've been deleted") + } + if scenario.Store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) != nil { + t.Error("firstService should still exist") + } + scenario.Store.Clear() + }) + } +}