66 "database/sql"
77 _ "embed"
88 "fmt"
9+ "github.com/icinga/icinga-go-library/types"
910 "github.com/icinga/icinga-testing/services"
1011 "github.com/icinga/icinga-testing/utils"
1112 "github.com/icinga/icinga-testing/utils/eventually"
@@ -16,9 +17,9 @@ import (
1617 "github.com/stretchr/testify/assert"
1718 "github.com/stretchr/testify/require"
1819 "go.uber.org/zap"
19- "golang.org/x/exp/slices"
2020 "io"
2121 "reflect"
22+ "slices"
2223 "sort"
2324 "strings"
2425 "testing"
@@ -112,7 +113,6 @@ func TestObjectSync(t *testing.T) {
112113 t .Parallel ()
113114
114115 for _ , host := range data .Hosts {
115- host := host
116116 t .Run ("Verify-" + host .VariantInfoString (), func (t * testing.T ) {
117117 t .Parallel ()
118118
@@ -144,7 +144,6 @@ func TestObjectSync(t *testing.T) {
144144 t .Parallel ()
145145
146146 for _ , service := range data .Services {
147- service := service
148147 t .Run ("Verify-" + service .VariantInfoString (), func (t * testing.T ) {
149148 t .Parallel ()
150149
@@ -171,6 +170,40 @@ func TestObjectSync(t *testing.T) {
171170 }
172171 })
173172
173+ t .Run ("SlaLifeCycle" , func (t * testing.T ) {
174+ t .Parallel ()
175+
176+ slinfo := & SlaLifecycle {CreateTime : types .UnixMilli (time .Now ())}
177+
178+ t .Run ("Hosts" , func (t * testing.T ) {
179+ t .Parallel ()
180+
181+ for hostId , host := range data .Hosts {
182+ t .Run ("Verify-Host-" + fmt .Sprint (hostId ), func (t * testing.T ) {
183+ t .Parallel ()
184+
185+ eventually .Assert (t , func (t require.TestingT ) {
186+ verifySlaLifeCycleRow (t , db , slinfo , host .Name , "" )
187+ }, 20 * time .Second , 1 * time .Second )
188+ })
189+ }
190+ })
191+
192+ t .Run ("Services" , func (t * testing.T ) {
193+ t .Parallel ()
194+
195+ for serviceId , service := range data .Services {
196+ t .Run ("Verify-Service-" + fmt .Sprint (serviceId ), func (t * testing.T ) {
197+ t .Parallel ()
198+
199+ eventually .Assert (t , func (t require.TestingT ) {
200+ verifySlaLifeCycleRow (t , db , slinfo , * service .HostName , service .Name )
201+ }, 20 * time .Second , 1 * time .Second )
202+ })
203+ }
204+ })
205+ })
206+
174207 t .Run ("HostGroup" , func (t * testing.T ) {
175208 t .Parallel ()
176209 // TODO(jb): add tests
@@ -324,8 +357,6 @@ func TestObjectSync(t *testing.T) {
324357 t .Parallel ()
325358
326359 for _ , service := range makeTestSyncServices (t ) {
327- service := service
328-
329360 t .Run ("CreateAndDelete-" + service .VariantInfoString (), func (t * testing.T ) {
330361 t .Parallel ()
331362
@@ -418,6 +449,34 @@ func TestObjectSync(t *testing.T) {
418449 })
419450 })
420451
452+ t .Run ("SlaLifeCycle" , func (t * testing.T ) {
453+ t .Parallel ()
454+
455+ for serviceId , service := range makeTestSyncServices (t ) {
456+ //service.Name += fmt.Sprint(serviceId)
457+
458+ t .Run ("Verify-Service-" + fmt .Sprint (serviceId ), func (t * testing.T ) {
459+ t .Parallel ()
460+
461+ client .CreateObject (t , "services" , * service .HostName + "!" + service .Name , map [string ]interface {}{
462+ "attrs" : makeIcinga2ApiAttributes (service , false ),
463+ })
464+
465+ slinfo := & SlaLifecycle {CreateTime : types .UnixMilli (time .Now ())}
466+ eventually .Assert (t , func (t require.TestingT ) {
467+ verifySlaLifeCycleRow (t , db , slinfo , * service .HostName , service .Name )
468+ }, 20 * time .Second , 1 * time .Second )
469+
470+ client .DeleteObject (t , "services" , * service .HostName + "!" + service .Name , false )
471+
472+ slinfo .DeleteTime = types .UnixMilli (time .Now ())
473+ eventually .Assert (t , func (t require.TestingT ) {
474+ verifySlaLifeCycleRow (t , db , slinfo , "" , "" )
475+ }, 20 * time .Second , 1 * time .Second )
476+ })
477+ }
478+ })
479+
421480 t .Run ("User" , func (t * testing.T ) {
422481 t .Parallel ()
423482
@@ -1188,6 +1247,68 @@ func verifyIcingaDbRow(t require.TestingT, db *sqlx.DB, obj interface{}) {
11881247 require .False (t , rows .Next (), "SQL query should return only one row: %s" , query )
11891248}
11901249
1250+ func verifySlaLifeCycleRow (t require.TestingT , db * sqlx.DB , slinfo * SlaLifecycle , host string , service string ) {
1251+ query := `SELECT "create_time", "delete_time", "sla_lifecycle"."host_id", "sla_lifecycle"."service_id" FROM "sla_lifecycle"`
1252+ var args []interface {}
1253+ if ! slinfo .HostID .Valid () {
1254+ query += ` INNER JOIN "host" ON "host"."id"="sla_lifecycle"."host_id"`
1255+ where := ` WHERE "host"."name"=?`
1256+
1257+ args = append (args , host )
1258+ if service == "" {
1259+ where += ` AND "service_id" IS NULL`
1260+ } else {
1261+ query += ` INNER JOIN "service" ON "service"."id"="sla_lifecycle"."service_id"`
1262+ where += ` AND "service"."name"=?`
1263+ args = append (args , service )
1264+ }
1265+
1266+ query += where + ` AND "delete_time" = 0`
1267+ } else {
1268+ query += ` WHERE "host_id"=?`
1269+ args = []interface {}{slinfo .HostID }
1270+ if ! slinfo .ServiceID .Valid () {
1271+ query += ` AND "service_id" IS NULL`
1272+ } else {
1273+ query += ` AND "service_id"=?`
1274+ args = append (args , slinfo .ServiceID )
1275+ }
1276+ }
1277+
1278+ var resultSet []SlaLifecycle
1279+ err := db .Select (& resultSet , db .Rebind (query ), args ... )
1280+ require .NoError (t , err , "querying sla lifecycle should not fail: Query: %q" , query )
1281+
1282+ require .Len (t , resultSet , 1 , "there should be one sla lifecycle entry" )
1283+
1284+ result := resultSet [0 ]
1285+ zerotimestamp := time .Unix (0 , 0 )
1286+
1287+ require .NotEqual (t , zerotimestamp , result .CreateTime .Time ())
1288+ assert .WithinDuration (t , slinfo .CreateTime .Time (), result .CreateTime .Time (), time .Minute )
1289+
1290+ if slinfo .DeleteTime .Time ().IsZero () {
1291+ // We can't join on the host/service tables, as the sla lifecycle entries may reference entries that have
1292+ // already been deleted. So cache the host/service id to use as a filter when asserting the sla lifecycles
1293+ // delete event.
1294+ slinfo .HostID = result .HostID
1295+ slinfo .ServiceID = result .ServiceID
1296+
1297+ require .Equal (t , zerotimestamp , result .DeleteTime .Time ())
1298+ } else {
1299+ require .NotEqual (t , zerotimestamp , result .DeleteTime .Time ())
1300+ require .Less (t , result .CreateTime .Time (), result .DeleteTime .Time ())
1301+ assert .WithinDuration (t , slinfo .DeleteTime .Time (), result .DeleteTime .Time (), time .Minute )
1302+ }
1303+ }
1304+
1305+ type SlaLifecycle struct {
1306+ CreateTime types.UnixMilli `db:"create_time"`
1307+ DeleteTime types.UnixMilli `db:"delete_time"`
1308+ HostID types.Binary `db:"host_id"`
1309+ ServiceID types.Binary `db:"service_id"`
1310+ }
1311+
11911312// newString allocates a new *string and initializes it. This helper function exists as
11921313// there seems to be no way to achieve this within a single statement.
11931314func newString (s string ) * string {
0 commit comments