fix: env.json caching, readiness and unique lockerIDs (#3596)

* fix: readiness check

* disable cache for env.json

* always generate unique lockerID

* fix tests
This commit is contained in:
Livio Amstutz 2022-05-04 17:09:49 +02:00 committed by GitHub
parent 929ed8745a
commit 94e420bb24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 8 additions and 276 deletions

View File

@ -15,7 +15,6 @@ import (
"github.com/zitadel/zitadel/internal/api/grpc/server" "github.com/zitadel/zitadel/internal/api/grpc/server"
http_util "github.com/zitadel/zitadel/internal/api/http" http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/authz/repository" "github.com/zitadel/zitadel/internal/authz/repository"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/telemetry/tracing" "github.com/zitadel/zitadel/internal/telemetry/tracing"
@ -139,19 +138,6 @@ func (a *API) healthHandler() http.Handler {
} }
return nil return nil
}, },
func(ctx context.Context) error {
iam, err := a.health.Instance(ctx)
if err != nil && !errors.IsNotFound(err) {
return errors.ThrowPreconditionFailed(err, "API-dsgT2", "IAM SETUP CHECK FAILED")
}
if iam == nil || iam.SetupStarted < domain.StepCount-1 {
return errors.ThrowPreconditionFailed(nil, "API-HBfs3", "IAM NOT SET UP")
}
if iam.SetupDone < domain.StepCount-1 {
return errors.ThrowPreconditionFailed(nil, "API-DASs2", "IAM SETUP RUNNING")
}
return nil
},
} }
handler := http.NewServeMux() handler := http.NewServeMux()
handler.HandleFunc("/healthz", handleHealth) handler.HandleFunc("/healthz", handleHealth)

View File

@ -73,7 +73,7 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, inst
security := middleware.SecurityHeaders(csp(), nil) security := middleware.SecurityHeaders(csp(), nil)
handler := mux.NewRouter() handler := mux.NewRouter()
handler.Use(cache, security) handler.Use(security)
handler.Handle(envRequestPath, middleware.TelemetryHandler()(instanceHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handler.Handle(envRequestPath, middleware.TelemetryHandler()(instanceHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
instance := authz.GetInstance(r.Context()) instance := authz.GetInstance(r.Context())
if instance.InstanceID() == "" { if instance.InstanceID() == "" {
@ -89,7 +89,7 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, inst
_, err = w.Write(environmentJSON) _, err = w.Write(environmentJSON)
logging.OnError(err).Error("error serving environment.json") logging.OnError(err).Error("error serving environment.json")
})))) }))))
handler.SkipClean(true).PathPrefix("").Handler(http.FileServer(&spaHandler{http.FS(fSys)})) handler.SkipClean(true).PathPrefix("").Handler(cache(http.FileServer(&spaHandler{http.FS(fSys)})))
return handler, nil return handler, nil
} }

View File

@ -15,9 +15,6 @@ type InstanceWriteModel struct {
State domain.InstanceState State domain.InstanceState
GeneratedDomain string GeneratedDomain string
SetUpStarted domain.Step
SetUpDone domain.Step
GlobalOrgID string GlobalOrgID string
ProjectID string ProjectID string
DefaultLanguage language.Tag DefaultLanguage language.Tag
@ -53,12 +50,6 @@ func (wm *InstanceWriteModel) Reduce() error {
wm.GlobalOrgID = e.OrgID wm.GlobalOrgID = e.OrgID
case *instance.DefaultLanguageSetEvent: case *instance.DefaultLanguageSetEvent:
wm.DefaultLanguage = e.Language wm.DefaultLanguage = e.Language
case *instance.SetupStepEvent:
if e.Done {
wm.SetUpDone = e.Step
} else {
wm.SetUpStarted = e.Step
}
} }
} }
return nil return nil
@ -78,9 +69,7 @@ func (wm *InstanceWriteModel) Query() *eventstore.SearchQueryBuilder {
instance.InstanceDomainRemovedEventType, instance.InstanceDomainRemovedEventType,
instance.ProjectSetEventType, instance.ProjectSetEventType,
instance.GlobalOrgSetEventType, instance.GlobalOrgSetEventType,
instance.DefaultLanguageSetEventType, instance.DefaultLanguageSetEventType).
instance.SetupStartedEventType,
instance.SetupDoneEventType).
Builder() Builder()
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"os"
"time" "time"
"github.com/zitadel/logging" "github.com/zitadel/logging"
@ -34,11 +33,8 @@ type locker struct {
} }
func NewLocker(client *sql.DB, lockTable, projectionName string) Locker { func NewLocker(client *sql.DB, lockTable, projectionName string) Locker {
workerName, err := os.Hostname() workerName, err := id.SonyFlakeGenerator.Next()
if err != nil || workerName == "" { logging.OnError(err).Panic("unable to generate lockID")
workerName, err = id.SonyFlakeGenerator.Next()
logging.OnError(err).Panic("unable to generate lockID")
}
return &locker{ return &locker{
client: client, client: client,
lockStmt: fmt.Sprintf(lockStmtFormat, lockTable), lockStmt: fmt.Sprintf(lockStmtFormat, lockTable),

View File

@ -2,7 +2,6 @@ package spooler
import ( import (
"math/rand" "math/rand"
"os"
"github.com/zitadel/logging" "github.com/zitadel/logging"
@ -19,11 +18,8 @@ type Config struct {
} }
func (c *Config) New() *Spooler { func (c *Config) New() *Spooler {
lockID, err := os.Hostname() lockID, err := id.SonyFlakeGenerator.Next()
if err != nil || lockID == "" { logging.OnError(err).Panic("unable to generate lockID")
lockID, err = id.SonyFlakeGenerator.Next()
logging.OnError(err).Panic("unable to generate lockID")
}
//shuffle the handlers for better balance when running multiple pods //shuffle the handlers for better balance when running multiple pods
rand.Shuffle(len(c.ViewHandlers), func(i, j int) { rand.Shuffle(len(c.ViewHandlers), func(i, j int) {

View File

@ -11,7 +11,6 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query/projection" "github.com/zitadel/zitadel/internal/query/projection"
) )
@ -56,14 +55,6 @@ var (
name: projection.InstanceColumnConsoleAppID, name: projection.InstanceColumnConsoleAppID,
table: instanceTable, table: instanceTable,
} }
InstanceColumnSetupStarted = Column{
name: projection.InstanceColumnSetUpStarted,
table: instanceTable,
}
InstanceColumnSetupDone = Column{
name: projection.InstanceColumnSetUpDone,
table: instanceTable,
}
InstanceColumnDefaultLanguage = Column{ InstanceColumnDefaultLanguage = Column{
name: projection.InstanceColumnDefaultLanguage, name: projection.InstanceColumnDefaultLanguage,
table: instanceTable, table: instanceTable,
@ -82,8 +73,6 @@ type Instance struct {
ConsoleID string ConsoleID string
ConsoleAppID string ConsoleAppID string
DefaultLang language.Tag DefaultLang language.Tag
SetupStarted domain.Step
SetupDone domain.Step
Domains []*InstanceDomain Domains []*InstanceDomain
host string host string
} }
@ -211,8 +200,6 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
InstanceColumnProjectID.identifier(), InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(), InstanceColumnConsoleID.identifier(),
InstanceColumnConsoleAppID.identifier(), InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(), InstanceColumnDefaultLanguage.identifier(),
). ).
From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar), From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
@ -228,8 +215,6 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
&instance.IAMProjectID, &instance.IAMProjectID,
&instance.ConsoleID, &instance.ConsoleID,
&instance.ConsoleAppID, &instance.ConsoleAppID,
&instance.SetupStarted,
&instance.SetupDone,
&lang, &lang,
) )
if err != nil { if err != nil {
@ -254,8 +239,6 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
InstanceColumnProjectID.identifier(), InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(), InstanceColumnConsoleID.identifier(),
InstanceColumnConsoleAppID.identifier(), InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(), InstanceColumnDefaultLanguage.identifier(),
countColumn.identifier(), countColumn.identifier(),
).From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar), ).From(instanceTable.identifier()).PlaceholderFormat(sq.Dollar),
@ -276,8 +259,6 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
&instance.IAMProjectID, &instance.IAMProjectID,
&instance.ConsoleID, &instance.ConsoleID,
&instance.ConsoleAppID, &instance.ConsoleAppID,
&instance.SetupStarted,
&instance.SetupDone,
&lang, &lang,
&count, &count,
) )
@ -312,8 +293,6 @@ func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Rows)
InstanceColumnProjectID.identifier(), InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(), InstanceColumnConsoleID.identifier(),
InstanceColumnConsoleAppID.identifier(), InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(), InstanceColumnDefaultLanguage.identifier(),
InstanceDomainDomainCol.identifier(), InstanceDomainDomainCol.identifier(),
InstanceDomainIsPrimaryCol.identifier(), InstanceDomainIsPrimaryCol.identifier(),
@ -350,8 +329,6 @@ func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Rows)
&instance.IAMProjectID, &instance.IAMProjectID,
&instance.ConsoleID, &instance.ConsoleID,
&instance.ConsoleAppID, &instance.ConsoleAppID,
&instance.SetupStarted,
&instance.SetupDone,
&lang, &lang,
&domain, &domain,
&isPrimary, &isPrimary,

View File

@ -11,7 +11,6 @@ import (
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
errs "github.com/zitadel/zitadel/internal/errors" errs "github.com/zitadel/zitadel/internal/errors"
) )
@ -41,8 +40,6 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.iam_project_id,`+ ` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+ ` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+ ` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+ ` projections.instances.default_language`+
` FROM projections.instances`), ` FROM projections.instances`),
nil, nil,
@ -72,8 +69,6 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.iam_project_id,`+ ` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+ ` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+ ` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+ ` projections.instances.default_language`+
` FROM projections.instances`), ` FROM projections.instances`),
[]string{ []string{
@ -85,8 +80,6 @@ func Test_InstancePrepares(t *testing.T) {
"iam_project_id", "iam_project_id",
"console_client_id", "console_client_id",
"console_app_id", "console_app_id",
"setup_started",
"setup_done",
"default_language", "default_language",
}, },
[]driver.Value{ []driver.Value{
@ -98,8 +91,6 @@ func Test_InstancePrepares(t *testing.T) {
"project-id", "project-id",
"client-id", "client-id",
"app-id", "app-id",
domain.Step2,
domain.Step1,
"en", "en",
}, },
), ),
@ -113,8 +104,6 @@ func Test_InstancePrepares(t *testing.T) {
IAMProjectID: "project-id", IAMProjectID: "project-id",
ConsoleID: "client-id", ConsoleID: "client-id",
ConsoleAppID: "app-id", ConsoleAppID: "app-id",
SetupStarted: domain.Step2,
SetupDone: domain.Step1,
DefaultLang: language.English, DefaultLang: language.English,
}, },
}, },
@ -133,8 +122,6 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.iam_project_id,`+ ` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+ ` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+ ` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+ ` projections.instances.default_language`+
` FROM projections.instances`), ` FROM projections.instances`),
sql.ErrConnDone, sql.ErrConnDone,

View File

@ -22,8 +22,6 @@ const (
InstanceColumnConsoleID = "console_client_id" InstanceColumnConsoleID = "console_client_id"
InstanceColumnConsoleAppID = "console_app_id" InstanceColumnConsoleAppID = "console_app_id"
InstanceColumnSequence = "sequence" InstanceColumnSequence = "sequence"
InstanceColumnSetUpStarted = "setup_started"
InstanceColumnSetUpDone = "setup_done"
InstanceColumnDefaultLanguage = "default_language" InstanceColumnDefaultLanguage = "default_language"
) )
@ -46,8 +44,6 @@ func NewInstanceProjection(ctx context.Context, config crdb.StatementHandlerConf
crdb.NewColumn(InstanceColumnConsoleID, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(InstanceColumnConsoleID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnConsoleAppID, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(InstanceColumnConsoleAppID, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(InstanceColumnSequence, crdb.ColumnTypeInt64), crdb.NewColumn(InstanceColumnSequence, crdb.ColumnTypeInt64),
crdb.NewColumn(InstanceColumnSetUpStarted, crdb.ColumnTypeInt64, crdb.Default(0)),
crdb.NewColumn(InstanceColumnSetUpDone, crdb.ColumnTypeInt64, crdb.Default(0)),
crdb.NewColumn(InstanceColumnDefaultLanguage, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(InstanceColumnDefaultLanguage, crdb.ColumnTypeText, crdb.Default("")),
}, },
crdb.NewPrimaryKey(InstanceColumnID), crdb.NewPrimaryKey(InstanceColumnID),
@ -82,14 +78,6 @@ func (p *InstanceProjection) reducers() []handler.AggregateReducer {
Event: instance.DefaultLanguageSetEventType, Event: instance.DefaultLanguageSetEventType,
Reduce: p.reduceDefaultLanguageSet, Reduce: p.reduceDefaultLanguageSet,
}, },
{
Event: instance.SetupStartedEventType,
Reduce: p.reduceSetupEvent,
},
{
Event: instance.SetupDoneEventType,
Reduce: p.reduceSetupEvent,
},
}, },
}, },
} }
@ -184,24 +172,3 @@ func (p *InstanceProjection) reduceDefaultLanguageSet(event eventstore.Event) (*
}, },
), nil ), nil
} }
func (p *InstanceProjection) reduceSetupEvent(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SetupStepEvent)
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-d9nfw", "reduce.wrong.event.type %v", []eventstore.EventType{instance.SetupDoneEventType, instance.SetupStartedEventType})
}
columns := []handler.Column{
handler.NewCol(InstanceColumnID, e.Aggregate().InstanceID),
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
}
if e.EventType == instance.SetupStartedEventType {
columns = append(columns, handler.NewCol(InstanceColumnSetUpStarted, e.Step))
} else {
columns = append(columns, handler.NewCol(InstanceColumnSetUpDone, e.Step))
}
return crdb.NewUpsertStatement(
e,
columns,
), nil
}

View File

@ -3,7 +3,6 @@ package projection
import ( import (
"testing" "testing"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/handler" "github.com/zitadel/zitadel/internal/eventstore/handler"
@ -141,66 +140,6 @@ func TestInstanceProjection_reduces(t *testing.T) {
}, },
}, },
}, },
{
name: "reduceSetupStarted",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.SetupStartedEventType),
instance.AggregateType,
[]byte(`{"Step": 1}`),
), instance.SetupStepMapper),
},
reduce: (&InstanceProjection{}).reduceSetupEvent,
want: wantReduce{
projection: InstanceProjectionTable,
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, setup_started) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{
"instance-id",
anyArg{},
uint64(15),
domain.Step1,
},
},
},
},
},
},
{
name: "reduceSetupDone",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.SetupDoneEventType),
instance.AggregateType,
[]byte(`{"Step": 1}`),
), instance.SetupStepMapper),
},
reduce: (&InstanceProjection{}).reduceSetupEvent,
want: wantReduce{
projection: InstanceProjectionTable,
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPSERT INTO projections.instances (id, change_date, sequence, setup_done) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{
"instance-id",
anyArg{},
uint64(15),
domain.Step1,
},
},
},
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -1,103 +0,0 @@
package instance
import (
"context"
"encoding/json"
"strconv"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
const (
UniqueStepStarted = "stepstarted"
UniqueStepDone = "stepdone"
SetupDoneEventType eventstore.EventType = "iam.setup.done"
SetupStartedEventType eventstore.EventType = "iam.setup.started"
)
type SetupStepEvent struct {
eventstore.BaseEvent `json:"-"`
Step domain.Step `json:"Step"`
Done bool `json:"-"`
}
func NewAddSetupStepStartedUniqueConstraint(step domain.Step) *eventstore.EventUniqueConstraint {
return eventstore.NewAddEventUniqueConstraint(
UniqueStepStarted,
strconv.Itoa(int(step)),
"Errors.Step.Started.AlreadyExists")
}
func NewAddSetupStepDoneUniqueConstraint(step domain.Step) *eventstore.EventUniqueConstraint {
return eventstore.NewAddEventUniqueConstraint(
UniqueStepDone,
strconv.Itoa(int(step)),
"Errors.Step.Done.AlreadyExists")
}
func (e *SetupStepEvent) Data() interface{} {
return e
}
func (e *SetupStepEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
if e.Done {
return []*eventstore.EventUniqueConstraint{NewAddSetupStepDoneUniqueConstraint(e.Step)}
} else {
return []*eventstore.EventUniqueConstraint{NewAddSetupStepStartedUniqueConstraint(e.Step)}
}
}
func SetupStepMapper(event *repository.Event) (eventstore.Event, error) {
step := &SetupStepEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
Done: eventstore.EventType(event.Type) == SetupDoneEventType,
Step: domain.Step1,
}
if len(event.Data) == 0 {
return step, nil
}
err := json.Unmarshal(event.Data, step)
if err != nil {
return nil, errors.ThrowInternal(err, "IAM-O6rVg", "unable to unmarshal step")
}
return step, nil
}
func NewSetupStepDoneEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
step domain.Step,
) *SetupStepEvent {
return &SetupStepEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
SetupDoneEventType,
),
Step: step,
Done: true,
}
}
func NewSetupStepStartedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
step domain.Step,
) *SetupStepEvent {
return &SetupStepEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
SetupStartedEventType,
),
Step: step,
}
}

View File

@ -5,9 +5,7 @@ import (
) )
func RegisterEventMappers(es *eventstore.Eventstore) { func RegisterEventMappers(es *eventstore.Eventstore) {
es.RegisterFilterEventMapper(SetupStartedEventType, SetupStepMapper). es.RegisterFilterEventMapper(GlobalOrgSetEventType, GlobalOrgSetMapper).
RegisterFilterEventMapper(SetupDoneEventType, SetupStepMapper).
RegisterFilterEventMapper(GlobalOrgSetEventType, GlobalOrgSetMapper).
RegisterFilterEventMapper(ProjectSetEventType, ProjectSetMapper). RegisterFilterEventMapper(ProjectSetEventType, ProjectSetMapper).
RegisterFilterEventMapper(ConsoleSetEventType, ConsoleSetMapper). RegisterFilterEventMapper(ConsoleSetEventType, ConsoleSetMapper).
RegisterFilterEventMapper(DefaultLanguageSetEventType, DefaultLanguageSetMapper). RegisterFilterEventMapper(DefaultLanguageSetEventType, DefaultLanguageSetMapper).