-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: mysql error alerts when the auto ops rule is already triggered #196
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,6 @@ package api | |
|
||
import ( | ||
"context" | ||
"errors" | ||
"net/url" | ||
"strconv" | ||
"time" | ||
|
@@ -48,8 +47,6 @@ import ( | |
experimentproto "github.com/bucketeer-io/bucketeer/proto/experiment" | ||
) | ||
|
||
var errAlreadyTriggered = errors.New("auto ops Rule has already triggered") | ||
|
||
type options struct { | ||
logger *zap.Logger | ||
} | ||
|
@@ -836,6 +833,13 @@ func (s *AutoOpsService) ExecuteAutoOps( | |
if err := s.validateExecuteAutoOpsRequest(req, localizer); err != nil { | ||
return nil, err | ||
} | ||
triggered, err := s.checkIfHasAlreadyTriggered(ctx, localizer, req.Id, req.EnvironmentNamespace) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if triggered { | ||
return &autoopsproto.ExecuteAutoOpsResponse{AlreadyTriggered: true}, nil | ||
} | ||
tx, err := s.mysqlClient.BeginTx(ctx) | ||
if err != nil { | ||
s.logger.Error( | ||
|
@@ -859,23 +863,36 @@ func (s *AutoOpsService) ExecuteAutoOps( | |
if err != nil { | ||
return err | ||
} | ||
if autoOpsRule.AlreadyTriggered() { | ||
return errAlreadyTriggered | ||
} | ||
handler := command.NewAutoOpsCommandHandler(editor, autoOpsRule, s.publisher, req.EnvironmentNamespace) | ||
if err := handler.Handle(ctx, req.ChangeAutoOpsRuleTriggeredAtCommand); err != nil { | ||
return err | ||
} | ||
if err = autoOpsRuleStorage.UpdateAutoOpsRule(ctx, autoOpsRule, req.EnvironmentNamespace); err != nil { | ||
if err == v2as.ErrAutoOpsRuleUnexpectedAffectedRows { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there are no affected rows, it will not treat as an error. Otherwise, it might trigger the alert. |
||
s.logger.Warn( | ||
"No rows were affected", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("id", req.Id), | ||
zap.String("environmentNamespace", req.EnvironmentNamespace), | ||
)..., | ||
) | ||
return nil | ||
} | ||
return err | ||
} | ||
return ExecuteOperation(ctx, req.EnvironmentNamespace, autoOpsRule, s.featureClient, s.logger) | ||
}) | ||
if err != nil { | ||
if err == errAlreadyTriggered { | ||
return &autoopsproto.ExecuteAutoOpsResponse{AlreadyTriggered: true}, nil | ||
} | ||
if err == v2as.ErrAutoOpsRuleNotFound || err == v2as.ErrAutoOpsRuleUnexpectedAffectedRows { | ||
if err == v2as.ErrAutoOpsRuleNotFound { | ||
s.logger.Warn( | ||
"Auto Ops Rule not found", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("id", req.Id), | ||
zap.String("environmentNamespace", req.EnvironmentNamespace), | ||
)..., | ||
) | ||
dt, err := statusNotFound.WithDetails(&errdetails.LocalizedMessage{ | ||
Locale: localizer.GetLocale(), | ||
Message: localizer.MustLocalize(locale.NotFoundError), | ||
|
@@ -931,6 +948,63 @@ func (s *AutoOpsService) validateExecuteAutoOpsRequest( | |
return nil | ||
} | ||
|
||
func (s *AutoOpsService) checkIfHasAlreadyTriggered( | ||
ctx context.Context, | ||
localizer locale.Localizer, | ||
ruleID, | ||
environmentNamespace string, | ||
) (bool, error) { | ||
storage := v2as.NewAutoOpsRuleStorage(s.mysqlClient) | ||
autoOpsRule, err := storage.GetAutoOpsRule(ctx, ruleID, environmentNamespace) | ||
if err != nil { | ||
if err == v2as.ErrAutoOpsRuleNotFound { | ||
s.logger.Warn( | ||
"Auto Ops Rule not found", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("ruleID", ruleID), | ||
zap.String("environmentNamespace", environmentNamespace), | ||
)..., | ||
) | ||
dt, err := statusNotFound.WithDetails(&errdetails.LocalizedMessage{ | ||
Locale: localizer.GetLocale(), | ||
Message: localizer.MustLocalize(locale.NotFoundError), | ||
}) | ||
if err != nil { | ||
return false, statusInternal.Err() | ||
} | ||
return false, dt.Err() | ||
} | ||
s.logger.Error( | ||
"Failed to get auto ops rule", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("environmentNamespace", environmentNamespace), | ||
)..., | ||
) | ||
dt, err := statusInternal.WithDetails(&errdetails.LocalizedMessage{ | ||
Locale: localizer.GetLocale(), | ||
Message: localizer.MustLocalize(locale.InternalServerError), | ||
}) | ||
if err != nil { | ||
return false, statusInternal.Err() | ||
} | ||
return false, dt.Err() | ||
} | ||
if autoOpsRule.AlreadyTriggered() { | ||
s.logger.Warn( | ||
"Auto Ops Rule already triggered", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("ruleID", ruleID), | ||
zap.String("environmentNamespace", environmentNamespace), | ||
)..., | ||
) | ||
return true, nil | ||
} | ||
return false, nil | ||
} | ||
|
||
func (s *AutoOpsService) ListOpsCounts( | ||
ctx context.Context, | ||
req *autoopsproto.ListOpsCountsRequest, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,17 +60,29 @@ func enableFeature( | |
EnvironmentNamespace: environmentNamespace, | ||
} | ||
_, err := featureClient.EnableFeature(ctx, req) | ||
if code := status.Code(err); code == codes.FailedPrecondition { | ||
logger.Warn( | ||
"Failed to enable feature", | ||
if err != nil { | ||
if code := status.Code(err); code == codes.FailedPrecondition { | ||
logger.Warn( | ||
"Feature flag is already enabled", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("featureId", req.Id), | ||
zap.String("environmentNamespace", req.EnvironmentNamespace), | ||
)..., | ||
) | ||
return nil | ||
} | ||
logger.Error( | ||
"Failed to enable feature flag", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("featureId", req.Id), | ||
zap.String("environmentNamespace", req.EnvironmentNamespace), | ||
)..., | ||
) | ||
return nil | ||
return err | ||
} | ||
return err | ||
return nil | ||
} | ||
|
||
func disableFeature( | ||
|
@@ -86,15 +98,27 @@ func disableFeature( | |
EnvironmentNamespace: environmentNamespace, | ||
} | ||
_, err := featureClient.DisableFeature(ctx, req) | ||
if code := status.Code(err); code == codes.FailedPrecondition { | ||
logger.Warn( | ||
"Failed to disable feature", | ||
if err != nil { | ||
if code := status.Code(err); code == codes.FailedPrecondition { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I split the implementation to print warning and error levels to make debugging easier. |
||
logger.Warn( | ||
"Feature flag is already disabled", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("featureId", req.Id), | ||
zap.String("environmentNamespace", req.EnvironmentNamespace), | ||
)..., | ||
) | ||
return nil | ||
} | ||
logger.Error( | ||
"Failed to disable feature flag", | ||
log.FieldsFromImcomingContext(ctx).AddFields( | ||
zap.Error(err), | ||
zap.String("featureId", req.Id), | ||
zap.String("environmentNamespace", req.EnvironmentNamespace), | ||
)..., | ||
) | ||
return nil | ||
return err | ||
} | ||
return err | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to move this implementation from the transaction to check if it had already been triggered because the response returns the
AlreadyTriggered
boolean.https://github.com/bucketeer-io/bucketeer/pull/196/files#diff-13112ed94680ecaec33373531ff5c65e48cecae142657203e04d967a185684d5L862-L864