Close #124: Add support for Postgres as a storage solution

This commit is contained in:
TwinProduction
2021-09-10 18:00:04 -04:00
committed by Chris
parent 06ef7f9efe
commit bacf7d841b
47 changed files with 7593 additions and 244 deletions

View File

@ -4,6 +4,8 @@ package storage
type Config struct {
// File is the path of the file to use for persistence
// If blank, persistence is disabled
//
// XXX: Rename to path for v4.0.0
File string `yaml:"file"`
// Type of store

View File

@ -7,7 +7,7 @@ import (
"github.com/TwinProduction/gatus/storage/store"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sqlite"
"github.com/TwinProduction/gatus/storage/store/sql"
)
var (
@ -45,15 +45,15 @@ func Initialize(cfg *Config) error {
if cfg == nil {
cfg = &Config{}
}
if len(cfg.File) == 0 {
log.Printf("[storage][Initialize] Creating storage provider with type=%s", cfg.Type)
} else {
if len(cfg.File) == 0 && cfg.Type != TypePostgres {
log.Printf("[storage][Initialize] Creating storage provider with type=%s and file=%s", cfg.Type, cfg.File)
} else {
log.Printf("[storage][Initialize] Creating storage provider with type=%s", cfg.Type)
}
ctx, cancelFunc = context.WithCancel(context.Background())
switch cfg.Type {
case TypeSQLite:
provider, err = sqlite.NewStore(string(cfg.Type), cfg.File)
case TypeSQLite, TypePostgres:
provider, err = sql.NewStore(string(cfg.Type), cfg.File)
if err != nil {
return err
}

View File

@ -4,7 +4,7 @@ import (
"testing"
"time"
"github.com/TwinProduction/gatus/storage/store/sqlite"
"github.com/TwinProduction/gatus/storage/store/sql"
)
func TestGet(t *testing.T) {
@ -44,7 +44,7 @@ func TestInitialize(t *testing.T) {
{
Name: "sqlite-no-file",
Cfg: &Config{Type: TypeSQLite},
ExpectedErr: sqlite.ErrFilePathNotSpecified,
ExpectedErr: sql.ErrFilePathNotSpecified,
},
{
Name: "sqlite-with-file",

View File

@ -48,7 +48,7 @@ func NewStore(file string) (*Store, error) {
// GetAllServiceStatuses returns all monitored core.ServiceStatus
// with a subset of core.Result defined by the page and pageSize parameters
func (s *Store) GetAllServiceStatuses(params *paging.ServiceStatusParams) []*core.ServiceStatus {
func (s *Store) GetAllServiceStatuses(params *paging.ServiceStatusParams) ([]*core.ServiceStatus, error) {
serviceStatuses := s.cache.GetAll()
pagedServiceStatuses := make([]*core.ServiceStatus, 0, len(serviceStatuses))
for _, v := range serviceStatuses {
@ -57,21 +57,21 @@ func (s *Store) GetAllServiceStatuses(params *paging.ServiceStatusParams) []*cor
sort.Slice(pagedServiceStatuses, func(i, j int) bool {
return pagedServiceStatuses[i].Key < pagedServiceStatuses[j].Key
})
return pagedServiceStatuses
return pagedServiceStatuses, nil
}
// GetServiceStatus returns the service status for a given service name in the given group
func (s *Store) GetServiceStatus(groupName, serviceName string, params *paging.ServiceStatusParams) *core.ServiceStatus {
func (s *Store) GetServiceStatus(groupName, serviceName string, params *paging.ServiceStatusParams) (*core.ServiceStatus, error) {
return s.GetServiceStatusByKey(util.ConvertGroupAndServiceToKey(groupName, serviceName), params)
}
// GetServiceStatusByKey returns the service status for a given key
func (s *Store) GetServiceStatusByKey(key string, params *paging.ServiceStatusParams) *core.ServiceStatus {
func (s *Store) GetServiceStatusByKey(key string, params *paging.ServiceStatusParams) (*core.ServiceStatus, error) {
serviceStatus := s.cache.GetValue(key)
if serviceStatus == nil {
return nil
return nil, common.ErrServiceNotFound
}
return ShallowCopyServiceStatus(serviceStatus.(*core.ServiceStatus), params)
return ShallowCopyServiceStatus(serviceStatus.(*core.ServiceStatus), params), nil
}
// GetUptimeByKey returns the uptime percentage during a time range
@ -156,7 +156,7 @@ func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time
}
// Insert adds the observed result for the specified service into the store
func (s *Store) Insert(service *core.Service, result *core.Result) {
func (s *Store) Insert(service *core.Service, result *core.Result) error {
key := service.Key()
s.Lock()
serviceStatus, exists := s.cache.Get(key)
@ -170,6 +170,7 @@ func (s *Store) Insert(service *core.Service, result *core.Result) {
AddResult(serviceStatus.(*core.ServiceStatus), result)
s.cache.Set(key, serviceStatus)
s.Unlock()
return nil
}
// DeleteAllServiceStatusesNotInKeys removes all ServiceStatus that are not within the keys provided

View File

@ -85,12 +85,14 @@ func TestStore_SanityCheck(t *testing.T) {
store, _ := NewStore("")
defer store.Close()
store.Insert(&testService, &testSuccessfulResult)
if numberOfServiceStatuses := len(store.GetAllServiceStatuses(paging.NewServiceStatusParams())); numberOfServiceStatuses != 1 {
serviceStatuses, _ := store.GetAllServiceStatuses(paging.NewServiceStatusParams())
if numberOfServiceStatuses := len(serviceStatuses); numberOfServiceStatuses != 1 {
t.Fatalf("expected 1 ServiceStatus, got %d", numberOfServiceStatuses)
}
store.Insert(&testService, &testUnsuccessfulResult)
// Both results inserted are for the same service, therefore, the count shouldn't have increased
if numberOfServiceStatuses := len(store.GetAllServiceStatuses(paging.NewServiceStatusParams())); numberOfServiceStatuses != 1 {
serviceStatuses, _ = store.GetAllServiceStatuses(paging.NewServiceStatusParams())
if numberOfServiceStatuses := len(serviceStatuses); numberOfServiceStatuses != 1 {
t.Fatalf("expected 1 ServiceStatus, got %d", numberOfServiceStatuses)
}
if hourlyAverageResponseTime, err := store.GetHourlyAverageResponseTimeByKey(testService.Key(), time.Now().Add(-24*time.Hour), time.Now()); err != nil {
@ -104,7 +106,7 @@ func TestStore_SanityCheck(t *testing.T) {
if averageResponseTime, _ := store.GetAverageResponseTimeByKey(testService.Key(), time.Now().Add(-24*time.Hour), time.Now()); averageResponseTime != 450 {
t.Errorf("expected average response time of last 24h to be 450, got %d", averageResponseTime)
}
ss := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, 20).WithEvents(1, 20))
ss, _ := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, 20).WithEvents(1, 20))
if ss == nil {
t.Fatalf("Store should've had key '%s', but didn't", testService.Key())
}

View File

@ -0,0 +1,69 @@
package sql
func (s *Store) createPostgresSchema() error {
_, err := s.db.Exec(`
CREATE TABLE IF NOT EXISTS service (
service_id BIGSERIAL PRIMARY KEY,
service_key TEXT UNIQUE,
service_name TEXT,
service_group TEXT,
UNIQUE(service_name, service_group)
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_event (
service_event_id BIGSERIAL PRIMARY KEY,
service_id INTEGER REFERENCES service(service_id) ON DELETE CASCADE,
event_type TEXT,
event_timestamp TIMESTAMP
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_result (
service_result_id BIGSERIAL PRIMARY KEY,
service_id BIGINT REFERENCES service(service_id) ON DELETE CASCADE,
success BOOLEAN,
errors TEXT,
connected BOOLEAN,
status BIGINT,
dns_rcode TEXT,
certificate_expiration BIGINT,
hostname TEXT,
ip TEXT,
duration BIGINT,
timestamp TIMESTAMP
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_result_condition (
service_result_condition_id BIGSERIAL PRIMARY KEY,
service_result_id BIGINT REFERENCES service_result(service_result_id) ON DELETE CASCADE,
condition TEXT,
success BOOLEAN
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_uptime (
service_uptime_id BIGSERIAL PRIMARY KEY,
service_id BIGINT REFERENCES service(service_id) ON DELETE CASCADE,
hour_unix_timestamp BIGINT,
total_executions BIGINT,
successful_executions BIGINT,
total_response_time BIGINT,
UNIQUE(service_id, hour_unix_timestamp)
)
`)
return err
}

View File

@ -0,0 +1,69 @@
package sql
func (s *Store) createSQLiteSchema() error {
_, err := s.db.Exec(`
CREATE TABLE IF NOT EXISTS service (
service_id INTEGER PRIMARY KEY,
service_key TEXT UNIQUE,
service_name TEXT,
service_group TEXT,
UNIQUE(service_name, service_group)
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_event (
service_event_id INTEGER PRIMARY KEY,
service_id INTEGER REFERENCES service(service_id) ON DELETE CASCADE,
event_type TEXT,
event_timestamp TIMESTAMP
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_result (
service_result_id INTEGER PRIMARY KEY,
service_id INTEGER REFERENCES service(service_id) ON DELETE CASCADE,
success INTEGER,
errors TEXT,
connected INTEGER,
status INTEGER,
dns_rcode TEXT,
certificate_expiration INTEGER,
hostname TEXT,
ip TEXT,
duration INTEGER,
timestamp TIMESTAMP
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_result_condition (
service_result_condition_id INTEGER PRIMARY KEY,
service_result_id INTEGER REFERENCES service_result(service_result_id) ON DELETE CASCADE,
condition TEXT,
success INTEGER
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_uptime (
service_uptime_id INTEGER PRIMARY KEY,
service_id INTEGER REFERENCES service(service_id) ON DELETE CASCADE,
hour_unix_timestamp INTEGER,
total_executions INTEGER,
successful_executions INTEGER,
total_response_time INTEGER,
UNIQUE(service_id, hour_unix_timestamp)
)
`)
return err
}

View File

@ -1,4 +1,4 @@
package sqlite
package sql
import (
"database/sql"
@ -12,6 +12,7 @@ import (
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/util"
_ "github.com/lib/pq"
_ "modernc.org/sqlite"
)
@ -79,84 +80,23 @@ func NewStore(driver, path string) (*Store, error) {
// createSchema creates the schema required to perform all database operations.
func (s *Store) createSchema() error {
_, err := s.db.Exec(`
CREATE TABLE IF NOT EXISTS service (
service_id INTEGER PRIMARY KEY,
service_key TEXT UNIQUE,
service_name TEXT,
service_group TEXT,
UNIQUE(service_name, service_group)
)
`)
if err != nil {
return err
if s.driver == "sqlite" {
return s.createSQLiteSchema()
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_event (
service_event_id INTEGER PRIMARY KEY,
service_id INTEGER REFERENCES service(service_id) ON DELETE CASCADE,
event_type TEXT,
event_timestamp TIMESTAMP
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_result (
service_result_id INTEGER PRIMARY KEY,
service_id INTEGER REFERENCES service(service_id) ON DELETE CASCADE,
success INTEGER,
errors TEXT,
connected INTEGER,
status INTEGER,
dns_rcode TEXT,
certificate_expiration INTEGER,
hostname TEXT,
ip TEXT,
duration INTEGER,
timestamp TIMESTAMP
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_result_condition (
service_result_condition_id INTEGER PRIMARY KEY,
service_result_id INTEGER REFERENCES service_result(service_result_id) ON DELETE CASCADE,
condition TEXT,
success INTEGER
)
`)
if err != nil {
return err
}
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS service_uptime (
service_uptime_id INTEGER PRIMARY KEY,
service_id INTEGER REFERENCES service(service_id) ON DELETE CASCADE,
hour_unix_timestamp INTEGER,
total_executions INTEGER,
successful_executions INTEGER,
total_response_time INTEGER,
UNIQUE(service_id, hour_unix_timestamp)
)
`)
return err
return s.createPostgresSchema()
}
// GetAllServiceStatuses returns all monitored core.ServiceStatus
// with a subset of core.Result defined by the page and pageSize parameters
func (s *Store) GetAllServiceStatuses(params *paging.ServiceStatusParams) []*core.ServiceStatus {
func (s *Store) GetAllServiceStatuses(params *paging.ServiceStatusParams) ([]*core.ServiceStatus, error) {
tx, err := s.db.Begin()
if err != nil {
return nil
return nil, err
}
keys, err := s.getAllServiceKeys(tx)
if err != nil {
_ = tx.Rollback()
return nil
return nil, err
}
serviceStatuses := make([]*core.ServiceStatus, 0, len(keys))
for _, key := range keys {
@ -169,29 +109,29 @@ func (s *Store) GetAllServiceStatuses(params *paging.ServiceStatusParams) []*cor
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
}
return serviceStatuses
return serviceStatuses, err
}
// GetServiceStatus returns the service status for a given service name in the given group
func (s *Store) GetServiceStatus(groupName, serviceName string, params *paging.ServiceStatusParams) *core.ServiceStatus {
func (s *Store) GetServiceStatus(groupName, serviceName string, params *paging.ServiceStatusParams) (*core.ServiceStatus, error) {
return s.GetServiceStatusByKey(util.ConvertGroupAndServiceToKey(groupName, serviceName), params)
}
// GetServiceStatusByKey returns the service status for a given key
func (s *Store) GetServiceStatusByKey(key string, params *paging.ServiceStatusParams) *core.ServiceStatus {
func (s *Store) GetServiceStatusByKey(key string, params *paging.ServiceStatusParams) (*core.ServiceStatus, error) {
tx, err := s.db.Begin()
if err != nil {
return nil
return nil, err
}
serviceStatus, err := s.getServiceStatusByKey(tx, key, params)
if err != nil {
_ = tx.Rollback()
return nil
return nil, err
}
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
}
return serviceStatus
return serviceStatus, err
}
// GetUptimeByKey returns the uptime percentage during a time range
@ -270,10 +210,10 @@ func (s *Store) GetHourlyAverageResponseTimeByKey(key string, from, to time.Time
}
// Insert adds the observed result for the specified service into the store
func (s *Store) Insert(service *core.Service, result *core.Result) {
func (s *Store) Insert(service *core.Service, result *core.Result) error {
tx, err := s.db.Begin()
if err != nil {
return
return err
}
//start := time.Now()
serviceID, err := s.getServiceID(tx, service)
@ -282,11 +222,13 @@ func (s *Store) Insert(service *core.Service, result *core.Result) {
// Service doesn't exist in the database, insert it
if serviceID, err = s.insertService(tx, service); err != nil {
_ = tx.Rollback()
return // failed to insert service
log.Printf("[sql][Insert] Failed to create service with group=%s; service=%s: %s", service.Group, service.Name, err.Error())
return err
}
} else {
_ = tx.Rollback()
return
log.Printf("[sql][Insert] Failed to retrieve id of service with group=%s; service=%s: %s", service.Group, service.Name, err.Error())
return err
}
}
// First, we need to check if we need to insert a new event.
@ -300,7 +242,7 @@ func (s *Store) Insert(service *core.Service, result *core.Result) {
// based on result.Success.
numberOfEvents, err := s.getNumberOfEventsByServiceID(tx, serviceID)
if err != nil {
log.Printf("[sqlite][Insert] Failed to retrieve total number of events for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to retrieve total number of events for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
}
if numberOfEvents == 0 {
// There's no events yet, which means we need to add the EventStart and the first healthy/unhealthy event
@ -310,18 +252,18 @@ func (s *Store) Insert(service *core.Service, result *core.Result) {
})
if err != nil {
// Silently fail
log.Printf("[sqlite][Insert] Failed to insert event=%s for group=%s; service=%s: %s", core.EventStart, service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to insert event=%s for group=%s; service=%s: %s", core.EventStart, service.Group, service.Name, err.Error())
}
event := core.NewEventFromResult(result)
if err = s.insertEvent(tx, serviceID, event); err != nil {
// Silently fail
log.Printf("[sqlite][Insert] Failed to insert event=%s for group=%s; service=%s: %s", event.Type, service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to insert event=%s for group=%s; service=%s: %s", event.Type, service.Group, service.Name, err.Error())
}
} else {
// Get the success value of the previous result
var lastResultSuccess bool
if lastResultSuccess, err = s.getLastServiceResultSuccessValue(tx, serviceID); err != nil {
log.Printf("[sqlite][Insert] Failed to retrieve outcome of previous result for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to retrieve outcome of previous result for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
} else {
// If we managed to retrieve the outcome of the previous result, we'll compare it with the new result.
// If the final outcome (success or failure) of the previous and the new result aren't the same, it means
@ -331,7 +273,7 @@ func (s *Store) Insert(service *core.Service, result *core.Result) {
event := core.NewEventFromResult(result)
if err = s.insertEvent(tx, serviceID, event); err != nil {
// Silently fail
log.Printf("[sqlite][Insert] Failed to insert event=%s for group=%s; service=%s: %s", event.Type, service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to insert event=%s for group=%s; service=%s: %s", event.Type, service.Group, service.Name, err.Error())
}
}
}
@ -340,48 +282,48 @@ func (s *Store) Insert(service *core.Service, result *core.Result) {
// (since we're only deleting MaximumNumberOfEvents at a time instead of 1)
if numberOfEvents > eventsCleanUpThreshold {
if err = s.deleteOldServiceEvents(tx, serviceID); err != nil {
log.Printf("[sqlite][Insert] Failed to delete old events for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to delete old events for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
}
}
}
// Second, we need to insert the result.
if err = s.insertResult(tx, serviceID, result); err != nil {
log.Printf("[sqlite][Insert] Failed to insert result for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to insert result for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
_ = tx.Rollback() // If we can't insert the result, we'll rollback now since there's no point continuing
return
return err
}
// Clean up old results
numberOfResults, err := s.getNumberOfResultsByServiceID(tx, serviceID)
if err != nil {
log.Printf("[sqlite][Insert] Failed to retrieve total number of results for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to retrieve total number of results for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
} else {
if numberOfResults > resultsCleanUpThreshold {
if err = s.deleteOldServiceResults(tx, serviceID); err != nil {
log.Printf("[sqlite][Insert] Failed to delete old results for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to delete old results for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
}
}
}
// Finally, we need to insert the uptime data.
// Because the uptime data significantly outlives the results, we can't rely on the results for determining the uptime
if err = s.updateServiceUptime(tx, serviceID, result); err != nil {
log.Printf("[sqlite][Insert] Failed to update uptime for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to update uptime for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
}
// Clean up old uptime entries
ageOfOldestUptimeEntry, err := s.getAgeOfOldestServiceUptimeEntry(tx, serviceID)
if err != nil {
log.Printf("[sqlite][Insert] Failed to retrieve oldest service uptime entry for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to retrieve oldest service uptime entry for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
} else {
if ageOfOldestUptimeEntry > uptimeCleanUpThreshold {
if err = s.deleteOldUptimeEntries(tx, serviceID, time.Now().Add(-(uptimeRetention + time.Hour))); err != nil {
log.Printf("[sqlite][Insert] Failed to delete old uptime entries for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
log.Printf("[sql][Insert] Failed to delete old uptime entries for group=%s; service=%s: %s", service.Group, service.Name, err.Error())
}
}
}
//log.Printf("[sqlite][Insert] Successfully inserted result in duration=%dms", time.Since(start).Milliseconds())
//log.Printf("[sql][Insert] Successfully inserted result in duration=%dms", time.Since(start).Milliseconds())
if err = tx.Commit(); err != nil {
_ = tx.Rollback()
}
return
return err
}
// DeleteAllServiceStatusesNotInKeys removes all rows owned by a service whose key is not within the keys provided
@ -393,13 +335,16 @@ func (s *Store) DeleteAllServiceStatusesNotInKeys(keys []string) int {
result, err = s.db.Exec("DELETE FROM service")
} else {
args := make([]interface{}, 0, len(keys))
query := "DELETE FROM service WHERE service_key NOT IN ("
for i := range keys {
query += fmt.Sprintf("$%d,", i+1)
args = append(args, keys[i])
}
result, err = s.db.Exec(fmt.Sprintf("DELETE FROM service WHERE service_key NOT IN (%s)", strings.Trim(strings.Repeat("?,", len(keys)), ",")), args...)
query = query[:len(query)-1] + ")"
result, err = s.db.Exec(query, args...)
}
if err != nil {
log.Printf("[sqlite][DeleteAllServiceStatusesNotInKeys] Failed to delete rows that do not belong to any of keys=%v: %s", keys, err.Error())
log.Printf("[sql][DeleteAllServiceStatusesNotInKeys] Failed to delete rows that do not belong to any of keys=%v: %s", keys, err.Error())
return 0
}
rowsAffects, _ := result.RowsAffected()
@ -423,17 +368,18 @@ func (s *Store) Close() {
// insertService inserts a service in the store and returns the generated id of said service
func (s *Store) insertService(tx *sql.Tx, service *core.Service) (int64, error) {
//log.Printf("[sqlite][insertService] Inserting service with group=%s and name=%s", service.Group, service.Name)
result, err := tx.Exec(
"INSERT INTO service (service_key, service_name, service_group) VALUES ($1, $2, $3)",
//log.Printf("[sql][insertService] Inserting service with group=%s and name=%s", service.Group, service.Name)
var id int64
err := tx.QueryRow(
"INSERT INTO service (service_key, service_name, service_group) VALUES ($1, $2, $3) RETURNING service_id",
service.Key(),
service.Name,
service.Group,
)
).Scan(&id)
if err != nil {
return 0, err
}
return result.LastInsertId()
return id, nil
}
// insertEvent inserts a service event in the store
@ -442,7 +388,7 @@ func (s *Store) insertEvent(tx *sql.Tx, serviceID int64, event *core.Event) erro
"INSERT INTO service_event (service_id, event_type, event_timestamp) VALUES ($1, $2, $3)",
serviceID,
event.Type,
event.Timestamp,
event.Timestamp.UTC(),
)
if err != nil {
return err
@ -452,10 +398,12 @@ func (s *Store) insertEvent(tx *sql.Tx, serviceID int64, event *core.Event) erro
// insertResult inserts a result in the store
func (s *Store) insertResult(tx *sql.Tx, serviceID int64, result *core.Result) error {
res, err := tx.Exec(
var serviceResultID int64
err := tx.QueryRow(
`
INSERT INTO service_result (service_id, success, errors, connected, status, dns_rcode, certificate_expiration, hostname, ip, duration, timestamp)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING service_result_id
`,
serviceID,
result.Success,
@ -467,12 +415,8 @@ func (s *Store) insertResult(tx *sql.Tx, serviceID int64, result *core.Result) e
result.Hostname,
result.IP,
result.Duration,
result.Timestamp,
)
if err != nil {
return err
}
serviceResultID, err := res.LastInsertId()
result.Timestamp.UTC(),
).Scan(&serviceResultID)
if err != nil {
return err
}
@ -505,9 +449,9 @@ func (s *Store) updateServiceUptime(tx *sql.Tx, serviceID int64, result *core.Re
INSERT INTO service_uptime (service_id, hour_unix_timestamp, total_executions, successful_executions, total_response_time)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT(service_id, hour_unix_timestamp) DO UPDATE SET
total_executions = excluded.total_executions + total_executions,
successful_executions = excluded.successful_executions + successful_executions,
total_response_time = excluded.total_response_time + total_response_time
total_executions = excluded.total_executions + service_uptime.total_executions,
successful_executions = excluded.successful_executions + service_uptime.successful_executions,
total_response_time = excluded.total_response_time + service_uptime.total_response_time
`,
serviceID,
unixTimestampFlooredAtHour,
@ -543,12 +487,12 @@ func (s *Store) getServiceStatusByKey(tx *sql.Tx, key string, parameters *paging
serviceStatus := core.NewServiceStatus(key, serviceGroup, serviceName)
if parameters.EventsPageSize > 0 {
if serviceStatus.Events, err = s.getEventsByServiceID(tx, serviceID, parameters.EventsPage, parameters.EventsPageSize); err != nil {
log.Printf("[sqlite][getServiceStatusByKey] Failed to retrieve events for key=%s: %s", key, err.Error())
log.Printf("[sql][getServiceStatusByKey] Failed to retrieve events for key=%s: %s", key, err.Error())
}
}
if parameters.ResultsPageSize > 0 {
if serviceStatus.Results, err = s.getResultsByServiceID(tx, serviceID, parameters.ResultsPage, parameters.ResultsPageSize); err != nil {
log.Printf("[sqlite][getServiceStatusByKey] Failed to retrieve results for key=%s: %s", key, err.Error())
log.Printf("[sql][getServiceStatusByKey] Failed to retrieve results for key=%s: %s", key, err.Error())
}
}
//if parameters.IncludeUptime {
@ -561,7 +505,7 @@ func (s *Store) getServiceStatusByKey(tx *sql.Tx, key string, parameters *paging
}
func (s *Store) getServiceIDGroupAndNameByKey(tx *sql.Tx, key string) (id int64, group, name string, err error) {
rows, err := tx.Query(
err = tx.QueryRow(
`
SELECT service_id, service_group, service_name
FROM service
@ -569,17 +513,13 @@ func (s *Store) getServiceIDGroupAndNameByKey(tx *sql.Tx, key string) (id int64,
LIMIT 1
`,
key,
)
).Scan(&id, &group, &name)
if err != nil {
if err == sql.ErrNoRows {
return 0, "", "", common.ErrServiceNotFound
}
return 0, "", "", err
}
for rows.Next() {
_ = rows.Scan(&id, &group, &name)
}
_ = rows.Close()
if id == 0 {
return 0, "", "", common.ErrServiceNotFound
}
return
}
@ -760,48 +700,27 @@ func (s *Store) getServiceHourlyAverageResponseTimes(tx *sql.Tx, serviceID int64
}
func (s *Store) getServiceID(tx *sql.Tx, service *core.Service) (int64, error) {
rows, err := tx.Query("SELECT service_id FROM service WHERE service_key = $1", service.Key())
if err != nil {
return 0, err
}
var id int64
var found bool
for rows.Next() {
_ = rows.Scan(&id)
found = true
break
}
_ = rows.Close()
if !found {
return 0, common.ErrServiceNotFound
err := tx.QueryRow("SELECT service_id FROM service WHERE service_key = $1", service.Key()).Scan(&id)
if err != nil {
if err == sql.ErrNoRows {
return 0, common.ErrServiceNotFound
}
return 0, err
}
return id, nil
}
func (s *Store) getNumberOfEventsByServiceID(tx *sql.Tx, serviceID int64) (int64, error) {
rows, err := tx.Query("SELECT COUNT(1) FROM service_event WHERE service_id = $1", serviceID)
if err != nil {
return 0, err
}
var numberOfEvents int64
for rows.Next() {
_ = rows.Scan(&numberOfEvents)
}
_ = rows.Close()
return numberOfEvents, nil
err := tx.QueryRow("SELECT COUNT(1) FROM service_event WHERE service_id = $1", serviceID).Scan(&numberOfEvents)
return numberOfEvents, err
}
func (s *Store) getNumberOfResultsByServiceID(tx *sql.Tx, serviceID int64) (int64, error) {
rows, err := tx.Query("SELECT COUNT(1) FROM service_result WHERE service_id = $1", serviceID)
if err != nil {
return 0, err
}
var numberOfResults int64
for rows.Next() {
_ = rows.Scan(&numberOfResults)
}
_ = rows.Close()
return numberOfResults, nil
err := tx.QueryRow("SELECT COUNT(1) FROM service_result WHERE service_id = $1", serviceID).Scan(&numberOfResults)
return numberOfResults, err
}
func (s *Store) getAgeOfOldestServiceUptimeEntry(tx *sql.Tx, serviceID int64) (time.Duration, error) {
@ -833,20 +752,13 @@ func (s *Store) getAgeOfOldestServiceUptimeEntry(tx *sql.Tx, serviceID int64) (t
}
func (s *Store) getLastServiceResultSuccessValue(tx *sql.Tx, serviceID int64) (bool, error) {
rows, err := tx.Query("SELECT success FROM service_result WHERE service_id = $1 ORDER BY service_result_id DESC LIMIT 1", serviceID)
if err != nil {
return false, err
}
var success bool
var found bool
for rows.Next() {
_ = rows.Scan(&success)
found = true
break
}
_ = rows.Close()
if !found {
return false, errNoRowsReturned
err := tx.QueryRow("SELECT success FROM service_result WHERE service_id = $1 ORDER BY service_result_id DESC LIMIT 1", serviceID).Scan(&success)
if err != nil {
if err == sql.ErrNoRows {
return false, errNoRowsReturned
}
return false, err
}
return success, nil
}
@ -868,12 +780,7 @@ func (s *Store) deleteOldServiceEvents(tx *sql.Tx, serviceID int64) error {
serviceID,
common.MaximumNumberOfEvents,
)
if err != nil {
return err
}
//rowsAffected, _ := result.RowsAffected()
//log.Printf("deleted %d rows from service_event", rowsAffected)
return nil
return err
}
// deleteOldServiceResults deletes old service results that are no longer needed
@ -893,20 +800,10 @@ func (s *Store) deleteOldServiceResults(tx *sql.Tx, serviceID int64) error {
serviceID,
common.MaximumNumberOfResults,
)
if err != nil {
return err
}
//rowsAffected, _ := result.RowsAffected()
//log.Printf("deleted %d rows from service_result", rowsAffected)
return nil
return err
}
func (s *Store) deleteOldUptimeEntries(tx *sql.Tx, serviceID int64, maxAge time.Time) error {
_, err := tx.Exec("DELETE FROM service_uptime WHERE service_id = $1 AND hour_unix_timestamp < $2", serviceID, maxAge.Unix())
//if err != nil {
// return err
//}
//rowsAffected, _ := result.RowsAffected()
//log.Printf("deleted %d rows from service_uptime", rowsAffected)
return err
}

View File

@ -1,4 +1,4 @@
package sqlite
package sql
import (
"testing"
@ -157,7 +157,7 @@ func TestStore_InsertCleansUpEventsAndResultsProperly(t *testing.T) {
for i := 0; i < resultsCleanUpThreshold+eventsCleanUpThreshold; i++ {
store.Insert(&testService, &testSuccessfulResult)
store.Insert(&testService, &testUnsuccessfulResult)
ss := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults*5).WithEvents(1, common.MaximumNumberOfEvents*5))
ss, _ := store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults*5).WithEvents(1, common.MaximumNumberOfEvents*5))
if len(ss.Results) > resultsCleanUpThreshold+1 {
t.Errorf("number of results shouldn't have exceeded %d, reached %d", resultsCleanUpThreshold, len(ss.Results))
}
@ -182,7 +182,7 @@ func TestStore_Persistence(t *testing.T) {
if uptime, _ := store.GetUptimeByKey(testService.Key(), time.Now().Add(-time.Hour*24*7), time.Now()); uptime != 0.5 {
t.Errorf("the uptime over the past 7d should've been 0.5, got %f", uptime)
}
ssFromOldStore := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents))
ssFromOldStore, _ := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents))
if ssFromOldStore == nil || ssFromOldStore.Group != "group" || ssFromOldStore.Name != "name" || len(ssFromOldStore.Events) != 3 || len(ssFromOldStore.Results) != 2 {
store.Close()
t.Fatal("sanity check failed")
@ -190,7 +190,7 @@ func TestStore_Persistence(t *testing.T) {
store.Close()
store, _ = NewStore("sqlite", file)
defer store.Close()
ssFromNewStore := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents))
ssFromNewStore, _ := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, common.MaximumNumberOfResults).WithEvents(1, common.MaximumNumberOfEvents))
if ssFromNewStore == nil || ssFromNewStore.Group != "group" || ssFromNewStore.Name != "name" || len(ssFromNewStore.Events) != 3 || len(ssFromNewStore.Results) != 2 {
t.Fatal("failed sanity check")
}
@ -265,12 +265,14 @@ func TestStore_SanityCheck(t *testing.T) {
store, _ := NewStore("sqlite", t.TempDir()+"/TestStore_SanityCheck.db")
defer store.Close()
store.Insert(&testService, &testSuccessfulResult)
if numberOfServiceStatuses := len(store.GetAllServiceStatuses(paging.NewServiceStatusParams())); numberOfServiceStatuses != 1 {
serviceStatuses, _ := store.GetAllServiceStatuses(paging.NewServiceStatusParams())
if numberOfServiceStatuses := len(serviceStatuses); numberOfServiceStatuses != 1 {
t.Fatalf("expected 1 ServiceStatus, got %d", numberOfServiceStatuses)
}
store.Insert(&testService, &testUnsuccessfulResult)
// Both results inserted are for the same service, therefore, the count shouldn't have increased
if numberOfServiceStatuses := len(store.GetAllServiceStatuses(paging.NewServiceStatusParams())); numberOfServiceStatuses != 1 {
serviceStatuses, _ = store.GetAllServiceStatuses(paging.NewServiceStatusParams())
if numberOfServiceStatuses := len(serviceStatuses); numberOfServiceStatuses != 1 {
t.Fatalf("expected 1 ServiceStatus, got %d", numberOfServiceStatuses)
}
if hourlyAverageResponseTime, err := store.GetHourlyAverageResponseTimeByKey(testService.Key(), time.Now().Add(-24*time.Hour), time.Now()); err != nil {
@ -284,7 +286,7 @@ func TestStore_SanityCheck(t *testing.T) {
if averageResponseTime, _ := store.GetAverageResponseTimeByKey(testService.Key(), time.Now().Add(-24*time.Hour), time.Now()); averageResponseTime != 450 {
t.Errorf("expected average response time of last 24h to be 450, got %d", averageResponseTime)
}
ss := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, 20).WithEvents(1, 20))
ss, _ := store.GetServiceStatus(testService.Group, testService.Name, paging.NewServiceStatusParams().WithResults(1, 20).WithEvents(1, 20))
if ss == nil {
t.Fatalf("Store should've had key '%s', but didn't", testService.Key())
}

View File

@ -6,20 +6,20 @@ import (
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sqlite"
"github.com/TwinProduction/gatus/storage/store/sql"
)
// Store is the interface that each stores should implement
type Store interface {
// GetAllServiceStatuses returns the JSON encoding of all monitored core.ServiceStatus
// with a subset of core.Result defined by the page and pageSize parameters
GetAllServiceStatuses(params *paging.ServiceStatusParams) []*core.ServiceStatus
GetAllServiceStatuses(params *paging.ServiceStatusParams) ([]*core.ServiceStatus, error)
// GetServiceStatus returns the service status for a given service name in the given group
GetServiceStatus(groupName, serviceName string, params *paging.ServiceStatusParams) *core.ServiceStatus
GetServiceStatus(groupName, serviceName string, params *paging.ServiceStatusParams) (*core.ServiceStatus, error)
// GetServiceStatusByKey returns the service status for a given key
GetServiceStatusByKey(key string, params *paging.ServiceStatusParams) *core.ServiceStatus
GetServiceStatusByKey(key string, params *paging.ServiceStatusParams) (*core.ServiceStatus, error)
// GetUptimeByKey returns the uptime percentage during a time range
GetUptimeByKey(key string, from, to time.Time) (float64, error)
@ -31,7 +31,7 @@ type Store interface {
GetHourlyAverageResponseTimeByKey(key string, from, to time.Time) (map[int64]int, error)
// Insert adds the observed result for the specified service into the store
Insert(service *core.Service, result *core.Result)
Insert(service *core.Service, result *core.Result) error
// DeleteAllServiceStatusesNotInKeys removes all ServiceStatus that are not within the keys provided
//
@ -44,7 +44,7 @@ type Store interface {
// Save persists the data if and where it needs to be persisted
Save() error
// Close terminates every connections and closes the store, if applicable.
// Close terminates every connection and closes the store, if applicable.
// Should only be used before stopping the application.
Close()
}
@ -54,5 +54,5 @@ type Store interface {
var (
// Validate interface implementation on compile
_ Store = (*memory.Store)(nil)
_ Store = (*sqlite.Store)(nil)
_ Store = (*sql.Store)(nil)
)

View File

@ -7,7 +7,7 @@ import (
"github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sqlite"
"github.com/TwinProduction/gatus/storage/store/sql"
)
func BenchmarkStore_GetAllServiceStatuses(b *testing.B) {
@ -15,7 +15,7 @@ func BenchmarkStore_GetAllServiceStatuses(b *testing.B) {
if err != nil {
b.Fatal("failed to create store:", err.Error())
}
sqliteStore, err := sqlite.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetAllServiceStatuses.db")
sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetAllServiceStatuses.db")
if err != nil {
b.Fatal("failed to create store:", err.Error())
}
@ -73,7 +73,7 @@ func BenchmarkStore_Insert(b *testing.B) {
if err != nil {
b.Fatal("failed to create store:", err.Error())
}
sqliteStore, err := sqlite.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_Insert.db")
sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_Insert.db")
if err != nil {
b.Fatal("failed to create store:", err.Error())
}
@ -145,7 +145,7 @@ func BenchmarkStore_GetServiceStatusByKey(b *testing.B) {
if err != nil {
b.Fatal("failed to create store:", err.Error())
}
sqliteStore, err := sqlite.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetServiceStatusByKey.db")
sqliteStore, err := sql.NewStore("sqlite", b.TempDir()+"/BenchmarkStore_GetServiceStatusByKey.db")
if err != nil {
b.Fatal("failed to create store:", err.Error())
}

View File

@ -8,7 +8,7 @@ import (
"github.com/TwinProduction/gatus/storage/store/common"
"github.com/TwinProduction/gatus/storage/store/common/paging"
"github.com/TwinProduction/gatus/storage/store/memory"
"github.com/TwinProduction/gatus/storage/store/sqlite"
"github.com/TwinProduction/gatus/storage/store/sql"
)
var (
@ -92,7 +92,7 @@ func initStoresAndBaseScenarios(t *testing.T, testName string) []*Scenario {
if err != nil {
t.Fatal("failed to create store:", err.Error())
}
sqliteStore, err := sqlite.NewStore("sqlite", t.TempDir()+"/"+testName+".db")
sqliteStore, err := sql.NewStore("sqlite", t.TempDir()+"/"+testName+".db")
if err != nil {
t.Fatal("failed to create store:", err.Error())
}
@ -125,8 +125,10 @@ func TestStore_GetServiceStatusByKey(t *testing.T) {
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, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
serviceStatus, err := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
if err != nil {
t.Fatal("shouldn't have returned an error, got", err.Error())
}
if serviceStatus == nil {
t.Fatalf("serviceStatus shouldn't have been nil")
}
@ -153,15 +155,24 @@ func TestStore_GetServiceStatusForMissingStatusReturnsNil(t *testing.T) {
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, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
serviceStatus, err := scenario.Store.GetServiceStatus("nonexistantgroup", "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
if err != common.ErrServiceNotFound {
t.Error("should've returned ErrServiceNotFound, got", err)
}
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, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
serviceStatus, err = scenario.Store.GetServiceStatus(testService.Group, "nonexistantname", paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
if err != common.ErrServiceNotFound {
t.Error("should've returned ErrServiceNotFound, got", err)
}
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, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
serviceStatus, err = scenario.Store.GetServiceStatus("nonexistantgroup", testService.Name, paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
if err != common.ErrServiceNotFound {
t.Error("should've returned ErrServiceNotFound, got", err)
}
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)
}
@ -179,7 +190,10 @@ func TestStore_GetAllServiceStatuses(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))
serviceStatuses, err := scenario.Store.GetAllServiceStatuses(paging.NewServiceStatusParams().WithResults(1, 20))
if err != nil {
t.Error("shouldn't have returned an error, got", err.Error())
}
if len(serviceStatuses) != 1 {
t.Fatal("expected 1 service status")
}
@ -208,7 +222,10 @@ func TestStore_GetAllServiceStatusesWithResultsAndEvents(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))
serviceStatuses, err := scenario.Store.GetAllServiceStatuses(paging.NewServiceStatusParams().WithResults(1, 20).WithEvents(1, 50))
if err != nil {
t.Error("shouldn't have returned an error, got", err.Error())
}
if len(serviceStatuses) != 1 {
t.Fatal("expected 1 service status")
}
@ -238,14 +255,20 @@ func TestStore_GetServiceStatusPage1IsHasMoreRecentResultsThanPage2(t *testing.T
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))
serviceStatusPage1, err := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(1, 1))
if err != nil {
t.Error("shouldn't have returned an error, got", err.Error())
}
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))
serviceStatusPage2, err := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithResults(2, 1))
if err != nil {
t.Error("shouldn't have returned an error, got", err.Error())
}
if serviceStatusPage2 == nil {
t.Fatalf("serviceStatusPage2 shouldn't have been nil")
}
@ -398,8 +421,10 @@ func TestStore_Insert(t *testing.T) {
t.Run(scenario.Name, func(t *testing.T) {
scenario.Store.Insert(&testService, &testSuccessfulResult)
scenario.Store.Insert(&testService, &testUnsuccessfulResult)
ss := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
ss, err := scenario.Store.GetServiceStatusByKey(testService.Key(), paging.NewServiceStatusParams().WithEvents(1, common.MaximumNumberOfEvents).WithResults(1, common.MaximumNumberOfResults))
if err != nil {
t.Error("shouldn't have returned an error, got", err)
}
if ss == nil {
t.Fatalf("Store should've had key '%s', but didn't", testService.Key())
}
@ -473,22 +498,23 @@ func TestStore_DeleteAllServiceStatusesNotInKeys(t *testing.T) {
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 {
if ss, _ := scenario.Store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()); ss == nil {
t.Fatal("firstService should exist")
}
if scenario.Store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) == nil {
if ss, _ := scenario.Store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()); ss == nil {
t.Fatal("secondService should exist")
}
scenario.Store.DeleteAllServiceStatusesNotInKeys([]string{firstService.Key()})
if scenario.Store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()) == nil {
if ss, _ := scenario.Store.GetServiceStatusByKey(firstService.Key(), paging.NewServiceStatusParams()); ss == nil {
t.Error("secondService should've been deleted")
}
if scenario.Store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()) != nil {
if ss, _ := scenario.Store.GetServiceStatusByKey(secondService.Key(), paging.NewServiceStatusParams()); ss != nil {
t.Error("firstService should still exist")
}
// Delete everything
scenario.Store.DeleteAllServiceStatusesNotInKeys([]string{})
if len(scenario.Store.GetAllServiceStatuses(paging.NewServiceStatusParams())) != 0 {
serviceStatuses, _ := scenario.Store.GetAllServiceStatuses(paging.NewServiceStatusParams())
if len(serviceStatuses) != 0 {
t.Errorf("everything should've been deleted")
}
})

View File

@ -4,6 +4,7 @@ package storage
type Type string
const (
TypeMemory Type = "memory" // In-memory store
TypeSQLite Type = "sqlite" // SQLite store
TypeMemory Type = "memory" // In-memory store
TypeSQLite Type = "sqlite" // SQLite store
TypePostgres Type = "postgres" // Postgres store
)