-
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
404ff5b
commit dc56c42
Showing
5 changed files
with
300 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" | ||
fixedvalue "github.com/hashicorp/nomad-autoscaler/plugins/builtin/strategy/fixed-value/plugin" | ||
) | ||
|
||
func main() { | ||
plugins.Serve(factory) | ||
} | ||
|
||
// factory returns a new instance of the FixedValue Strategy plugin. | ||
func factory(log hclog.Logger) interface{} { | ||
return fixedvalue.NewFixedValuePlugin(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,110 @@ | ||
package plugin | ||
|
||
import ( | ||
"fmt" | ||
"strconv" | ||
|
||
"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 = "fixed-value" | ||
|
||
// These are the keys read from the RunRequest.Config map. | ||
runConfigKeyValue = "value" | ||
) | ||
|
||
var ( | ||
PluginID = plugins.PluginID{ | ||
Name: pluginName, | ||
PluginType: sdk.PluginTypeStrategy, | ||
} | ||
|
||
PluginConfig = &plugins.InternalPluginConfig{ | ||
Factory: func(l hclog.Logger) interface{} { return NewFixedValuePlugin(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 FixedValue implementation of the strategy.Strategy | ||
// interface. | ||
type StrategyPlugin struct { | ||
config map[string]string | ||
logger hclog.Logger | ||
} | ||
|
||
// NewFixedValuePlugin returns the FixedValue implementation of the | ||
// strategy.Strategy interface. | ||
func NewFixedValuePlugin(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 { | ||
s.config = config | ||
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) { | ||
|
||
// Read and parse fixed value from req.Config. | ||
t := eval.Check.Strategy.Config[runConfigKeyValue] | ||
if t == "" { | ||
return nil, fmt.Errorf("missing required field `%s`", runConfigKeyValue) | ||
} | ||
|
||
value, err := strconv.ParseInt(t, 10, 64) | ||
if err != nil { | ||
return nil, fmt.Errorf("invalid value for `%s`: %v (%T)", runConfigKeyValue, t, t) | ||
} | ||
|
||
// Identify the direction of scaling, if any. | ||
eval.Action.Direction = s.calculateDirection(count, value) | ||
if eval.Action.Direction == sdk.ScaleDirectionNone { | ||
return eval, nil | ||
} | ||
|
||
// Log at trace level the details of the strategy calculation. This is | ||
// helpful in ultra-debugging situations when there is a need to understand | ||
// all the calculations made. | ||
s.logger.Trace("calculated scaling strategy results", | ||
"check_name", eval.Check.Name, "current_count", count, "new_count", value, | ||
"direction", eval.Action.Direction) | ||
|
||
eval.Action.Count = value | ||
eval.Action.Reason = fmt.Sprintf("scaling %s because fixed value is %d", eval.Action.Direction, value) | ||
|
||
return eval, nil | ||
} | ||
|
||
// calculateDirection is used to calculate the direction of scaling that should | ||
// occur, if any at all. | ||
func (s *StrategyPlugin) calculateDirection(count, fixed int64) sdk.ScaleDirection { | ||
if count == fixed { | ||
return sdk.ScaleDirectionNone | ||
} else if count < fixed { | ||
return sdk.ScaleDirectionUp | ||
} | ||
return sdk.ScaleDirectionDown | ||
} |
166 changes: 166 additions & 0 deletions
166
plugins/builtin/strategy/fixed-value/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,166 @@ | ||
package plugin | ||
|
||
import ( | ||
"fmt" | ||
"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) | ||
assert.Equal(t, expectedOutput, s.config) | ||
} | ||
|
||
func TestStrategyPlugin_PluginInfo(t *testing.T) { | ||
s := &StrategyPlugin{} | ||
expectedOutput := &base.PluginInfo{Name: "fixed-value", 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{ | ||
Check: &sdk.ScalingPolicyCheck{ | ||
Strategy: &sdk.ScalingPolicyStrategy{}, | ||
}, | ||
}, | ||
expectedResp: nil, | ||
expectedError: fmt.Errorf("missing required field `value`"), | ||
name: "incorrect strategy input config", | ||
}, | ||
{ | ||
inputEval: &sdk.ScalingCheckEvaluation{ | ||
Check: &sdk.ScalingPolicyCheck{ | ||
Strategy: &sdk.ScalingPolicyStrategy{ | ||
Config: map[string]string{"value": "not-the-int-you're-looking-for"}, | ||
}, | ||
}, | ||
}, | ||
expectedResp: nil, | ||
expectedError: fmt.Errorf("invalid value for `value`: not-the-int-you're-looking-for (string)"), | ||
name: "incorrect input strategy config fixed value - string", | ||
}, | ||
{ | ||
inputEval: &sdk.ScalingCheckEvaluation{ | ||
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 13}}, | ||
Check: &sdk.ScalingPolicyCheck{ | ||
Strategy: &sdk.ScalingPolicyStrategy{ | ||
Config: map[string]string{"value": "13"}, | ||
}, | ||
}, | ||
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{"value": "13"}, | ||
}, | ||
}, | ||
Action: &sdk.ScalingAction{ | ||
Count: 13, | ||
Reason: "scaling up because fixed value is 13", | ||
Direction: sdk.ScaleDirectionUp, | ||
}, | ||
}, | ||
expectedError: nil, | ||
name: "fixed scale up", | ||
}, | ||
{ | ||
inputEval: &sdk.ScalingCheckEvaluation{ | ||
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 26}}, | ||
Check: &sdk.ScalingPolicyCheck{ | ||
Strategy: &sdk.ScalingPolicyStrategy{ | ||
Config: map[string]string{"value": "4"}, | ||
}, | ||
}, | ||
Action: &sdk.ScalingAction{}, | ||
}, | ||
inputCount: 10, | ||
expectedResp: &sdk.ScalingCheckEvaluation{ | ||
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 26}}, | ||
Check: &sdk.ScalingPolicyCheck{ | ||
Strategy: &sdk.ScalingPolicyStrategy{ | ||
Config: map[string]string{"value": "4"}, | ||
}, | ||
}, | ||
Action: &sdk.ScalingAction{ | ||
Count: 4, | ||
Reason: "scaling down because fixed value is 4", | ||
Direction: sdk.ScaleDirectionDown, | ||
}, | ||
}, | ||
expectedError: nil, | ||
name: "fixed scale down", | ||
}, | ||
{ | ||
inputEval: &sdk.ScalingCheckEvaluation{ | ||
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 9}}, | ||
Check: &sdk.ScalingPolicyCheck{ | ||
Strategy: &sdk.ScalingPolicyStrategy{ | ||
Config: map[string]string{"value": "10"}, | ||
}, | ||
}, | ||
Action: &sdk.ScalingAction{}, | ||
}, | ||
inputCount: 10, | ||
expectedResp: &sdk.ScalingCheckEvaluation{ | ||
Metrics: sdk.TimestampedMetrics{sdk.TimestampedMetric{Value: 9}}, | ||
Check: &sdk.ScalingPolicyCheck{ | ||
Strategy: &sdk.ScalingPolicyStrategy{ | ||
Config: map[string]string{"value": "10"}, | ||
}, | ||
}, | ||
Action: &sdk.ScalingAction{ | ||
Direction: sdk.ScaleDirectionNone, | ||
}, | ||
}, | ||
expectedError: nil, | ||
name: "no scaling - current count same as fixed", | ||
}, | ||
} | ||
|
||
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 | ||
fixedCount int64 | ||
expectedOutput sdk.ScaleDirection | ||
}{ | ||
{inputCount: 0, fixedCount: 1, expectedOutput: sdk.ScaleDirectionUp}, | ||
{inputCount: 5, fixedCount: 5, expectedOutput: sdk.ScaleDirectionNone}, | ||
{inputCount: 4, fixedCount: 0, expectedOutput: sdk.ScaleDirectionDown}, | ||
} | ||
|
||
s := &StrategyPlugin{} | ||
|
||
for _, tc := range testCases { | ||
assert.Equal(t, tc.expectedOutput, s.calculateDirection(tc.inputCount, tc.fixedCount)) | ||
} | ||
} |
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