@@ -173,7 +173,7 @@ func TestObjectSync(t *testing.T) {
173173 t .Run ("SlaLifeCycle" , func (t * testing.T ) {
174174 t .Parallel ()
175175
176- slinfo := & SlaLifecycle { CreateTime : types .UnixMilli (time .Now ())}
176+ createTime := types .UnixMilli (time .Now ())
177177
178178 t .Run ("Hosts" , func (t * testing.T ) {
179179 t .Parallel ()
@@ -182,8 +182,14 @@ func TestObjectSync(t *testing.T) {
182182 t .Run ("Verify-Host-" + fmt .Sprint (hostId ), func (t * testing.T ) {
183183 t .Parallel ()
184184
185+ slinfo := & SlaLifecycle {CreateTime : createTime }
186+
185187 eventually .Assert (t , func (t require.TestingT ) {
186- verifySlaLifeCycleRow (t , db , slinfo , host .Name , "" )
188+ // We can't join on the host table, as the sla lifecycle entries may reference hosts that have
189+ // already been deleted. So fetch the host id from DB before performing the actual test.
190+ require .NoError (t , fetchCheckableId (db , slinfo , host .Name , "" ))
191+
192+ verifySlaLifecycleRow (t , db , slinfo , false )
187193 }, 20 * time .Second , 1 * time .Second )
188194 })
189195 }
@@ -196,8 +202,14 @@ func TestObjectSync(t *testing.T) {
196202 t .Run ("Verify-Service-" + fmt .Sprint (serviceId ), func (t * testing.T ) {
197203 t .Parallel ()
198204
205+ slinfo := & SlaLifecycle {CreateTime : createTime }
206+
199207 eventually .Assert (t , func (t require.TestingT ) {
200- verifySlaLifeCycleRow (t , db , slinfo , * service .HostName , service .Name )
208+ // We can't join on the service table, as the sla lifecycle entries may reference services that
209+ // have already been deleted. So fetch the service id from DB before performing the actual test.
210+ require .NoError (t , fetchCheckableId (db , slinfo , * service .HostName , service .Name ))
211+
212+ verifySlaLifecycleRow (t , db , slinfo , false )
201213 }, 20 * time .Second , 1 * time .Second )
202214 })
203215 }
@@ -452,29 +464,71 @@ func TestObjectSync(t *testing.T) {
452464 t .Run ("SlaLifeCycle" , func (t * testing.T ) {
453465 t .Parallel ()
454466
455- for serviceId , service := range makeTestSyncServices (t ) {
456- //service.Name += fmt.Sprint(serviceId)
467+ assertCheckableFunc := func (checkable any , objType string , objName string , host string , service string ) {
468+ client .CreateObject (t , objType , objName , map [string ]any {
469+ "attrs" : makeIcinga2ApiAttributes (checkable , false ),
470+ })
457471
458- t .Run ("Verify-Service-" + fmt .Sprint (serviceId ), func (t * testing.T ) {
459- t .Parallel ()
472+ slinfo := & SlaLifecycle {CreateTime : types .UnixMilli (time .Now ())}
473+ eventually .Assert (t , func (t require.TestingT ) {
474+ // We can't join on the host/service tables, as the sla lifecycle entries may reference checkables
475+ // that have already been deleted. So fetch the host/service id from DB before performing the actual test.
476+ require .NoError (t , fetchCheckableId (db , slinfo , host , service ))
460477
461- client .CreateObject (t , "services" , * service .HostName + "!" + service .Name , map [string ]interface {}{
462- "attrs" : makeIcinga2ApiAttributes (service , false ),
463- })
478+ verifySlaLifecycleRow (t , db , slinfo , false )
479+ }, 20 * time .Second , 1 * time .Second )
464480
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 )
481+ client .DeleteObject (t , objType , objName , false )
469482
470- client .DeleteObject (t , "services" , * service .HostName + "!" + service .Name , false )
483+ slinfo .DeleteTime = types .UnixMilli (time .Now ())
484+ eventually .Assert (t , func (t require.TestingT ) {
485+ verifySlaLifecycleRow (t , db , slinfo , false )
486+ }, 20 * time .Second , 1 * time .Second )
471487
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 )
488+ client .CreateObject (t , objType , objName , map [string ]interface {}{
489+ "attrs" : makeIcinga2ApiAttributes (checkable , false ),
476490 })
491+
492+ // We are recreating this checkable, so we only have to change the timestamps as the
493+ // checkable id will remain the same.
494+ slinfo .CreateTime = types .UnixMilli (time .Now ())
495+ slinfo .DeleteTime = types .UnixMilli (time.Time {})
496+
497+ eventually .Assert (t , func (t require.TestingT ) {
498+ verifySlaLifecycleRow (t , db , slinfo , true )
499+ }, 20 * time .Second , 1 * time .Second )
500+
501+ client .DeleteObject (t , objType , objName , false )
502+
503+ slinfo .DeleteTime = types .UnixMilli (time .Now ())
504+ eventually .Assert (t , func (t require.TestingT ) {
505+ verifySlaLifecycleRow (t , db , slinfo , true )
506+ }, 20 * time .Second , 1 * time .Second )
477507 }
508+
509+ t .Run ("Host" , func (t * testing.T ) {
510+ t .Parallel ()
511+
512+ for hostId , host := range makeTestSyncHosts (t ) {
513+ t .Run ("Verify-Host-" + fmt .Sprint (hostId ), func (t * testing.T ) {
514+ t .Parallel ()
515+
516+ assertCheckableFunc (host , "hosts" , host .Name , host .Name , "" )
517+ })
518+ }
519+ })
520+
521+ t .Run ("Service" , func (t * testing.T ) {
522+ t .Parallel ()
523+
524+ for serviceId , service := range makeTestSyncServices (t ) {
525+ t .Run ("Verify-Service-" + fmt .Sprint (serviceId ), func (t * testing.T ) {
526+ t .Parallel ()
527+
528+ assertCheckableFunc (service , "services" , * service .HostName + "!" + service .Name , * service .HostName , service .Name )
529+ })
530+ }
531+ })
478532 })
479533
480534 t .Run ("User" , func (t * testing.T ) {
@@ -1247,61 +1301,80 @@ func verifyIcingaDbRow(t require.TestingT, db *sqlx.DB, obj interface{}) {
12471301 require .False (t , rows .Next (), "SQL query should return only one row: %s" , query )
12481302}
12491303
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"`
1304+ // verifySlaLifecycleRow verifies the sla lifecycle entries matching the given host/service id. It checks the creation
1305+ // and deletion time of the specified checkable. When the provided checkable was recreated, it also additionally requires
1306+ // two sla lifecycle entries to exist that match the checkables id.
1307+ func verifySlaLifecycleRow (t require.TestingT , db * sqlx.DB , slinfo * SlaLifecycle , isRecreated bool ) {
1308+ query := `SELECT "create_time", "delete_time" FROM "sla_lifecycle" WHERE "host_id" = ?`
12521309 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`
1310+ args = []interface {}{slinfo .HostID }
1311+ if ! slinfo .ServiceID .Valid () {
1312+ query += ` AND "service_id" IS NULL`
12671313 } 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- }
1314+ query += ` AND "service_id" = ?`
1315+ args = append (args , slinfo .ServiceID )
12761316 }
1317+ query += ` ORDER BY "create_time" ASC`
12771318
12781319 var resultSet []SlaLifecycle
12791320 err := db .Select (& resultSet , db .Rebind (query ), args ... )
12801321 require .NoError (t , err , "querying sla lifecycle should not fail: Query: %q" , query )
12811322
1282- require .Len (t , resultSet , 1 , "there should be one sla lifecycle entry" )
1283-
1284- result := resultSet [0 ]
12851323 zerotimestamp := time .Unix (0 , 0 )
1324+ var result SlaLifecycle
12861325
1287- require .NotEqual (t , zerotimestamp , result .CreateTime .Time ())
1326+ if isRecreated {
1327+ require .Len (t , resultSet , 2 , "there should be two sla lifecycle entry" )
1328+
1329+ result = resultSet [1 ]
1330+ recreated := resultSet [0 ]
1331+ assert .NotEqual (t , zerotimestamp , recreated .CreateTime .Time ())
1332+ assert .NotEqual (t , zerotimestamp , recreated .DeleteTime .Time ())
1333+
1334+ assert .Less (t , recreated .CreateTime .Time (), slinfo .CreateTime .Time ())
1335+ assert .Less (t , recreated .DeleteTime .Time (), slinfo .CreateTime .Time ())
1336+
1337+ if ! slinfo .DeleteTime .Time ().IsZero () {
1338+ assert .Less (t , recreated .CreateTime .Time (), slinfo .DeleteTime .Time ())
1339+ assert .Less (t , recreated .DeleteTime .Time (), slinfo .DeleteTime .Time ())
1340+ }
1341+ } else {
1342+ require .Len (t , resultSet , 1 , "there should be one sla lifecycle entry" )
1343+
1344+ result = resultSet [0 ]
1345+ }
1346+
1347+ assert .NotEqual (t , zerotimestamp , result .CreateTime .Time ())
12881348 assert .WithinDuration (t , slinfo .CreateTime .Time (), result .CreateTime .Time (), time .Minute )
12891349
12901350 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 ())
1351+ assert .Equal (t , zerotimestamp , result .DeleteTime .Time ())
12981352 } else {
1299- require .NotEqual (t , zerotimestamp , result .DeleteTime .Time ())
1300- require .Less (t , result .CreateTime .Time (), result .DeleteTime .Time ())
1353+ assert .NotEqual (t , zerotimestamp , result .DeleteTime .Time ())
1354+ assert .Less (t , result .CreateTime .Time (), result .DeleteTime .Time ())
13011355 assert .WithinDuration (t , slinfo .DeleteTime .Time (), result .DeleteTime .Time (), time .Minute )
13021356 }
13031357}
13041358
1359+ // fetchCheckableId retrieves host/service id from the database matching the given host/service
1360+ // name and scans to the provided slinfo. Returns an error on any database failure.
1361+ func fetchCheckableId (db * sqlx.DB , slinfo * SlaLifecycle , host string , service string ) error {
1362+ query := `SELECT "sla_lifecycle"."host_id", "sla_lifecycle"."service_id"
1363+ FROM "sla_lifecycle" INNER JOIN "host" ON "host"."id"="sla_lifecycle"."host_id"`
1364+
1365+ where := ` WHERE "host"."name" = ?`
1366+ args := []interface {}{host }
1367+ if service == "" {
1368+ where += ` AND "service_id" IS NULL`
1369+ } else {
1370+ query += ` INNER JOIN "service" ON "service"."id"="sla_lifecycle"."service_id"`
1371+ where += ` AND "service"."name" = ?`
1372+ args = append (args , service )
1373+ }
1374+
1375+ return db .QueryRowx (db .Rebind (query + where ), args ... ).StructScan (slinfo )
1376+ }
1377+
13051378type SlaLifecycle struct {
13061379 CreateTime types.UnixMilli `db:"create_time"`
13071380 DeleteTime types.UnixMilli `db:"delete_time"`
0 commit comments