Skip to content

Commit

Permalink
pass-through strategy plugin (#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
jazzyfresh authored Mar 31, 2021
1 parent 993e026 commit 404ff5b
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 0 deletions.
16 changes: 16 additions & 0 deletions plugins/builtin/strategy/pass-through/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad-autoscaler/plugins"
passthrough "github.com/hashicorp/nomad-autoscaler/plugins/builtin/strategy/pass-through/plugin"
)

func main() {
plugins.Serve(factory)
}

// factory returns a new instance of the PassThrough Strategy plugin.
func factory(log hclog.Logger) interface{} {
return passthrough.NewPassThroughPlugin(log)
}
96 changes: 96 additions & 0 deletions plugins/builtin/strategy/pass-through/plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package plugin

import (
"fmt"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad-autoscaler/plugins"
"github.com/hashicorp/nomad-autoscaler/plugins/base"
"github.com/hashicorp/nomad-autoscaler/plugins/strategy"
"github.com/hashicorp/nomad-autoscaler/sdk"
)

const (
// pluginName is the unique name of the this plugin amongst strategy
// plugins.
pluginName = "pass-through"
)

var (
PluginID = plugins.PluginID{
Name: pluginName,
PluginType: sdk.PluginTypeStrategy,
}

PluginConfig = &plugins.InternalPluginConfig{
Factory: func(l hclog.Logger) interface{} { return NewPassThroughPlugin(l) },
}

pluginInfo = &base.PluginInfo{
Name: pluginName,
PluginType: sdk.PluginTypeStrategy,
}
)

// Assert that StrategyPlugin meets the strategy.Strategy interface.
var _ strategy.Strategy = (*StrategyPlugin)(nil)

// StrategyPlugin is the None implementation of the strategy.Strategy
// interface.
type StrategyPlugin struct {
logger hclog.Logger
}

// NewPassThroughPlugin returns the Pass Through implementation of the
// strategy.Strategy interface.
func NewPassThroughPlugin(log hclog.Logger) strategy.Strategy {
return &StrategyPlugin{
logger: log,
}
}

// SetConfig satisfies the SetConfig function on the base.Base interface.
func (s *StrategyPlugin) SetConfig(config map[string]string) error {
return nil
}

// PluginInfo satisfies the PluginInfo function on the base.Base interface.
func (s *StrategyPlugin) PluginInfo() (*base.PluginInfo, error) {
return pluginInfo, nil
}

// Run satisfies the Run function on the strategy.Strategy interface.
func (s *StrategyPlugin) Run(eval *sdk.ScalingCheckEvaluation, count int64) (*sdk.ScalingCheckEvaluation, error) {

// This shouldn't happen, but check it just in case.
if len(eval.Metrics) == 0 {
eval.Action.Direction = sdk.ScaleDirectionNone
return eval, nil
}

// Use only the latest value for now.
metric := eval.Metrics[len(eval.Metrics)-1]

// Identify the direction of scaling, if any.
eval.Action.Direction = s.calculateDirection(count, metric.Value)
if eval.Action.Direction == sdk.ScaleDirectionNone {
return eval, nil
}

eval.Action.Count = int64(metric.Value)
eval.Action.Reason = fmt.Sprintf("scaling %s because metric is %d", eval.Action.Direction, eval.Action.Count)

return eval, nil
}

// calculateDirection is used to calculate the direction of scaling that should
// occur, if any at all.
func (s *StrategyPlugin) calculateDirection(count int64, metric float64) sdk.ScaleDirection {
if metric > float64(count) {
return sdk.ScaleDirectionUp
} else if metric < float64(count) {
return sdk.ScaleDirectionDown
} else {
return sdk.ScaleDirectionNone
}
}
168 changes: 168 additions & 0 deletions plugins/builtin/strategy/pass-through/plugin/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package plugin

import (
"testing"

hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad-autoscaler/plugins/base"
"github.com/hashicorp/nomad-autoscaler/sdk"
"github.com/stretchr/testify/assert"
)

func TestStrategyPlugin_SetConfig(t *testing.T) {
s := &StrategyPlugin{}
expectedOutput := map[string]string{"example-item": "example-value"}
err := s.SetConfig(expectedOutput)
assert.Nil(t, err)
}

func TestStrategyPlugin_PluginInfo(t *testing.T) {
s := &StrategyPlugin{}
expectedOutput := &base.PluginInfo{Name: "pass-through", PluginType: "strategy"}
actualOutput, err := s.PluginInfo()
assert.Nil(t, err)
assert.Equal(t, expectedOutput, actualOutput)
}

func TestStrategyPlugin_Run(t *testing.T) {
testCases := []struct {
inputEval *sdk.ScalingCheckEvaluation
inputCount int64
expectedResp *sdk.ScalingCheckEvaluation
expectedError error
name string
}{
{
inputEval: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 13}},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{},
},
inputCount: 2,
expectedResp: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 13}},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{
Count: 13,
Direction: sdk.ScaleDirectionUp,
Reason: "scaling up because metric is 13",
},
},
expectedError: nil,
name: "pass-through scale up",
},
{
inputEval: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 0}},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{},
},
inputCount: 2,
expectedResp: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 0}},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{
Count: 0,
Direction: sdk.ScaleDirectionDown,
Reason: "scaling down because metric is 0",
},
},
expectedError: nil,
name: "pass-through scale down to 0",
},
{
inputEval: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 10}},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{},
},
inputCount: 10,
expectedResp: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 10}},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{
Direction: sdk.ScaleDirectionNone,
},
},
expectedError: nil,
name: "no scaling - current count same as metric",
},
{
inputEval: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{},
},
inputCount: 10,
expectedResp: &sdk.ScalingCheckEvaluation{
Metrics: sdk.TimestampedMetrics{},
Check: &sdk.ScalingPolicyCheck{
Strategy: &sdk.ScalingPolicyStrategy{
Config: map[string]string{},
},
},
Action: &sdk.ScalingAction{
Direction: sdk.ScaleDirectionNone,
},
},
expectedError: nil,
name: "no scaling - empty metric timeseries",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
s := &StrategyPlugin{logger: hclog.NewNullLogger()}
actualResp, actualError := s.Run(tc.inputEval, tc.inputCount)
assert.Equal(t, tc.expectedResp, actualResp, tc.name)
assert.Equal(t, tc.expectedError, actualError, tc.name)
})
}
}

func TestStrategyPlugin_calculateDirection(t *testing.T) {
testCases := []struct {
inputCount int64
inputMetric float64
threshold float64
expectedOutput sdk.ScaleDirection
}{
{inputCount: 0, inputMetric: 1, expectedOutput: sdk.ScaleDirectionUp},
{inputCount: 5, inputMetric: 5, expectedOutput: sdk.ScaleDirectionNone},
{inputCount: 4, inputMetric: 0, expectedOutput: sdk.ScaleDirectionDown},
}

s := &StrategyPlugin{}

for _, tc := range testCases {
assert.Equal(t, tc.expectedOutput, s.calculateDirection(tc.inputCount, tc.inputMetric))
}
}
5 changes: 5 additions & 0 deletions plugins/manager/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
datadog "github.com/hashicorp/nomad-autoscaler/plugins/builtin/apm/datadog/plugin"
nomadAPM "github.com/hashicorp/nomad-autoscaler/plugins/builtin/apm/nomad/plugin"
prometheus "github.com/hashicorp/nomad-autoscaler/plugins/builtin/apm/prometheus/plugin"
passthrough "github.com/hashicorp/nomad-autoscaler/plugins/builtin/strategy/pass-through/plugin"
targetValue "github.com/hashicorp/nomad-autoscaler/plugins/builtin/strategy/target-value/plugin"
awsASG "github.com/hashicorp/nomad-autoscaler/plugins/builtin/target/aws-asg/plugin"
azureVMSS "github.com/hashicorp/nomad-autoscaler/plugins/builtin/target/azure-vmss/plugin"
Expand All @@ -29,6 +30,9 @@ func (pm *PluginManager) loadInternalPlugin(cfg *config.Plugin, pluginType strin
case plugins.InternalTargetNomad:
info.factory = nomadTarget.PluginConfig.Factory
info.driver = "nomad-target"
case plugins.InternalStrategyPassThrough:
info.factory = passthrough.PluginConfig.Factory
info.driver = "pass-through"
case plugins.InternalStrategyTargetValue:
info.factory = targetValue.PluginConfig.Factory
info.driver = "target-value"
Expand Down Expand Up @@ -86,6 +90,7 @@ func (pm *PluginManager) useInternal(plugin string) bool {
case plugins.InternalAPMNomad,
plugins.InternalTargetNomad,
plugins.InternalAPMPrometheus,
plugins.InternalStrategyPassThrough,
plugins.InternalStrategyTargetValue,
plugins.InternalTargetAWSASG,
plugins.InternalTargetAzureVMSS,
Expand Down
3 changes: 3 additions & 0 deletions plugins/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const (
// InternalAPMPrometheus is the Prometheus APM internal plugin name.
InternalAPMPrometheus = "prometheus"

// InternalStrategyPassThrough is the Pass Through strategy internal plugin name.
InternalStrategyPassThrough = "pass-through"

// InternalStrategyTargetValue is the Target Value Strategy internal plugin
// name.
InternalStrategyTargetValue = "target-value"
Expand Down

0 comments on commit 404ff5b

Please sign in to comment.