Skip to content
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

Add metric knob for maxInconclusive #181

Merged
merged 2 commits into from
Oct 7, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 9 additions & 14 deletions analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,10 @@ func assessMetricStatus(metric v1alpha1.Metric, result v1alpha1.MetricResult, te
log.Infof("metric assessed %s: failed (%d) > maxFailures (%d)", v1alpha1.AnalysisStatusFailed, result.Failed, metric.MaxFailures)
return v1alpha1.AnalysisStatusFailed
}
if result.Inconclusive > metric.MaxInconclusive {
log.Infof("metric assessed %s: inconclusive (%d) > maxInconclusive (%d)", v1alpha1.AnalysisStatusInconclusive, result.Inconclusive, metric.MaxInconclusive)
return v1alpha1.AnalysisStatusInconclusive
}
consecutiveErrors := analysisutil.ConsecutiveErrors(result)
maxConsecutiveErrors := DefaultMaxConsecutiveErrors
if metric.MaxConsecutiveErrors != nil {
Expand All @@ -295,22 +299,13 @@ func assessMetricStatus(metric v1alpha1.Metric, result v1alpha1.MetricResult, te
log.Infof("metric assessed %s: consecutiveErrors (%d) > maxConsecutiveErrors (%d)", v1alpha1.AnalysisStatusError, consecutiveErrors, maxConsecutiveErrors)
return v1alpha1.AnalysisStatusError
}
// If a count was specified, and we reached that count, then we assess the status based on
// the greater of the Successful & Inconclusive status counters.
// Error and Failed counters are ignored because those checks have already been taken into
// consideration above, and we do not want to fail the metric if failures < maxFailures.
// TODO(jessesuen): may need to tweak this logic
// If a count was specified, and we reached that count, then metric is considered Successful.
// The Error, Failed, Inconclusive counters are ignored because those checks have already been
// taken into consideration above, and we do not want to fail if failures < maxFailures.
effectiveCount := metric.EffectiveCount()
if effectiveCount != nil && result.Count >= *effectiveCount {
var status v1alpha1.AnalysisStatus
if result.Successful > result.Inconclusive {
status = v1alpha1.AnalysisStatusSuccessful
} else {
status = v1alpha1.AnalysisStatusInconclusive
}
log.Infof("metric assessed %s: count %d reached, successful: %d, inconclusive: %d, errors: %d, failures: %d",
status, result.Count, result.Successful, result.Inconclusive, result.Error, result.Failed)
return status
log.Infof("metric assessed %s: count (%d) reached", v1alpha1.AnalysisStatusSuccessful, *effectiveCount)
return v1alpha1.AnalysisStatusSuccessful
}
// if we get here, this metric runs indefinitely
if terminating {
Expand Down
31 changes: 31 additions & 0 deletions analysis/analysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,11 @@ func TestAssessMetricStatusMaxFailures(t *testing.T) { // max failures
metric := v1alpha1.Metric{
Name: "success-rate",
MaxFailures: 2,
Interval: pointer.Int32Ptr(60),
}
result := v1alpha1.MetricResult{
Failed: 3,
Count: 3,
Measurements: []v1alpha1.Measurement{
{
Value: "99",
Expand All @@ -326,7 +328,36 @@ func TestAssessMetricStatusMaxFailures(t *testing.T) { // max failures
}
assert.Equal(t, v1alpha1.AnalysisStatusFailed, assessMetricStatus(metric, result, false))
assert.Equal(t, v1alpha1.AnalysisStatusFailed, assessMetricStatus(metric, result, true))
metric.MaxFailures = 3
assert.Equal(t, v1alpha1.AnalysisStatusRunning, assessMetricStatus(metric, result, false))
assert.Equal(t, v1alpha1.AnalysisStatusSuccessful, assessMetricStatus(metric, result, true))
}

func TestAssessMetricStatusMaxInconclusive(t *testing.T) { // max failures
metric := v1alpha1.Metric{
Name: "success-rate",
MaxInconclusive: 2,
Interval: pointer.Int32Ptr(60),
}
result := v1alpha1.MetricResult{
Inconclusive: 3,
Count: 3,
Measurements: []v1alpha1.Measurement{
{
Value: "99",
Status: v1alpha1.AnalysisStatusInconclusive,
StartedAt: timePtr(metav1.NewTime(time.Now().Add(-60 * time.Second))),
FinishedAt: timePtr(metav1.NewTime(time.Now().Add(-60 * time.Second))),
},
},
}
assert.Equal(t, v1alpha1.AnalysisStatusInconclusive, assessMetricStatus(metric, result, false))
assert.Equal(t, v1alpha1.AnalysisStatusInconclusive, assessMetricStatus(metric, result, true))
metric.MaxInconclusive = 3
assert.Equal(t, v1alpha1.AnalysisStatusRunning, assessMetricStatus(metric, result, false))
assert.Equal(t, v1alpha1.AnalysisStatusSuccessful, assessMetricStatus(metric, result, true))
}

func TestAssessMetricStatusConsecutiveErrors(t *testing.T) {
metric := v1alpha1.Metric{
Name: "success-rate",
Expand Down
3 changes: 3 additions & 0 deletions manifests/crds/analysis-run-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ spec:
maxFailures:
format: int32
type: integer
maxInconclusive:
format: int32
type: integer
name:
type: string
provider:
Expand Down
3 changes: 3 additions & 0 deletions manifests/crds/analysis-template-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ spec:
maxFailures:
format: int32
type: integer
maxInconclusive:
format: int32
type: integer
name:
type: string
provider:
Expand Down
9 changes: 6 additions & 3 deletions pkg/apis/rollouts/v1alpha1/analysis_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ type Metric struct {
// either condition, the measurement is considered Inconclusive
FailureCondition string `json:"failureCondition,omitempty"`
// MaxFailures is the maximum number of times the measurement is allowed to fail, before the
// entire metric is considered failed (default: 0)
// entire metric is considered Failed (default: 0)
MaxFailures int32 `json:"maxFailures,omitempty"`
// MaxInconclusive is the maximum number of times the measurement is allowed to measure
// Inconclusive, before the entire metric is considered Inconclusive (default: 0)
MaxInconclusive int32 `json:"maxInconclusive,omitempty"`
// MaxConsecutiveErrors is the maximum number of times the measurement is allowed to error in
// succession, before the metric is considered error (default: 4)
MaxConsecutiveErrors *int32 `json:"maxConsecutiveErrors,omitempty"`
Expand Down Expand Up @@ -85,10 +88,10 @@ type AnalysisProvider struct {
Prometheus *PrometheusMetric `json:"prometheus,omitempty"`
}

// AnalysisStatus is the overall status of the AnalysisRun, MetricResults, or Measurement
// AnalysisStatus is the overall status of an AnalysisRun, MetricResult, or Measurement
type AnalysisStatus string

// AnalysisStatus is the overall status of the AnalysisRun, MetricResults
// Possible AnalysisStatus values
const (
AnalysisStatusPending AnalysisStatus = "Pending"
AnalysisStatusRunning AnalysisStatus = "Running"
Expand Down
11 changes: 9 additions & 2 deletions pkg/apis/rollouts/v1alpha1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 10 additions & 8 deletions utils/analysis/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ func ValidateMetric(metric v1alpha1.Metric) error {
if metric.Count < metric.MaxFailures {
return fmt.Errorf("count must be >= maxFailures")
}
if metric.Count < metric.MaxInconclusive {
return fmt.Errorf("count must be >= maxInconclusive")
jessesuen marked this conversation as resolved.
Show resolved Hide resolved
}
if metric.Count > 1 && metric.Interval == nil {
return fmt.Errorf("interval must be specified when count > 1")
}
Expand Down Expand Up @@ -78,17 +81,16 @@ func IsWorse(current, new v1alpha1.AnalysisStatus) bool {
return newIndex > currentIndex
}

// IsTerminating returns whether or not the analysis run is terminating
// IsTerminating returns whether or not the analysis run is terminating, either because a terminate
// was requested explicitly, or because a metric has already measured Failed, Error, or Inconclusive
// which causes the run to end prematurely.
func IsTerminating(run *v1alpha1.AnalysisRun) bool {
return run.Spec.Terminate || IsFailing(run)
}

// IsFailing returns whether or not any metric has measured Failed or Error, which will eventually
// cause the entire run to fail.
func IsFailing(run *v1alpha1.AnalysisRun) bool {
if run.Spec.Terminate {
return true
}
for _, res := range run.Status.MetricResults {
jessesuen marked this conversation as resolved.
Show resolved Hide resolved
switch res.Status {
case v1alpha1.AnalysisStatusFailed, v1alpha1.AnalysisStatusError:
case v1alpha1.AnalysisStatusFailed, v1alpha1.AnalysisStatusError, v1alpha1.AnalysisStatusInconclusive:
return true
}
}
Expand Down
11 changes: 7 additions & 4 deletions utils/analysis/analysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func TestIsWorst(t *testing.T) {
assert.False(t, IsWorse(v1alpha1.AnalysisStatusFailed, v1alpha1.AnalysisStatusFailed))
}

func TestIsFailing(t *testing.T) {
func TestIsFastFailTerminating(t *testing.T) {
run := &v1alpha1.AnalysisRun{
Status: &v1alpha1.AnalysisRunStatus{
Status: v1alpha1.AnalysisStatusRunning,
Expand All @@ -168,13 +168,16 @@ func TestIsFailing(t *testing.T) {
},
}
successRate := run.Status.MetricResults[1]
assert.False(t, IsFailing(run))
assert.False(t, IsTerminating(run))
successRate.Status = v1alpha1.AnalysisStatusError
run.Status.MetricResults[1] = successRate
assert.True(t, IsFailing(run))
assert.True(t, IsTerminating(run))
successRate.Status = v1alpha1.AnalysisStatusFailed
run.Status.MetricResults[1] = successRate
assert.True(t, IsFailing(run))
assert.True(t, IsTerminating(run))
successRate.Status = v1alpha1.AnalysisStatusInconclusive
run.Status.MetricResults[1] = successRate
assert.True(t, IsTerminating(run))
}

func TestGetResult(t *testing.T) {
Expand Down