diff --git a/common/runtime/model.go b/common/runtime/model.go index 289fad3ee9..14aa276dd1 100644 --- a/common/runtime/model.go +++ b/common/runtime/model.go @@ -6,6 +6,8 @@ import ( "strings" "time" + kebError "github.com/kyma-project/kyma-environment-broker/internal/error" + "github.com/kyma-project/control-plane/components/provisioner/pkg/gqlschema" ) @@ -254,6 +256,7 @@ type Operation struct { FinishedStages []string `json:"finishedStages"` ExecutedButNotCompletedSteps []string `json:"executedButNotCompletedSteps,omitempty"` Parameters ProvisioningParametersDTO `json:"parameters,omitempty"` + Error *kebError.LastError `json:"error,omitempty"` } type RuntimesPage struct { diff --git a/internal/edp/errors.go b/internal/edp/errors.go index 5ff707c259..efc0954b4b 100644 --- a/internal/edp/errors.go +++ b/internal/edp/errors.go @@ -13,7 +13,7 @@ type edpError struct { message string } -type EDPErrReason = kebError.ErrReason +type EDPErrReason = kebError.Reason const ( ErrEDPConflict EDPErrReason = "err_edp_internal" @@ -51,11 +51,11 @@ func (e edpError) Code() int { return e.code } -func (e edpError) Component() kebError.ErrComponent { - return kebError.ErrEDP +func (e edpError) GetDependency() kebError.Component { + return kebError.EDPDependency } -func (e edpError) Reason() EDPErrReason { +func (e edpError) GetReason() EDPErrReason { reason := ErrEDPOther switch e.code { diff --git a/internal/error/errors.go b/internal/error/errors.go index 1521b3906e..8612640429 100644 --- a/internal/error/errors.go +++ b/internal/error/errors.go @@ -1,7 +1,6 @@ package error import ( - "encoding/json" "errors" "strings" @@ -11,139 +10,158 @@ import ( ) const OperationTimeOutMsg string = "operation has reached the time limit" +const NotSet = "not-set" + +type Reason string +type Component string type ErrorReporter interface { error - Reason() ErrReason - Component() ErrComponent + GetReason() Reason + GetDependency() Component } // error reporter type LastError struct { - message string - reason ErrReason - component ErrComponent -} - -type LastErrorJSON struct { - Message string `json:"message"` - Reason ErrReason `json:"reason"` - Component ErrComponent `json:"component"` + Message string `json:"message,omitempty"` + Reason Reason `json:"reason,omitempty"` + Component Component `json:"component,omitempty"` + Step string `json:"step,omitempty"` + Team string `json:"team,omitempty"` } -type ErrReason string - const ( - ErrKEBInternal ErrReason = "err_keb_internal" - ErrKEBTimeOut ErrReason = "err_keb_timeout" - ErrProvisionerNilLastError ErrReason = "err_provisioner_nil_last_error" - ErrHttpStatusCode ErrReason = "err_http_status_code" - ErrClusterNotFound ErrReason = "err_cluster_not_found" - ErrK8SUnexpectedServerError ErrReason = "err_k8s_unexpected_server_error" - ErrK8SUnexpectedObjectError ErrReason = "err_k8s_unexpected_object_error" - ErrK8SNoMatchError ErrReason = "err_k8s_no_match_error" - ErrK8SAmbiguousError ErrReason = "err_k8s_ambiguous_error" + KEBInternalCode Reason = "err_keb_internal" + KEBTimeOutCode Reason = "err_keb_timeout" + ProvisionerCode Reason = "err_provisioner_nil_last_error" + HttpStatusCode Reason = "err_http_status_code" + ClusterNotFoundCode Reason = "err_cluster_not_found" + K8SUnexpectedServerCode Reason = "err_k8s_unexpected_server_error" + K8SUnexpectedObjectCode Reason = "err_k8s_unexpected_object_error" + K8SNoMatchCode Reason = "err_k8s_no_match_error" + K8SAmbiguousCode Reason = "err_k8s_ambiguous_error" ) -type ErrComponent string - const ( - ErrDB ErrComponent = "db - keb" - ErrK8SClient ErrComponent = "k8s client - keb" - ErrKEB ErrComponent = "keb" - ErrEDP ErrComponent = "edp" - ErrProvisioner ErrComponent = "provisioner" - ErrReconciler ErrComponent = "reconciler" + KebDbDependency Component = "db - keb" + K8sDependency Component = "k8s client - keb" + KEBDependency Component = "keb" + EDPDependency Component = "edp" + ProvisionerDependency Component = "provisioner" + ReconcileDependency Component = "reconciler" + KIMDependency Component = "kim" + LifeCycleManagerDependency Component = "lifecycle-manager" ) -func (err LastError) Reason() ErrReason { - return err.reason +func (err LastError) GetReason() Reason { + return err.Reason } -func (err LastError) Component() ErrComponent { - return err.component +func (err LastError) GetDependency() Component { + return err.Component } func (err LastError) Error() string { - return err.message + return err.Message } -func (err LastError) SetComponent(component ErrComponent) LastError { - err.component = component +func (err LastError) SetComponent(component Component) LastError { + err.Component = component return err } -func (err LastError) SetReason(reason ErrReason) LastError { - err.reason = reason +func (err LastError) SetReason(reason Reason) LastError { + err.Reason = reason return err } func (err LastError) SetMessage(msg string) LastError { - err.message = msg + err.Message = msg + return err +} + +func (err LastError) StepName() string { + return err.Step +} + +func (err LastError) SetStepName(stepName string) LastError { + err.Step = stepName return err } -func TimeoutError(msg string) LastError { +func TimeoutError(msg, step string) LastError { return LastError{ - message: msg, - reason: ErrKEBTimeOut, - component: ErrKEB, + Message: msg, + Reason: KEBTimeOutCode, + Component: KEBDependency, + Step: step, } } // resolve error component and reason -func ReasonForError(err error) LastError { +func ReasonForError(err error, step string) LastError { if err == nil { return LastError{} } cause := UnwrapAll(err) - if lastErr := checkK8SError(cause); lastErr.component == ErrK8SClient { - lastErr.message = err.Error() + if lastErr := checkK8SError(cause); lastErr.Component == K8sDependency { + lastErr.Message = err.Error() + lastErr.Step = step return lastErr } if status := ErrorReporter(nil); errors.As(cause, &status) { return LastError{ - message: err.Error(), - reason: status.Reason(), - component: status.Component(), + Message: err.Error(), + Reason: status.GetReason(), + Component: status.GetDependency(), + Step: step, } } if ee, ok := cause.(gcli.ExtendedError); ok { - var errReason ErrReason - var errComponent ErrComponent + var errReason Reason + var errComponent Component + var errStep string reason, found := ee.Extensions()["error_reason"] if found { if r, ok := reason.(string); ok { - errReason = ErrReason(r) + errReason = Reason(r) } } component, found := ee.Extensions()["error_component"] if found { if c, ok := component.(string); ok { - errComponent = ErrComponent(c) + errComponent = Component(c) + } + } + step, found := ee.Extensions()["error_step"] + if found { + if s, ok := step.(string); ok { + errStep = s } } return LastError{ - message: err.Error(), - reason: errReason, - component: errComponent, + Message: err.Error(), + Reason: errReason, + Component: errComponent, + Step: errStep, } } if strings.Contains(err.Error(), OperationTimeOutMsg) { - return TimeoutError(err.Error()) + return TimeoutError(err.Error(), step) } return LastError{ - message: err.Error(), - reason: ErrKEBInternal, - component: ErrKEB, + Message: err.Error(), + Reason: KEBInternalCode, + Component: KEBDependency, + Step: step, } } @@ -154,23 +172,23 @@ func checkK8SError(cause error) LastError { switch { case errors.As(cause, &status): if apierr.IsUnexpectedServerError(cause) { - lastErr.reason = ErrK8SUnexpectedServerError + lastErr.Reason = K8SUnexpectedServerCode } else { // reason could be an empty unknown "" - lastErr.reason = ErrReason(apierr.ReasonForError(cause)) + lastErr.Reason = Reason(apierr.ReasonForError(cause)) } - lastErr.component = ErrK8SClient + lastErr.Component = K8sDependency return lastErr case apierr.IsUnexpectedObjectError(cause): - lastErr.reason = ErrK8SUnexpectedObjectError + lastErr.Reason = K8SUnexpectedObjectCode case apierr2.IsAmbiguousError(cause): - lastErr.reason = ErrK8SAmbiguousError + lastErr.Reason = K8SAmbiguousCode case apierr2.IsNoMatchError(cause): - lastErr.reason = ErrK8SNoMatchError + lastErr.Reason = K8SNoMatchCode } - if lastErr.reason != "" { - lastErr.component = ErrK8SClient + if lastErr.Reason != "" { + lastErr.Component = K8sDependency } return lastErr @@ -199,23 +217,3 @@ func UnwrapAll(err error) error { } return err } - -func (l LastError) MarshalJSON() ([]byte, error) { - return json.Marshal( - LastErrorJSON{ - Message: l.message, - Reason: l.reason, - Component: l.component, - }) -} - -func (l *LastError) UnmarshalJSON(data []byte) error { - tmp := &LastErrorJSON{} - if err := json.Unmarshal(data, &tmp); err != nil { - return err - } - l.message = tmp.Message - l.reason = tmp.Reason - l.component = tmp.Component - return nil -} diff --git a/internal/error/errors_test.go b/internal/error/errors_test.go index 9fdbc9f2d9..f644f5913d 100644 --- a/internal/error/errors_test.go +++ b/internal/error/errors_test.go @@ -29,27 +29,27 @@ func TestLastError(t *testing.T) { expectTimeoutMsg := "something: operation has reached the time limit: 2h" // when - edpLastErr := kebError.ReasonForError(edpErr) - edpConfLastErr := kebError.ReasonForError(edpConfErr) - dbLastErr := kebError.ReasonForError(dbErr) - timeoutLastErr := kebError.ReasonForError(timeoutErr) + edpLastErr := kebError.ReasonForError(edpErr, "") + edpConfLastErr := kebError.ReasonForError(edpConfErr, "") + dbLastErr := kebError.ReasonForError(dbErr, "") + timeoutLastErr := kebError.ReasonForError(timeoutErr, "") // then - assert.Equal(t, edp.ErrEDPBadRequest, edpLastErr.Reason()) - assert.Equal(t, kebError.ErrEDP, edpLastErr.Component()) + assert.Equal(t, edp.ErrEDPBadRequest, edpLastErr.GetReason()) + assert.Equal(t, kebError.EDPDependency, edpLastErr.GetDependency()) assert.Equal(t, expectEdpMsg, edpLastErr.Error()) - assert.Equal(t, edp.ErrEDPConflict, edpConfLastErr.Reason()) - assert.Equal(t, kebError.ErrEDP, edpConfLastErr.Component()) + assert.Equal(t, edp.ErrEDPConflict, edpConfLastErr.GetReason()) + assert.Equal(t, kebError.EDPDependency, edpConfLastErr.GetDependency()) assert.Equal(t, expectEdpConfMsg, edpConfLastErr.Error()) assert.True(t, edp.IsConflictError(edpConfErr)) - assert.Equal(t, dberr.ErrDBNotFound, dbLastErr.Reason()) - assert.Equal(t, kebError.ErrDB, dbLastErr.Component()) + assert.Equal(t, dberr.ErrDBNotFound, dbLastErr.GetReason()) + assert.Equal(t, kebError.KebDbDependency, dbLastErr.GetDependency()) assert.Equal(t, expectDbErr, dbLastErr.Error()) - assert.Equal(t, kebError.ErrKEBTimeOut, timeoutLastErr.Reason()) - assert.Equal(t, kebError.ErrKEB, timeoutLastErr.Component()) + assert.Equal(t, kebError.KEBTimeOutCode, timeoutLastErr.GetReason()) + assert.Equal(t, kebError.KEBDependency, timeoutLastErr.GetDependency()) assert.Equal(t, expectTimeoutMsg, timeoutLastErr.Error()) }) } @@ -59,8 +59,8 @@ func TestTemporaryErrorToLastError(t *testing.T) { // given err := kebError.LastError{}. SetMessage(fmt.Sprintf("Got status %d", 502)). - SetReason(kebError.ErrHttpStatusCode). - SetComponent(kebError.ErrReconciler) + SetReason(kebError.HttpStatusCode). + SetComponent(kebError.ReconcileDependency) tempErr := fmt.Errorf("something else: %w", kebError.WrapNewTemporaryError(fmt.Errorf("something: %w", err))) expectMsg := fmt.Sprintf("something else: something: Got status %d", 502) @@ -68,17 +68,17 @@ func TestTemporaryErrorToLastError(t *testing.T) { expectEdpMsg := fmt.Sprintf("EDP server returns failed status %s", "501") // when - lastErr := kebError.ReasonForError(tempErr) - edpLastErr := kebError.ReasonForError(edpTempErr) + lastErr := kebError.ReasonForError(tempErr, "") + edpLastErr := kebError.ReasonForError(edpTempErr, "") // then - assert.Equal(t, kebError.ErrHttpStatusCode, lastErr.Reason()) - assert.Equal(t, kebError.ErrReconciler, lastErr.Component()) + assert.Equal(t, kebError.HttpStatusCode, lastErr.GetReason()) + assert.Equal(t, kebError.ReconcileDependency, lastErr.GetDependency()) assert.Equal(t, expectMsg, lastErr.Error()) assert.True(t, kebError.IsTemporaryError(tempErr)) - assert.Equal(t, edp.ErrEDPTimeout, edpLastErr.Reason()) - assert.Equal(t, kebError.ErrEDP, edpLastErr.Component()) + assert.Equal(t, edp.ErrEDPTimeout, edpLastErr.GetReason()) + assert.Equal(t, kebError.EDPDependency, edpLastErr.GetDependency()) assert.Equal(t, expectEdpMsg, edpLastErr.Error()) assert.True(t, kebError.IsTemporaryError(edpTempErr)) }) @@ -89,11 +89,11 @@ func TestTemporaryErrorToLastError(t *testing.T) { expectMsg := "something: temporary error..." // when - lastErr := kebError.ReasonForError(tempErr) + lastErr := kebError.ReasonForError(tempErr, "") // then - assert.Equal(t, kebError.ErrKEBInternal, lastErr.Reason()) - assert.Equal(t, kebError.ErrKEB, lastErr.Component()) + assert.Equal(t, kebError.KEBInternalCode, lastErr.GetReason()) + assert.Equal(t, kebError.KEBDependency, lastErr.GetDependency()) assert.Equal(t, expectMsg, lastErr.Error()) assert.True(t, kebError.IsTemporaryError(tempErr)) }) @@ -104,12 +104,12 @@ func TestNotFoundError(t *testing.T) { err := fmt.Errorf("something: %w", kebError.NotFoundError{}) // when - lastErr := kebError.ReasonForError(err) + lastErr := kebError.ReasonForError(err, "") // then assert.EqualError(t, lastErr, "something: not found") - assert.Equal(t, kebError.ErrClusterNotFound, lastErr.Reason()) - assert.Equal(t, kebError.ErrReconciler, lastErr.Component()) + assert.Equal(t, kebError.ClusterNotFoundCode, lastErr.GetReason()) + assert.Equal(t, kebError.ReconcileDependency, lastErr.GetDependency()) assert.True(t, kebError.IsNotFoundError(err)) } @@ -121,25 +121,25 @@ func TestK8SLastError(t *testing.T) { errNoMatch := fmt.Errorf("something: %w", &apierr2.NoKindMatchError{}) // when - lastErrBadReq := kebError.ReasonForError(errBadReq) - lastErrUnexpObj := kebError.ReasonForError(errUnexpObj) - lastErrAmbi := kebError.ReasonForError(errAmbi) - lastErrNoMatch := kebError.ReasonForError(errNoMatch) + lastErrBadReq := kebError.ReasonForError(errBadReq, "") + lastErrUnexpObj := kebError.ReasonForError(errUnexpObj, "") + lastErrAmbi := kebError.ReasonForError(errAmbi, "") + lastErrNoMatch := kebError.ReasonForError(errNoMatch, "") // then assert.EqualError(t, lastErrBadReq, "something: bad request here") - assert.Equal(t, kebError.ErrReason("BadRequest"), lastErrBadReq.Reason()) - assert.Equal(t, kebError.ErrK8SClient, lastErrBadReq.Component()) + assert.Equal(t, kebError.Reason("BadRequest"), lastErrBadReq.GetReason()) + assert.Equal(t, kebError.K8sDependency, lastErrBadReq.GetDependency()) assert.ErrorContains(t, lastErrUnexpObj, "something: unexpected object: ") - assert.Equal(t, kebError.ErrK8SUnexpectedObjectError, lastErrUnexpObj.Reason()) - assert.Equal(t, kebError.ErrK8SClient, lastErrUnexpObj.Component()) + assert.Equal(t, kebError.K8SUnexpectedObjectCode, lastErrUnexpObj.GetReason()) + assert.Equal(t, kebError.K8sDependency, lastErrUnexpObj.GetDependency()) assert.ErrorContains(t, lastErrAmbi, "matches multiple resources or kinds") - assert.Equal(t, kebError.ErrK8SAmbiguousError, lastErrAmbi.Reason()) - assert.Equal(t, kebError.ErrK8SClient, lastErrAmbi.Component()) + assert.Equal(t, kebError.K8SAmbiguousCode, lastErrAmbi.GetReason()) + assert.Equal(t, kebError.K8sDependency, lastErrAmbi.GetDependency()) assert.ErrorContains(t, lastErrNoMatch, "something: no matches for kind") - assert.Equal(t, kebError.ErrK8SNoMatchError, lastErrNoMatch.Reason()) - assert.Equal(t, kebError.ErrK8SClient, lastErrNoMatch.Component()) + assert.Equal(t, kebError.K8SNoMatchCode, lastErrNoMatch.GetReason()) + assert.Equal(t, kebError.K8sDependency, lastErrNoMatch.GetDependency()) } diff --git a/internal/error/not_found_error.go b/internal/error/not_found_error.go index 11f12ff10d..e3e2da6a53 100644 --- a/internal/error/not_found_error.go +++ b/internal/error/not_found_error.go @@ -11,12 +11,12 @@ func (NotFoundError) IsNotFound() bool { return true } -func (NotFoundError) Reason() ErrReason { - return ErrClusterNotFound +func (NotFoundError) GetReason() Reason { + return ClusterNotFoundCode } -func (NotFoundError) Component() ErrComponent { - return ErrReconciler +func (NotFoundError) GetDependency() Component { + return ReconcileDependency } func IsNotFoundError(err error) bool { diff --git a/internal/error/temporary_error.go b/internal/error/temporary_error.go index 56e4500d63..5ff3e34d30 100644 --- a/internal/error/temporary_error.go +++ b/internal/error/temporary_error.go @@ -19,10 +19,10 @@ func AsTemporaryError(err error, context string, args ...interface{}) *Temporary return &TemporaryError{message: msg} } -func (te TemporaryError) Error() string { return te.message } -func (TemporaryError) Temporary() bool { return true } -func (TemporaryError) Reason() ErrReason { return ErrKEBInternal } -func (TemporaryError) Component() ErrComponent { return ErrKEB } +func (te TemporaryError) Error() string { return te.message } +func (TemporaryError) Temporary() bool { return true } +func (TemporaryError) GetReason() Reason { return KEBInternalCode } +func (TemporaryError) GetDependency() Component { return KEBDependency } func IsTemporaryError(err error) bool { cause := UnwrapAll(err) @@ -51,10 +51,10 @@ func WrapNewTemporaryError(err error) *WrapTemporaryError { func (te WrapTemporaryError) Error() string { return te.err.Error() } func (WrapTemporaryError) Temporary() bool { return true } -func (wte WrapTemporaryError) Reason() ErrReason { - return ReasonForError(wte.err).Reason() +func (wte WrapTemporaryError) GetReason() Reason { + return ReasonForError(wte.err, NotSet).GetReason() } -func (wte WrapTemporaryError) Component() ErrComponent { - return ReasonForError(wte.err).Component() +func (wte WrapTemporaryError) GetDependency() Component { + return ReasonForError(wte.err, NotSet).GetDependency() } diff --git a/internal/metricsv2/metrics.go b/internal/metricsv2/metrics.go index 4dad9d0ddd..3205805b4b 100644 --- a/internal/metricsv2/metrics.go +++ b/internal/metricsv2/metrics.go @@ -135,8 +135,8 @@ func GetLabels(op internal.Operation) map[string]string { labels["plan_id"] = op.ProvisioningParameters.PlanID labels["type"] = string(op.Type) labels["state"] = string(op.State) - labels["error_category"] = string(op.LastError.Component()) - labels["error_reason"] = string(op.LastError.Reason()) + labels["error_category"] = string(op.LastError.GetDependency()) + labels["error_reason"] = string(op.LastError.GetReason()) labels["error"] = op.LastError.Error() return labels } diff --git a/internal/process/operation_manager.go b/internal/process/operation_manager.go index ab2d43f0e2..4272912241 100644 --- a/internal/process/operation_manager.go +++ b/internal/process/operation_manager.go @@ -6,6 +6,7 @@ import ( "github.com/kyma-project/kyma-environment-broker/common/orchestration" "github.com/kyma-project/kyma-environment-broker/internal" + kebErr "github.com/kyma-project/kyma-environment-broker/internal/error" "github.com/kyma-project/kyma-environment-broker/internal/storage" "github.com/kyma-project/kyma-environment-broker/internal/storage/dberr" "github.com/pivotal-cf/brokerapi/v8/domain" @@ -13,13 +14,19 @@ import ( ) type OperationManager struct { - storage storage.Operations + storage storage.Operations + component kebErr.Component + step string } func NewOperationManager(storage storage.Operations) *OperationManager { return &OperationManager{storage: storage} } +func NewOperationManagerWithMetadata(storage storage.Operations, step string, component kebErr.Component) *OperationManager { + return &OperationManager{storage: storage, component: component, step: step} +} + // OperationSucceeded marks the operation as succeeded and returns status of the operation's update func (om *OperationManager) OperationSucceeded(operation internal.Operation, description string, log logrus.FieldLogger) (internal.Operation, time.Duration, error) { return om.update(operation, domain.Succeeded, description, log) @@ -27,6 +34,15 @@ func (om *OperationManager) OperationSucceeded(operation internal.Operation, des // OperationFailed marks the operation as failed and returns status of the operation's update func (om *OperationManager) OperationFailed(operation internal.Operation, description string, err error, log logrus.FieldLogger) (internal.Operation, time.Duration, error) { + if err != nil { + operation.LastError = kebErr.LastError{ + Message: err.Error(), + Reason: kebErr.Reason(description), + Component: om.component, + Step: om.step, + } + } + op, t, _ := om.update(operation, domain.Failed, description, log) // repeat in case of storage error if t != 0 { diff --git a/internal/process/operation_manager_test.go b/internal/process/operation_manager_test.go index 4c3199adeb..cde17375d3 100644 --- a/internal/process/operation_manager_test.go +++ b/internal/process/operation_manager_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/kyma-project/kyma-environment-broker/internal" + kebErr "github.com/kyma-project/kyma-environment-broker/internal/error" "github.com/kyma-project/kyma-environment-broker/internal/storage" ) @@ -79,6 +80,78 @@ func Test_OperationManager_RetryOperation(t *testing.T) { assert.Nil(t, err) } +func Test_OperationManager_LastError(t *testing.T) { + t.Run("when all last error field set with 1 component v1", func(t *testing.T) { + memory := storage.NewMemoryStorage() + operations := memory.Operations() + opManager := NewOperationManagerWithMetadata(operations, "some_step", kebErr.ProvisionerDependency) + op := internal.Operation{} + err := operations.InsertOperation(op) + require.NoError(t, err) + op, _, err = opManager.OperationFailed(op, "friendly message", fmt.Errorf("technical err"), fixLogger()) + assert.EqualValues(t, "provisioner", op.LastError.GetDependency()) + assert.EqualValues(t, "technical err", op.LastError.Error()) + assert.EqualValues(t, "friendly message", op.LastError.GetReason()) + assert.EqualValues(t, "some_step", op.LastError.StepName()) + }) + + t.Run("when all last error field set with 1 components v2", func(t *testing.T) { + memory := storage.NewMemoryStorage() + operations := memory.Operations() + opManager := NewOperationManagerWithMetadata(operations, "some_step", kebErr.KebDbDependency) + op := internal.Operation{} + err := operations.InsertOperation(op) + require.NoError(t, err) + op, _, err = opManager.OperationFailed(op, "friendly message", fmt.Errorf("technical err"), fixLogger()) + assert.EqualValues(t, "db - keb", op.LastError.GetDependency()) + assert.EqualValues(t, "technical err", op.LastError.Error()) + assert.EqualValues(t, "friendly message", op.LastError.GetReason()) + assert.EqualValues(t, "some_step", op.LastError.StepName()) + }) + + t.Run("when no error passed", func(t *testing.T) { + memory := storage.NewMemoryStorage() + operations := memory.Operations() + opManager := NewOperationManagerWithMetadata(operations, "some_step", kebErr.ProvisionerDependency) + op := internal.Operation{} + err := operations.InsertOperation(op) + require.NoError(t, err) + op, _, err = opManager.OperationFailed(op, "friendly message", nil, fixLogger()) + assert.EqualValues(t, "", op.LastError.GetDependency()) + assert.EqualValues(t, "", op.LastError.Error()) + assert.EqualValues(t, "", op.LastError.GetReason()) + assert.EqualValues(t, "", op.LastError.StepName()) + }) + + t.Run("when no description passed", func(t *testing.T) { + memory := storage.NewMemoryStorage() + operations := memory.Operations() + opManager := NewOperationManagerWithMetadata(operations, "some_step", kebErr.ProvisionerDependency) + op := internal.Operation{} + err := operations.InsertOperation(op) + require.NoError(t, err) + op, _, err = opManager.OperationFailed(op, "", fmt.Errorf("technical err"), fixLogger()) + assert.EqualValues(t, "provisioner", op.LastError.GetDependency()) + assert.EqualValues(t, "technical err", op.LastError.Error()) + assert.EqualValues(t, "", op.LastError.GetReason()) + assert.EqualValues(t, "some_step", op.LastError.StepName()) + }) + + t.Run("when no description and no err passed", func(t *testing.T) { + memory := storage.NewMemoryStorage() + operations := memory.Operations() + opManager := NewOperationManagerWithMetadata(operations, "some_step", kebErr.ReconcileDependency) + op := internal.Operation{} + err := operations.InsertOperation(op) + require.NoError(t, err) + op, _, err = opManager.OperationFailed(op, "", nil, fixLogger()) + assert.EqualValues(t, "", op.LastError.GetDependency()) + assert.EqualValues(t, "", op.LastError.Error()) + assert.EqualValues(t, "", op.LastError.GetReason()) + assert.EqualValues(t, "", op.LastError.StepName()) + }) +} + func fixLogger() logrus.FieldLogger { return logrus.StandardLogger() } diff --git a/internal/process/provisioning/apply_kyma_step.go b/internal/process/provisioning/apply_kyma_step.go index a8bf9a4e2a..eb81b89786 100644 --- a/internal/process/provisioning/apply_kyma_step.go +++ b/internal/process/provisioning/apply_kyma_step.go @@ -6,6 +6,8 @@ import ( "reflect" "time" + kebErr "github.com/kyma-project/kyma-environment-broker/internal/error" + "github.com/kyma-project/kyma-environment-broker/internal/process/steps" "github.com/kyma-project/kyma-environment-broker/internal" @@ -28,7 +30,9 @@ type ApplyKymaStep struct { var _ process.Step = &ApplyKymaStep{} func NewApplyKymaStep(os storage.Operations, cli client.Client) *ApplyKymaStep { - return &ApplyKymaStep{operationManager: process.NewOperationManager(os), k8sClient: cli} + step := &ApplyKymaStep{k8sClient: cli} + step.operationManager = process.NewOperationManagerWithMetadata(os, step.Name(), kebErr.LifeCycleManagerDependency) + return step } func (a *ApplyKymaStep) Name() string { diff --git a/internal/process/provisioning/check_runtime_step.go b/internal/process/provisioning/check_runtime_step.go index 1d7f3d5fa8..7c9ce52aeb 100644 --- a/internal/process/provisioning/check_runtime_step.go +++ b/internal/process/provisioning/check_runtime_step.go @@ -4,6 +4,8 @@ import ( "fmt" "time" + kebError "github.com/kyma-project/kyma-environment-broker/internal/error" + "github.com/kyma-project/kyma-environment-broker/internal/broker" "github.com/sirupsen/logrus" @@ -27,12 +29,13 @@ func NewCheckRuntimeStep(os storage.Operations, provisionerClient provisioner.Client, provisioningTimeout time.Duration, kimConfig broker.KimConfig) *CheckRuntimeStep { - return &CheckRuntimeStep{ + step := &CheckRuntimeStep{ provisionerClient: provisionerClient, - operationManager: process.NewOperationManager(os), provisioningTimeout: provisioningTimeout, kimConfig: kimConfig, } + step.operationManager = process.NewOperationManagerWithMetadata(os, step.Name(), kebError.ProvisionerDependency) + return step } var _ process.Step = (*CheckRuntimeStep)(nil) diff --git a/internal/process/provisioning/create_runtime_without_kyma_step.go b/internal/process/provisioning/create_runtime_without_kyma_step.go index c1a7166734..17163f1ffe 100644 --- a/internal/process/provisioning/create_runtime_without_kyma_step.go +++ b/internal/process/provisioning/create_runtime_without_kyma_step.go @@ -32,13 +32,14 @@ type CreateRuntimeWithoutKymaStep struct { } func NewCreateRuntimeWithoutKymaStep(os storage.Operations, runtimeStorage storage.RuntimeStates, is storage.Instances, cli provisioner.Client, kimConfig broker.KimConfig) *CreateRuntimeWithoutKymaStep { - return &CreateRuntimeWithoutKymaStep{ - operationManager: process.NewOperationManager(os), + step := &CreateRuntimeWithoutKymaStep{ instanceStorage: is, provisionerClient: cli, runtimeStateStorage: runtimeStorage, kimConfig: kimConfig, } + step.operationManager = process.NewOperationManagerWithMetadata(os, step.Name(), kebError.ProvisionerDependency) + return step } func (s *CreateRuntimeWithoutKymaStep) Name() string { diff --git a/internal/process/provisioning/edp_registration.go b/internal/process/provisioning/edp_registration.go index 7a5b2dda52..90e7588b73 100644 --- a/internal/process/provisioning/edp_registration.go +++ b/internal/process/provisioning/edp_registration.go @@ -32,11 +32,12 @@ type EDPRegistrationStep struct { } func NewEDPRegistrationStep(os storage.Operations, client EDPClient, config edp.Config) *EDPRegistrationStep { - return &EDPRegistrationStep{ - operationManager: process.NewOperationManager(os), - client: client, - config: config, + step := &EDPRegistrationStep{ + client: client, + config: config, } + step.operationManager = process.NewOperationManagerWithMetadata(os, step.Name(), kebError.EDPDependency) + return step } func (s *EDPRegistrationStep) Name() string { diff --git a/internal/process/provisioning/start_step.go b/internal/process/provisioning/start_step.go index 89fd7e11e2..a2c0341617 100644 --- a/internal/process/provisioning/start_step.go +++ b/internal/process/provisioning/start_step.go @@ -22,9 +22,9 @@ type StartStep struct { func NewStartStep(os storage.Operations, is storage.Instances) *StartStep { return &StartStep{ + operationManager: process.NewOperationManager(os), operationStorage: os, instanceStorage: is, - operationManager: process.NewOperationManager(os), } } diff --git a/internal/process/staged_manager.go b/internal/process/staged_manager.go index 51b4d19774..60ea178961 100644 --- a/internal/process/staged_manager.go +++ b/internal/process/staged_manager.go @@ -122,7 +122,7 @@ func (m *StagedManager) Execute(operationID string) (time.Duration, error) { logOperation := m.log.WithFields(logrus.Fields{"operation": operationID, "instanceID": operation.InstanceID, "planID": operation.ProvisioningParameters.PlanID}) logOperation.Infof("Start process operation steps for GlobalAccount=%s, ", operation.ProvisioningParameters.ErsContext.GlobalAccountID) if time.Since(operation.CreatedAt) > m.operationTimeout { - timeoutErr := kebError.TimeoutError("operation has reached the time limit") + timeoutErr := kebError.TimeoutError("operation has reached the time limit", kebError.NotSet) operation.LastError = timeoutErr defer m.publishEventOnFail(operation, err) logOperation.Infof("operation has reached the time limit: operation was created at: %s", operation.CreatedAt) @@ -232,8 +232,8 @@ func (m *StagedManager) runStep(step Step, operation internal.Operation, logger stepLogger := logger.WithFields(logrus.Fields{"step": step.Name(), "operation": processedOperation.ID}) processedOperation, backoff, err = step.Run(processedOperation, stepLogger) if err != nil { - processedOperation.LastError = kebError.ReasonForError(err) - logOperation := stepLogger.WithFields(logrus.Fields{"error_component": processedOperation.LastError.Component(), "error_reason": processedOperation.LastError.Reason()}) + processedOperation.LastError = kebError.ReasonForError(err, step.Name()) + logOperation := stepLogger.WithFields(logrus.Fields{"error_component": processedOperation.LastError.GetDependency(), "error_reason": processedOperation.LastError.GetReason()}) logOperation.Warnf("Last error from step: %s", processedOperation.LastError.Error()) // only save to storage, skip for alerting if error _, err = m.operationStorage.UpdateOperation(processedOperation) @@ -259,7 +259,7 @@ func (m *StagedManager) runStep(step Step, operation internal.Operation, logger // - the loop takes too much time (to not block the worker too long) if backoff == 0 || err != nil || time.Since(begin) > m.cfg.MaxStepProcessingTime { if err != nil { - logOperation := m.log.WithFields(logrus.Fields{"step": step.Name(), "operation": processedOperation.ID, "error_component": processedOperation.LastError.Component(), "error_reason": processedOperation.LastError.Reason()}) + logOperation := m.log.WithFields(logrus.Fields{"step": step.Name(), "operation": processedOperation.ID, "error_component": processedOperation.LastError.GetDependency(), "error_reason": processedOperation.LastError.GetReason()}) logOperation.Errorf("Last Error that terminated the step: %s", processedOperation.LastError.Error()) } return processedOperation, backoff, err @@ -270,7 +270,7 @@ func (m *StagedManager) runStep(step Step, operation internal.Operation, logger } func (m *StagedManager) publishEventOnFail(operation *internal.Operation, err error) { - logOperation := m.log.WithFields(logrus.Fields{"operation": operation.ID, "error_component": operation.LastError.Component(), "error_reason": operation.LastError.Reason()}) + logOperation := m.log.WithFields(logrus.Fields{"operation": operation.ID, "error_component": operation.LastError.GetDependency(), "error_reason": operation.LastError.GetReason()}) logOperation.Errorf("Last error: %s", operation.LastError.Error()) m.publishOperationFinishedEvent(*operation) diff --git a/internal/provisioner/client.go b/internal/provisioner/client.go index b7419bd6b6..eb13b01a2a 100644 --- a/internal/provisioner/client.go +++ b/internal/provisioner/client.go @@ -212,8 +212,8 @@ func OperationStatusLastError(lastErr *schema.LastError) kebError.ErrorReporter var err kebError.LastError if lastErr == nil { - return err.SetReason(kebError.ErrProvisionerNilLastError).SetComponent(kebError.ErrProvisioner) + return err.SetReason(kebError.ProvisionerCode).SetComponent(kebError.ProvisionerDependency).SetStepName(kebError.NotSet) } - return err.SetMessage(lastErr.ErrMessage).SetReason(kebError.ErrReason(lastErr.Reason)).SetComponent(kebError.ErrComponent(lastErr.Component)) + return err.SetMessage(lastErr.ErrMessage).SetReason(kebError.Reason(lastErr.Reason)).SetComponent(kebError.Component(lastErr.Component)).SetStepName(kebError.NotSet) } diff --git a/internal/provisioner/client_test.go b/internal/provisioner/client_test.go index fc6a395819..0c3ed48063 100644 --- a/internal/provisioner/client_test.go +++ b/internal/provisioner/client_test.go @@ -294,13 +294,13 @@ func TestClient_ReconnectRuntimeAgent(t *testing.T) { // when _, err := client.ProvisionRuntime(testAccountID, testSubAccountID, fixProvisionRuntimeInput()) - lastErr := kebError.ReasonForError(err) + lastErr := kebError.ReasonForError(err, "") // Then assert.Error(t, err) assert.False(t, kebError.IsTemporaryError(err)) - assert.Equal(t, kebError.ErrReason("Object not found"), lastErr.Reason()) - assert.Equal(t, kebError.ErrComponent("compass director"), lastErr.Component()) + assert.Equal(t, kebError.Reason("Object not found"), lastErr.GetReason()) + assert.Equal(t, kebError.Component("compass director"), lastErr.GetDependency()) }) t.Run("provisioner returns temporary code error", func(t *testing.T) { @@ -328,13 +328,13 @@ func TestClient_ReconnectRuntimeAgent(t *testing.T) { // when _, err := client.ProvisionRuntime(testAccountID, testSubAccountID, fixProvisionRuntimeInput()) - lastErr := kebError.ReasonForError(err) + lastErr := kebError.ReasonForError(err, "") // Then assert.Error(t, err) assert.True(t, kebError.IsTemporaryError(err)) - assert.Equal(t, kebError.ErrReason("whatever"), lastErr.Reason()) - assert.Equal(t, kebError.ErrComponent("db - provisioner"), lastErr.Component()) + assert.Equal(t, kebError.Reason("whatever"), lastErr.GetReason()) + assert.Equal(t, kebError.Component("db - provisioner"), lastErr.GetDependency()) }) t.Run("network error", func(t *testing.T) { @@ -405,8 +405,8 @@ func TestClient_OperationStatusLastError(t *testing.T) { lastErr := OperationStatusLastError(response.LastError) // Then - assert.Equal(t, kebError.ErrProvisioner, lastErr.Component()) - assert.Equal(t, kebError.ErrProvisionerNilLastError, lastErr.Reason()) + assert.Equal(t, kebError.ProvisionerDependency, lastErr.GetDependency()) + assert.Equal(t, kebError.ProvisionerCode, lastErr.GetReason()) assert.Equal(t, "", lastErr.Error()) }) @@ -427,16 +427,16 @@ func TestClient_OperationStatusLastError(t *testing.T) { lastErr := OperationStatusLastError(response.LastError) // Then - assert.Equal(t, kebError.ErrComponent("provisioner-db"), lastErr.Component()) - assert.Equal(t, kebError.ErrReason("not found"), lastErr.Reason()) + assert.Equal(t, kebError.Component("provisioner-db"), lastErr.GetDependency()) + assert.Equal(t, kebError.Reason("not found"), lastErr.GetReason()) assert.Equal(t, "error msg", lastErr.Error()) err := fmt.Errorf("something: %w", lastErr) - lastErr = kebError.ReasonForError(err) + lastErr = kebError.ReasonForError(err, "") // Then - assert.Equal(t, kebError.ErrComponent("provisioner-db"), lastErr.Component()) - assert.Equal(t, kebError.ErrReason("not found"), lastErr.Reason()) + assert.Equal(t, kebError.Component("provisioner-db"), lastErr.GetDependency()) + assert.Equal(t, kebError.Reason("not found"), lastErr.GetReason()) assert.Equal(t, "something: error msg", lastErr.Error()) }) } diff --git a/internal/runtime/converter.go b/internal/runtime/converter.go index 16f4a512e3..c1d177e96b 100644 --- a/internal/runtime/converter.go +++ b/internal/runtime/converter.go @@ -1,9 +1,12 @@ package runtime import ( + "reflect" + "github.com/kyma-project/kyma-environment-broker/common/orchestration" pkg "github.com/kyma-project/kyma-environment-broker/common/runtime" "github.com/kyma-project/kyma-environment-broker/internal" + kebError "github.com/kyma-project/kyma-environment-broker/internal/error" "github.com/pivotal-cf/brokerapi/v8/domain" ) @@ -65,6 +68,9 @@ func (c *converter) applyOperation(source *internal.Operation, target *pkg.Opera target.FinishedStages = source.FinishedStages target.ExecutedButNotCompletedSteps = source.ExcutedButNotCompleted target.Parameters = source.ProvisioningParameters.Parameters + if !reflect.DeepEqual(source.LastError, kebError.LastError{}) { + target.Error = &source.LastError + } } } diff --git a/internal/storage/dberr/errors.go b/internal/storage/dberr/errors.go index bd25f263c0..2c13748235 100644 --- a/internal/storage/dberr/errors.go +++ b/internal/storage/dberr/errors.go @@ -13,7 +13,7 @@ const ( CodeConflict = 4 ) -type DBErrReason = kebError.ErrReason +type DBErrReason = kebError.Reason const ( ErrDBInternal DBErrReason = "err_db_internal" @@ -81,11 +81,11 @@ func (e dbError) Error() string { return e.message } -func (e dbError) Component() kebError.ErrComponent { - return kebError.ErrDB +func (e dbError) GetDependency() kebError.Component { + return kebError.KebDbDependency } -func (e dbError) Reason() DBErrReason { +func (e dbError) GetReason() DBErrReason { reason := ErrDBUnknown switch e.code {