mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:17:32 +00:00
fix(eventstore): retry push on primary key sequence collision (#7420)
* fix(eventstore): retry push on primary key sequence collision * MaxRetries config option and unit test
This commit is contained in:
@@ -8,6 +8,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgconn"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/service"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -329,7 +331,7 @@ func Test_eventData(t *testing.T) {
|
||||
|
||||
type testPusher struct {
|
||||
events []Event
|
||||
err error
|
||||
errs []error
|
||||
|
||||
t *testing.T
|
||||
}
|
||||
@@ -339,8 +341,9 @@ func (repo *testPusher) Health(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (repo *testPusher) Push(ctx context.Context, commands ...Command) (events []Event, err error) {
|
||||
if repo.err != nil {
|
||||
return nil, repo.err
|
||||
if len(repo.errs) != 0 {
|
||||
err, repo.errs = repo.errs[0], repo.errs[1:]
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(repo.events) != len(commands) {
|
||||
@@ -439,6 +442,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
events []Command
|
||||
}
|
||||
type fields struct {
|
||||
maxRetries int
|
||||
pusher *testPusher
|
||||
eventMapper map[EventType]func(Event) (Event, error)
|
||||
}
|
||||
@@ -465,6 +469,51 @@ func TestEventstore_Push(t *testing.T) {
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 1,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
events: []Event{
|
||||
&BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
ID: "1",
|
||||
Type: "test.aggregate",
|
||||
ResourceOwner: "caos",
|
||||
InstanceID: "zitadel",
|
||||
},
|
||||
Data: []byte(nil),
|
||||
User: "editorUser",
|
||||
EventType: "test.event",
|
||||
},
|
||||
},
|
||||
},
|
||||
eventMapper: map[EventType]func(Event) (Event, error){
|
||||
"test.event": func(e Event) (Event, error) {
|
||||
return &testEvent{
|
||||
BaseEvent: BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
Type: e.Aggregate().Type,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "one aggregate one event, retry disabled",
|
||||
args: args{
|
||||
events: []Command{
|
||||
newTestEvent(
|
||||
"1",
|
||||
"",
|
||||
func() interface{} {
|
||||
return []byte(nil)
|
||||
},
|
||||
false),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 0,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
events: []Event{
|
||||
@@ -515,6 +564,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 1,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
events: []Event{
|
||||
@@ -586,6 +636,7 @@ func TestEventstore_Push(t *testing.T) {
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 1,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
events: combineEventLists(
|
||||
@@ -658,9 +709,10 @@ func TestEventstore_Push(t *testing.T) {
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 1,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
err: zerrors.ThrowInternal(nil, "V2-qaa4S", "test err"),
|
||||
t: t,
|
||||
errs: []error{zerrors.ThrowInternal(nil, "V2-qaa4S", "test err")},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
@@ -681,21 +733,179 @@ func TestEventstore_Push(t *testing.T) {
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 1,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
err: zerrors.ThrowInternal(nil, "V2-qaa4S", "test err"),
|
||||
t: t,
|
||||
errs: []error{zerrors.ThrowInternal(nil, "V2-qaa4S", "test err")},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "retry succeeds",
|
||||
args: args{
|
||||
events: []Command{
|
||||
newTestEvent(
|
||||
"1",
|
||||
"",
|
||||
func() interface{} {
|
||||
return []byte(nil)
|
||||
},
|
||||
false),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 1,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
events: []Event{
|
||||
&BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
ID: "1",
|
||||
Type: "test.aggregate",
|
||||
ResourceOwner: "caos",
|
||||
InstanceID: "zitadel",
|
||||
},
|
||||
Data: []byte(nil),
|
||||
User: "editorUser",
|
||||
EventType: "test.event",
|
||||
},
|
||||
},
|
||||
errs: []error{
|
||||
zerrors.ThrowInternal(&pgconn.PgError{
|
||||
ConstraintName: "events2_pkey",
|
||||
Code: "23505",
|
||||
}, "foo-err", "Errors.Internal"),
|
||||
},
|
||||
},
|
||||
eventMapper: map[EventType]func(Event) (Event, error){
|
||||
"test.event": func(e Event) (Event, error) {
|
||||
return &testEvent{
|
||||
BaseEvent: BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
Type: e.Aggregate().Type,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "retry fails",
|
||||
args: args{
|
||||
events: []Command{
|
||||
newTestEvent(
|
||||
"1",
|
||||
"",
|
||||
func() interface{} {
|
||||
return []byte(nil)
|
||||
},
|
||||
false),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 1,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
events: []Event{
|
||||
&BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
ID: "1",
|
||||
Type: "test.aggregate",
|
||||
ResourceOwner: "caos",
|
||||
InstanceID: "zitadel",
|
||||
},
|
||||
Data: []byte(nil),
|
||||
User: "editorUser",
|
||||
EventType: "test.event",
|
||||
},
|
||||
},
|
||||
errs: []error{
|
||||
zerrors.ThrowInternal(&pgconn.PgError{
|
||||
ConstraintName: "events2_pkey",
|
||||
Code: "23505",
|
||||
}, "foo-err", "Errors.Internal"),
|
||||
zerrors.ThrowInternal(&pgconn.PgError{
|
||||
ConstraintName: "events2_pkey",
|
||||
Code: "23505",
|
||||
}, "foo-err", "Errors.Internal"),
|
||||
},
|
||||
},
|
||||
eventMapper: map[EventType]func(Event) (Event, error){
|
||||
"test.event": func(e Event) (Event, error) {
|
||||
return &testEvent{
|
||||
BaseEvent: BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
Type: e.Aggregate().Type,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{wantErr: true},
|
||||
},
|
||||
{
|
||||
name: "retry disabled",
|
||||
args: args{
|
||||
events: []Command{
|
||||
newTestEvent(
|
||||
"1",
|
||||
"",
|
||||
func() interface{} {
|
||||
return []byte(nil)
|
||||
},
|
||||
false),
|
||||
},
|
||||
},
|
||||
fields: fields{
|
||||
maxRetries: 0,
|
||||
pusher: &testPusher{
|
||||
t: t,
|
||||
events: []Event{
|
||||
&BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
ID: "1",
|
||||
Type: "test.aggregate",
|
||||
ResourceOwner: "caos",
|
||||
InstanceID: "zitadel",
|
||||
},
|
||||
Data: []byte(nil),
|
||||
User: "editorUser",
|
||||
EventType: "test.event",
|
||||
},
|
||||
},
|
||||
errs: []error{
|
||||
zerrors.ThrowInternal(&pgconn.PgError{
|
||||
ConstraintName: "events2_pkey",
|
||||
Code: "23505",
|
||||
}, "foo-err", "Errors.Internal"),
|
||||
},
|
||||
},
|
||||
eventMapper: map[EventType]func(Event) (Event, error){
|
||||
"test.event": func(e Event) (Event, error) {
|
||||
return &testEvent{
|
||||
BaseEvent: BaseEvent{
|
||||
Agg: &Aggregate{
|
||||
Type: e.Aggregate().Type,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{wantErr: true},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
eventInterceptors = map[EventType]eventTypeInterceptors{}
|
||||
es := &Eventstore{
|
||||
pusher: tt.fields.pusher,
|
||||
maxRetries: tt.fields.maxRetries,
|
||||
pusher: tt.fields.pusher,
|
||||
}
|
||||
for eventType, mapper := range tt.fields.eventMapper {
|
||||
RegisterFilterEventMapper("test", eventType, mapper)
|
||||
|
Reference in New Issue
Block a user