-
Notifications
You must be signed in to change notification settings - Fork 596
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(#3097): add TranslationFailure and TranslationFailuresCollector …
…with unit tests (#3110) Introduces utility types that are going to be used for collecting failures that happen during the translation process.
- Loading branch information
Showing
2 changed files
with
164 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package parser | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/sirupsen/logrus" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
const ( | ||
// TranslationFailureReasonUnknown is used when no specific reason is specified when creating a TranslationFailure. | ||
TranslationFailureReasonUnknown = "unknown" | ||
) | ||
|
||
// TranslationFailure represents an error occurring during translating Kubernetes objects into Kong ones. | ||
// It can be associated with one or more Kubernetes objects. | ||
type TranslationFailure struct { | ||
causingObjects []client.Object | ||
reason string | ||
} | ||
|
||
// NewTranslationFailure creates a TranslationFailure with a reason that should be a human-readable explanation | ||
// of the error reason, and a causingObjects slice that specifies what objects have caused the error. | ||
func NewTranslationFailure(reason string, causingObjects ...client.Object) (TranslationFailure, error) { | ||
if reason == "" { | ||
reason = TranslationFailureReasonUnknown | ||
} | ||
if len(causingObjects) < 1 { | ||
return TranslationFailure{}, fmt.Errorf("no causing objects specified, reason: %s", reason) | ||
} | ||
|
||
return TranslationFailure{ | ||
causingObjects: causingObjects, | ||
reason: reason, | ||
}, nil | ||
} | ||
|
||
// CausingObjects returns a slice of objects that have caused the translation error. | ||
func (p TranslationFailure) CausingObjects() []client.Object { | ||
return p.causingObjects | ||
} | ||
|
||
// Reason returns a human-readable reason of the failure. | ||
func (p TranslationFailure) Reason() string { | ||
return p.reason | ||
} | ||
|
||
// TranslationFailuresCollector should be used to collect all translation failures that happen during the translation process. | ||
type TranslationFailuresCollector struct { | ||
failures []TranslationFailure | ||
logger logrus.FieldLogger | ||
} | ||
|
||
func NewTranslationFailuresCollector(logger logrus.FieldLogger) (*TranslationFailuresCollector, error) { | ||
if logger == nil { | ||
return nil, errors.New("missing logger") | ||
} | ||
return &TranslationFailuresCollector{logger: logger}, nil | ||
} | ||
|
||
// PushTranslationFailure registers a translation failure. | ||
func (c *TranslationFailuresCollector) PushTranslationFailure(reason string, causingObjects ...client.Object) { | ||
translationErr, err := NewTranslationFailure(reason, causingObjects...) | ||
if err != nil { | ||
c.logger.Warningf("failed to create translation failure: %w", err) | ||
return | ||
} | ||
|
||
c.failures = append(c.failures, translationErr) | ||
} | ||
|
||
// PopTranslationFailures returns all translation failures that occurred during the translation process and erases them | ||
// in the collector. It makes the collector reusable during next translation runs. | ||
func (c *TranslationFailuresCollector) PopTranslationFailures() []TranslationFailure { | ||
errs := c.failures | ||
c.failures = nil | ||
|
||
return errs | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package parser_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/sirupsen/logrus" | ||
"github.com/sirupsen/logrus/hooks/test" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/kong/kubernetes-ingress-controller/v2/internal/dataplane/parser" | ||
kongv1 "github.com/kong/kubernetes-ingress-controller/v2/pkg/apis/configuration/v1" | ||
) | ||
|
||
const someValidTranslationFailureReason = "some valid reason" | ||
|
||
func someValidTranslationFailureCausingObjects() []client.Object { | ||
return []client.Object{&kongv1.KongIngress{}, &kongv1.KongPlugin{}} | ||
} | ||
|
||
func TestTranslationFailure(t *testing.T) { | ||
t.Run("is created and returns reason and causing objects", func(t *testing.T) { | ||
transErr, err := parser.NewTranslationFailure(someValidTranslationFailureReason, someValidTranslationFailureCausingObjects()...) | ||
require.NoError(t, err) | ||
|
||
assert.Equal(t, someValidTranslationFailureReason, transErr.Reason()) | ||
assert.ElementsMatch(t, someValidTranslationFailureCausingObjects(), transErr.CausingObjects()) | ||
}) | ||
|
||
t.Run("fallbacks to unknown reason when empty", func(t *testing.T) { | ||
transErr, err := parser.NewTranslationFailure("", someValidTranslationFailureCausingObjects()...) | ||
require.NoError(t, err) | ||
require.Equal(t, parser.TranslationFailureReasonUnknown, transErr.Reason()) | ||
}) | ||
|
||
t.Run("requires at least one causing object", func(t *testing.T) { | ||
_, err := parser.NewTranslationFailure(someValidTranslationFailureReason, someValidTranslationFailureCausingObjects()[0]) | ||
require.NoError(t, err) | ||
|
||
_, err = parser.NewTranslationFailure(someValidTranslationFailureReason) | ||
require.Error(t, err) | ||
}) | ||
} | ||
|
||
func TestTranslationFailuresCollector(t *testing.T) { | ||
testLogger, _ := test.NewNullLogger() | ||
|
||
t.Run("is created when logger valid", func(t *testing.T) { | ||
collector, err := parser.NewTranslationFailuresCollector(testLogger) | ||
require.NoError(t, err) | ||
require.NotNil(t, collector) | ||
}) | ||
|
||
t.Run("requires non nil logger", func(t *testing.T) { | ||
_, err := parser.NewTranslationFailuresCollector(nil) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("pushes and pops translation failures", func(t *testing.T) { | ||
collector, err := parser.NewTranslationFailuresCollector(testLogger) | ||
require.NoError(t, err) | ||
|
||
collector.PushTranslationFailure(someValidTranslationFailureReason, someValidTranslationFailureCausingObjects()...) | ||
collector.PushTranslationFailure(someValidTranslationFailureReason, someValidTranslationFailureCausingObjects()...) | ||
|
||
collectedErrors := collector.PopTranslationFailures() | ||
require.Len(t, collectedErrors, 2) | ||
require.Empty(t, collector.PopTranslationFailures(), "second call should not return any failure") | ||
}) | ||
|
||
t.Run("does not crash but logs warning when no causing objects passed", func(t *testing.T) { | ||
logger, loggerHook := test.NewNullLogger() | ||
collector, err := parser.NewTranslationFailuresCollector(logger) | ||
require.NoError(t, err) | ||
|
||
collector.PushTranslationFailure(someValidTranslationFailureReason) | ||
|
||
lastLog := loggerHook.LastEntry() | ||
require.NotNil(t, lastLog) | ||
require.Equal(t, logrus.WarnLevel, lastLog.Level) | ||
require.Len(t, collector.PopTranslationFailures(), 0, "no failures expected - causing objects missing") | ||
}) | ||
} |