Skip to content

Commit b37318e

Browse files
committed
wip: working prototype. needs rebasing tho
1 parent 5415f23 commit b37318e

2 files changed

Lines changed: 58 additions & 53 deletions

File tree

pkg/icingadb/sync.go

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -173,34 +173,29 @@ func (s Sync) ApplyDelta(ctx context.Context, delta *Delta) error {
173173
if len(delta.Delete) > 0 {
174174
s.logger.Infof("Deleting %d items of type %s", len(delta.Delete), strcase.Delimited(types.Name(delta.Subject.Entity()), ' '))
175175

176-
// TODO: generalize, make errCh easier to read
177176
ids := delta.Delete.IDs()
178-
if _, ok := delta.Subject.Entity().(*v1.Downtime); ok {
179-
// Those downtimes are probably removed from the configuration files, i.e. Icinga 2 won't send
180-
// the corresponding downtime end/removed events for them. So try to mark them as cancelled manually,
181-
// so that they don't show up as if they were still active in the UI.
182-
//
183-
// The reason why we don't perform this in a separate goroutine like the other ones is that we don't
184-
// want to delete the downtimes until we've successfully updated the downtime history records. So,
185-
// if we fail to update the downtime history records, for whatever reason, forward the error to the
186-
// config sync group and abort the config sync.
187-
if err := s.mockDowntimeEnd(ctx, ids); err != nil {
188-
errCh := make(chan error, 1)
189-
errCh <- err
190-
close(errCh)
191-
com.ErrgroupReceive(g, errCh)
192-
}
193-
} else if _, ok := delta.Subject.Entity().(*v1.Comment); ok {
194-
if err := s.mockCommentEnd(ctx, ids); err != nil {
195-
errCh := make(chan error, 1)
196-
errCh <- err
197-
close(errCh)
198-
com.ErrgroupReceive(g, errCh)
199-
}
177+
178+
// Those objects are probably removed from the configuration files, i.e., Icinga 2 won't send the
179+
// corresponding end/removed events for them. So try to mark them as cancelled manually, so that
180+
// they don't show up as if they were still active in the UI.
181+
//
182+
// This is not performed in a separate goroutine to ensure that the relevant Comment/Downtime
183+
// object is not deleted before closing the history.
184+
185+
var err error
186+
switch delta.Subject.Entity().(type) {
187+
case *v1.Comment:
188+
err = s.mockCommentEnd(ctx, ids)
189+
case *v1.Downtime:
190+
err = s.mockDowntimeEnd(ctx, ids)
191+
}
192+
193+
if ctx.Err() == nil {
194+
g.Go(func() error { return err })
200195
}
201196

202-
// Start the deletion process only if we haven't aborted the config sync
203-
// due to an error in the downtime history update above.
197+
// Start the deletion process only if we haven't aborted the config sync due to an error in the
198+
// downtime history update above.
204199
if ctx.Err() == nil {
205200
g.Go(func() error {
206201
return s.db.Delete(ctx, delta.Subject.Entity(), ids, database.OnSuccessIncrement[any](stat))

pkg/icingadb/sync_history_mock.go

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,15 @@ import (
77
"time"
88

99
"github.com/icinga/icinga-go-library/backoff"
10-
"github.com/icinga/icinga-go-library/database"
1110
"github.com/icinga/icinga-go-library/objectpacker"
1211
"github.com/icinga/icinga-go-library/retry"
1312
"github.com/icinga/icinga-go-library/types"
1413
"github.com/icinga/icingadb/pkg/icingadb/v1/history"
1514
"github.com/jmoiron/sqlx"
1615
"github.com/pkg/errors"
16+
"go.uber.org/zap"
1717
)
1818

19-
// calcEventId mocks Icinga 2's IcingaDB::CalcEventID method for missing history entries.
20-
//
21-
// This is a helper function used below to simulate a new history.id primary key.
22-
func calcEventId(environmentId, eventType, objectName string) (types.Binary, error) {
23-
h := sha1.New() // #nosec G401 -- required by Icinga 2's HashValue function
24-
25-
err := objectpacker.PackAny([]string{environmentId, eventType, objectName}, h)
26-
if err != nil {
27-
return nil, err
28-
}
29-
30-
return types.Binary(h.Sum(nil)), nil
31-
}
32-
3319
// mkGenericHistoryMockStmts creates SQL queries to acquire data and to create a new history entry.
3420
//
3521
// This helper function is used in the methods implementing closeObjectAndMockHistory below.
@@ -57,37 +43,57 @@ func mkGenericHistoryMockStmts(tableName string) (stmtPopulate, stmtHistoryInser
5743
return
5844
}
5945

46+
// calcEventId mocks Icinga 2's IcingaDB::CalcEventID method for missing history entries.
47+
//
48+
// This is a helper function used below to simulate a new history.id primary key.
49+
func calcEventId(environmentId, eventType, objectName string) (types.Binary, error) {
50+
h := sha1.New() // #nosec G401 -- required by Icinga 2's HashValue function
51+
52+
err := objectpacker.PackAny([]string{environmentId, eventType, objectName}, h)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
return types.Binary(h.Sum(nil)), nil
58+
}
59+
6060
// closeObjectAndMockHistory manually closes vanished Icinga 2 objects, e.g., Downtimes of vanished Hosts.
6161
//
6262
// This function provides a common stub for each type-specific implementation below.
63-
func closeObjectAndMockHistory(
63+
func (s Sync) closeObjectAndMockHistory(
6464
ctx context.Context,
65-
db *database.DB,
6665
ids []any,
66+
objectType string,
6767
action func(ctx context.Context, tx *sqlx.Tx, id types.Binary) error,
6868
) error {
69+
startTime := time.Now()
70+
6971
for _, id := range ids {
7072
binaryId, ok := id.(types.Binary)
7173
if !ok {
7274
return errors.Errorf("ID is not types.Binary, but %T", id)
7375
}
7476

75-
// TODO: Pipeline multiple action calls into one transaction.
7677
err := retry.WithBackoff(
7778
ctx,
7879
func(ctx context.Context) error {
79-
return db.ExecTx(ctx, func(ctx context.Context, tx *sqlx.Tx) error {
80+
return s.db.ExecTx(ctx, func(ctx context.Context, tx *sqlx.Tx) error {
8081
return action(ctx, tx, binaryId)
8182
})
8283
},
8384
retry.Retryable,
8485
backoff.DefaultBackoff,
85-
db.GetDefaultRetrySettings())
86+
s.db.GetDefaultRetrySettings())
8687
if err != nil {
8788
return errors.Wrap(err, "can't mock object end")
8889
}
8990
}
9091

92+
s.logger.Infow("Manually closed vanished objects",
93+
zap.String("type", objectType),
94+
zap.Int("amount", len(ids)),
95+
zap.Duration("duration", time.Since(startTime)))
96+
9197
return nil
9298
}
9399

@@ -114,11 +120,13 @@ func (s Sync) mockCommentEnd(ctx context.Context, ids []any) error {
114120
has_been_removed = :has_been_removed
115121
WHERE comment_id = :comment_id`
116122

117-
return closeObjectAndMockHistory(
123+
return s.closeObjectAndMockHistory(
118124
ctx,
119-
s.db,
120125
ids,
126+
"comment",
121127
func(ctx context.Context, tx *sqlx.Tx, id types.Binary) error {
128+
eventTime := time.Now()
129+
122130
comment := &Comment{
123131
CommentHistory: history.CommentHistory{
124132
CommentHistoryEntity: history.CommentHistoryEntity{
@@ -141,7 +149,7 @@ func (s Sync) mockCommentEnd(ctx context.Context, ids []any) error {
141149

142150
// Fill fields to update comment_history
143151
comment.RemovedBy = types.MakeString("Comment Config Removed")
144-
comment.RemoveTime = types.UnixMilli(time.Now())
152+
comment.RemoveTime = types.UnixMilli(eventTime)
145153
comment.HasBeenRemoved = types.MakeBool(true)
146154

147155
_, err = tx.NamedExecContext(ctx, stmtCommentHistoryUpdate, comment)
@@ -156,7 +164,7 @@ func (s Sync) mockCommentEnd(ctx context.Context, ids []any) error {
156164
}
157165

158166
comment.EventType = "comment_remove"
159-
comment.EventTime = types.UnixMilli(time.Now())
167+
comment.EventTime = types.UnixMilli(eventTime)
160168

161169
_, err = tx.NamedExecContext(ctx, stmtHistoryInsert, comment)
162170
if err != nil {
@@ -196,11 +204,13 @@ func (s Sync) mockDowntimeEnd(ctx context.Context, ids []any) error {
196204
SET downtime_end = :cancel_time
197205
WHERE downtime_id = :downtime_id`
198206

199-
return closeObjectAndMockHistory(
207+
return s.closeObjectAndMockHistory(
200208
ctx,
201-
s.db,
202209
ids,
210+
"downtime",
203211
func(ctx context.Context, tx *sqlx.Tx, id types.Binary) error {
212+
eventTime := time.Now()
213+
204214
downtime := &Downtime{
205215
DowntimeHistory: history.DowntimeHistory{
206216
DowntimeHistoryEntity: history.DowntimeHistoryEntity{
@@ -224,7 +234,7 @@ func (s Sync) mockDowntimeEnd(ctx context.Context, ids []any) error {
224234
// Fill fields to update downtime_history and sla_history_downtime.
225235
downtime.CancelledBy = types.MakeString("Downtime Config Removed")
226236
downtime.HasBeenCancelled = types.MakeBool(true)
227-
downtime.CancelTime = types.UnixMilli(time.Now())
237+
downtime.CancelTime = types.UnixMilli(eventTime)
228238

229239
_, err = tx.NamedExecContext(ctx, stmtDowntimeHistoryUpdate, downtime)
230240
if err != nil {
@@ -243,7 +253,7 @@ func (s Sync) mockDowntimeEnd(ctx context.Context, ids []any) error {
243253
}
244254

245255
downtime.EventType = "downtime_end"
246-
downtime.EventTime = types.UnixMilli(time.Now())
256+
downtime.EventTime = types.UnixMilli(eventTime)
247257

248258
_, err = tx.NamedExecContext(ctx, stmtHistoryInsert, downtime)
249259
if err != nil {

0 commit comments

Comments
 (0)