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

feat: Enhance web provider #368

Merged
merged 2 commits into from
Jan 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
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
47 changes: 2 additions & 45 deletions metricproviders/prometheus/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,49 +85,6 @@ func (p *Provider) GarbageCollect(run *v1alpha1.AnalysisRun, metric v1alpha1.Met
return nil
}

func (p *Provider) evaluateResult(result interface{}, metric v1alpha1.Metric) v1alpha1.AnalysisPhase {
successCondition := false
failCondition := false
var err error

if metric.SuccessCondition != "" {
successCondition, err = evaluate.EvalCondition(result, metric.SuccessCondition)
if err != nil {
p.logCtx.Warning(err.Error())
return v1alpha1.AnalysisPhaseError
}
}
if metric.FailureCondition != "" {
failCondition, err = evaluate.EvalCondition(result, metric.FailureCondition)
if err != nil {
return v1alpha1.AnalysisPhaseError
}
}

switch {
case metric.SuccessCondition == "" && metric.FailureCondition == "":
//Always return success unless there is an error
return v1alpha1.AnalysisPhaseSuccessful
case metric.SuccessCondition != "" && metric.FailureCondition == "":
// Without a failure condition, a measurement is considered a failure if the measurement's success condition is not true
failCondition = !successCondition
case metric.SuccessCondition == "" && metric.FailureCondition != "":
// Without a success condition, a measurement is considered a successful if the measurement's failure condition is not true
successCondition = !failCondition
}

if failCondition {
return v1alpha1.AnalysisPhaseFailed
}

if !failCondition && !successCondition {
return v1alpha1.AnalysisPhaseInconclusive
}

// If we reach this code path, failCondition is false and successCondition is true
return v1alpha1.AnalysisPhaseSuccessful
}

func (p *Provider) processResponse(metric v1alpha1.Metric, response model.Value) (string, v1alpha1.AnalysisPhase, error) {
switch value := response.(type) {
case *model.Scalar:
Expand All @@ -136,7 +93,7 @@ func (p *Provider) processResponse(metric v1alpha1.Metric, response model.Value)
if math.IsNaN(result) {
return valueStr, v1alpha1.AnalysisPhaseInconclusive, nil
}
newStatus := p.evaluateResult(result, metric)
newStatus := evaluate.EvaluateResult(result, metric, p.logCtx)
return valueStr, newStatus, nil
case model.Vector:
results := make([]float64, 0, len(value))
Expand All @@ -157,7 +114,7 @@ func (p *Provider) processResponse(metric v1alpha1.Metric, response model.Value)
return valueStr, v1alpha1.AnalysisPhaseInconclusive, nil
}
}
newStatus := p.evaluateResult(results, metric)
newStatus := evaluate.EvaluateResult(results, metric, p.logCtx)
return valueStr, newStatus, nil
//TODO(dthomson) add other response types
default:
Expand Down
89 changes: 0 additions & 89 deletions metricproviders/prometheus/prometheus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,95 +171,6 @@ func TestGarbageCollect(t *testing.T) {
assert.NoError(t, err)
}

func TestEvaluateResultWithSuccess(t *testing.T) {
p := Provider{}
metric := v1alpha1.Metric{
SuccessCondition: "true",
FailureCondition: "false",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, status)
}

func TestEvaluateResultWithFailure(t *testing.T) {
p := Provider{}
metric := v1alpha1.Metric{
SuccessCondition: "true",
FailureCondition: "true",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseFailed, status)

}

func TestEvaluateResultInconclusive(t *testing.T) {
p := Provider{}
metric := v1alpha1.Metric{
SuccessCondition: "false",
FailureCondition: "false",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseInconclusive, status)
}

func TestEvaluateResultNoSuccessConditionAndNotFailing(t *testing.T) {
p := Provider{}
metric := v1alpha1.Metric{
SuccessCondition: "",
FailureCondition: "false",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, status)
}

func TestEvaluateResultNoFailureConditionAndNotSuccessful(t *testing.T) {
p := Provider{}
metric := v1alpha1.Metric{
SuccessCondition: "false",
FailureCondition: "",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseFailed, status)
}

func TestEvaluateResultNoFailureConditionAndNoSuccessCondition(t *testing.T) {
p := Provider{}
metric := v1alpha1.Metric{
SuccessCondition: "",
FailureCondition: "",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, status)
}

func TestEvaluateResultWithErrorOnSuccessCondition(t *testing.T) {
logCtx := log.WithField("test", "test")
p := Provider{
logCtx: *logCtx,
}
metric := v1alpha1.Metric{
SuccessCondition: "a == true",
FailureCondition: "true",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseError, status)

}

func TestEvaluateResultWithErrorOnFailureCondition(t *testing.T) {
logCtx := log.WithField("test", "test")
p := Provider{
logCtx: *logCtx,
}
metric := v1alpha1.Metric{
SuccessCondition: "true",
FailureCondition: "a == true",
}
status := p.evaluateResult(true, metric)
assert.Equal(t, v1alpha1.AnalysisPhaseError, status)

}

func TestProcessScalarResponse(t *testing.T) {
logCtx := log.WithField("test", "test")
p := Provider{
Expand Down
47 changes: 2 additions & 45 deletions metricproviders/wavefront/wavefront.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,49 +114,6 @@ func (p *Provider) GarbageCollect(run *v1alpha1.AnalysisRun, metric v1alpha1.Met
return nil
}

func (p *Provider) evaluateResult(result interface{}, metric v1alpha1.Metric) v1alpha1.AnalysisPhase {
successCondition := false
failCondition := false
var err error

if metric.SuccessCondition != "" {
successCondition, err = evaluate.EvalCondition(result, metric.SuccessCondition)
if err != nil {
p.logCtx.Warning(err.Error())
return v1alpha1.AnalysisPhaseError
}
}
if metric.FailureCondition != "" {
failCondition, err = evaluate.EvalCondition(result, metric.FailureCondition)
if err != nil {
return v1alpha1.AnalysisPhaseError
}
}

switch {
case metric.SuccessCondition == "" && metric.FailureCondition == "":
//Always return success unless there is an error
return v1alpha1.AnalysisPhaseSuccessful
case metric.SuccessCondition != "" && metric.FailureCondition == "":
// Without a failure condition, a measurement is considered a failure if the measurement's success condition is not true
failCondition = !successCondition
case metric.SuccessCondition == "" && metric.FailureCondition != "":
// Without a success condition, a measurement is considered a successful if the measurement's failure condition is not true
successCondition = !failCondition
}

if failCondition {
return v1alpha1.AnalysisPhaseFailed
}

if !failCondition && !successCondition {
return v1alpha1.AnalysisPhaseInconclusive
}

// If we reach this code path, failCondition is false and successCondition is true
return v1alpha1.AnalysisPhaseSuccessful
}

func (p *Provider) processResponse(metric v1alpha1.Metric, response *wavefront_api.QueryResponse) (string, v1alpha1.AnalysisPhase, error) {

if len(response.TimeSeries) == 1 {
Expand All @@ -165,7 +122,7 @@ func (p *Provider) processResponse(metric v1alpha1.Metric, response *wavefront_a
if math.IsNaN(result) {
return fmt.Sprintf("%.2f", result), v1alpha1.AnalysisPhaseInconclusive, nil
}
newStatus := p.evaluateResult(result, metric)
newStatus := evaluate.EvaluateResult(result, metric, p.logCtx)
return fmt.Sprintf("%.2f", result), newStatus, nil

} else if len(response.TimeSeries) > 1 {
Expand All @@ -185,7 +142,7 @@ func (p *Provider) processResponse(metric v1alpha1.Metric, response *wavefront_a
return valueStr, v1alpha1.AnalysisPhaseInconclusive, nil
}
}
newStatus := p.evaluateResult(results, metric)
newStatus := evaluate.EvaluateResult(results, metric, p.logCtx)
return valueStr, newStatus, nil

} else {
Expand Down
65 changes: 17 additions & 48 deletions metricproviders/webmetric/webmetric.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import (
"net/url"
"time"

"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/utils/evaluate"
metricutil "github.com/argoproj/argo-rollouts/utils/metric"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/jsonpath"

"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
"github.com/argoproj/argo-rollouts/utils/evaluate"
templateutil "github.com/argoproj/argo-rollouts/utils/template"
)

const (
Expand Down Expand Up @@ -47,16 +49,26 @@ func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alph
request := &http.Request{
Method: "GET", // TODO maybe make this configurable....also implies we will need body templates
}
urlStr, err := templateutil.ResolveArgs(metric.Provider.Web.URL, run.Spec.Args)
if err != nil {
return metricutil.MarkMeasurementError(measurement, err)
}

url, err := url.Parse(metric.Provider.Web.URL)
url, err := url.Parse(urlStr)
if err != nil {
return metricutil.MarkMeasurementError(measurement, err)
}

request.URL = url

request.Header = make(http.Header)

for _, header := range metric.Provider.Web.Headers {
request.Header.Set(header.Key, header.Value)
value, err := templateutil.ResolveArgs(header.Value, run.Spec.Args)
if err != nil {
return metricutil.MarkMeasurementError(measurement, err)
}
request.Header.Set(header.Key, value)
}

// Send Request
Expand Down Expand Up @@ -100,53 +112,10 @@ func (p *Provider) parseResponse(metric v1alpha1.Metric, response *http.Response
}
out := buf.String()

status := p.evaluateResponse(metric, out)
status := evaluate.EvaluateResult(out, metric, p.logCtx)
return out, status, nil
}

func (p *Provider) evaluateResponse(metric v1alpha1.Metric, result interface{}) v1alpha1.AnalysisPhase {
successCondition := false
failCondition := false
var err error

if metric.SuccessCondition != "" {
successCondition, err = evaluate.EvalCondition(result, metric.SuccessCondition)
if err != nil {
p.logCtx.Warning(err.Error())
return v1alpha1.AnalysisPhaseError
}
}
if metric.FailureCondition != "" {
failCondition, err = evaluate.EvalCondition(result, metric.FailureCondition)
if err != nil {
return v1alpha1.AnalysisPhaseError
}
}

switch {
case metric.SuccessCondition == "" && metric.FailureCondition == "":
//Always return success unless there is an error
return v1alpha1.AnalysisPhaseSuccessful
case metric.SuccessCondition != "" && metric.FailureCondition == "":
// Without a failure condition, a measurement is considered a failure if the measurement's success condition is not true
failCondition = !successCondition
case metric.SuccessCondition == "" && metric.FailureCondition != "":
// Without a success condition, a measurement is considered a successful if the measurement's failure condition is not true
successCondition = !failCondition
}

if failCondition {
return v1alpha1.AnalysisPhaseFailed
}

if !failCondition && !successCondition {
return v1alpha1.AnalysisPhaseInconclusive
}

// If we reach this code path, failCondition is false and successCondition is true
return v1alpha1.AnalysisPhaseSuccessful
}

// Resume should not be used the WebMetric provider since all the work should occur in the Run method
func (p *Provider) Resume(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement {
p.logCtx.Warn("WebMetric provider should not execute the Resume method")
Expand Down
47 changes: 47 additions & 0 deletions utils/evaluate/evaluate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,55 @@ import (
"strconv"

"github.com/antonmedv/expr"
"github.com/sirupsen/logrus"

"github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
)

func EvaluateResult(result interface{}, metric v1alpha1.Metric, logCtx logrus.Entry) v1alpha1.AnalysisPhase {
successCondition := false
failCondition := false
var err error

if metric.SuccessCondition != "" {
successCondition, err = EvalCondition(result, metric.SuccessCondition)
if err != nil {
logCtx.Warning(err.Error())
return v1alpha1.AnalysisPhaseError
}
}
if metric.FailureCondition != "" {
failCondition, err = EvalCondition(result, metric.FailureCondition)
if err != nil {
logCtx.Warning(err.Error())
return v1alpha1.AnalysisPhaseError
}
}

switch {
case metric.SuccessCondition == "" && metric.FailureCondition == "":
//Always return success unless there is an error
return v1alpha1.AnalysisPhaseSuccessful
case metric.SuccessCondition != "" && metric.FailureCondition == "":
// Without a failure condition, a measurement is considered a failure if the measurement's success condition is not true
failCondition = !successCondition
case metric.SuccessCondition == "" && metric.FailureCondition != "":
// Without a success condition, a measurement is considered a successful if the measurement's failure condition is not true
successCondition = !failCondition
}

if failCondition {
return v1alpha1.AnalysisPhaseFailed
}

if !failCondition && !successCondition {
return v1alpha1.AnalysisPhaseInconclusive
}

// If we reach this code path, failCondition is false and successCondition is true
return v1alpha1.AnalysisPhaseSuccessful
}

// EvalCondition evaluates the condition with the resultValue as an input
func EvalCondition(resultValue interface{}, condition string) (bool, error) {
var err error
Expand Down
Loading