package icingadb_test import ( "github.com/icinga/icinga-testing/utils" "github.com/icinga/icinga-testing/utils/eventually" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "strconv" "testing" "time" ) // Regression test for https://github.com/Icinga/icingadb/pull/394 // // Upsert and delete runtime objects for the same object must be executed in-order. Otherwise, an old update might // replace the object in the database with an older version or delete an object even if it still exists. func TestRegression394(t *testing.T) { numObjects := 200 logger := it.Logger(t) r := it.RedisServerT(t) rdb := getDatabase(t) i := it.Icinga2NodeT(t, "master") i.EnableIcingaDb(r) err := i.Reload() require.NoError(t, err, "icinga2 reload") // Wait for Icinga 2 to signal a successful dump before starting // Icinga DB to ensure that we actually test the initial sync. logger.Debug("waiting for icinga2 dump done signal") waitForDumpDoneSignal(t, r, 20*time.Second, 100*time.Millisecond) // Only after that, start Icinga DB. logger.Debug("starting icingadb") it.IcingaDbInstanceT(t, r, rdb) client := i.ApiClient() db, err := sqlx.Open(rdb.Driver(), rdb.DSN()) require.NoError(t, err, "connecting to SQL database shouldn't fail") t.Cleanup(func() { _ = db.Close() }) waitForPendingRuntimeUpdates := func(t *testing.T) { // To verify that all host runtime updates up to now have been processed, create a marker host, wait for it to // appear in the database, delete it, and wait for it to disappear again. markerName := utils.UniqueName(t, "marker") client.CreateHost(t, markerName, nil) eventually.Require(t, func(t require.TestingT) { var count int err = db.Get(&count, db.Rebind("SELECT COUNT(*) FROM host WHERE name = ?"), markerName) require.NoError(t, err, "select host count from database") assert.Equalf(t, 1, count, "marker host %q should appear in database", markerName) }, 10*time.Second, 100*time.Millisecond) client.DeleteHost(t, markerName, true) eventually.Require(t, func(t require.TestingT) { var count int err = db.Get(&count, db.Rebind("SELECT COUNT(*) FROM host WHERE name = ?"), markerName) require.NoError(t, err, "select host count from database") assert.Zerof(t, count, "marker host %q should disappear from database", markerName) }, 10*time.Second, 100*time.Millisecond) } t.Run("CreateAndDelete", func(t *testing.T) { // This test creates a number of hosts and deletes them immediately afterwards. If the delete operation for a // host is executed before the upsert operation, it would still exist in the database even though the object // is gone from Icinga 2. t.Parallel() namePrefix := utils.UniqueName(t, "host") + "-" for i := 0; i < numObjects; i++ { name := namePrefix + strconv.Itoa(i) client.CreateHost(t, name, nil) client.DeleteHost(t, name, true) } waitForPendingRuntimeUpdates(t) var countAfter int err = db.Get(&countAfter, db.Rebind("SELECT COUNT(*) FROM host WHERE name LIKE ?"), namePrefix+"%") require.NoError(t, err, "select host count from database") assert.Zero(t, countAfter, "no hosts should be left in database") }) t.Run("DeleteAndRecreate", func(t *testing.T) { // This test performs an operation that is probably more useful: delete an existing object and recreate it // immediately afterwards. If the delete operation is delayed, the host will be missing from the database. t.Parallel() namePrefix := utils.UniqueName(t, "host") + "-" hosts := make([]string, numObjects) for i := range hosts { name := namePrefix + strconv.Itoa(i) client.CreateHost(t, name, nil) hosts[i] = name } waitForPendingRuntimeUpdates(t) var countBefore int err = db.Get(&countBefore, db.Rebind("SELECT COUNT(*) FROM host WHERE name LIKE ?"), namePrefix+"%") require.NoError(t, err, "select host count from database") assert.Equal(t, numObjects, countBefore, "all hosts should exist in database before recreation") for _, name := range hosts { client.DeleteHost(t, name, true) client.CreateHost(t, name, nil) } waitForPendingRuntimeUpdates(t) var countAfter int err = db.Get(&countAfter, db.Rebind("SELECT COUNT(*) FROM host WHERE name LIKE ?"), namePrefix+"%") require.NoError(t, err, "select host count from database") assert.Equal(t, numObjects, countAfter, "all hosts should exist in database after recreation") }) }