1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
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")
})
}
|