-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
993e026
commit 404ff5b
Showing
5 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
168
plugins/builtin/strategy/pass-through/plugin/plugin_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters