Price-preferred node expander - part 1
This commit is contained in:
parent
6e1c5d23fd
commit
fa87710b1e
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package price
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
|
||||||
|
"k8s.io/autoscaler/cluster-autoscaler/expander"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: add preferred node
|
||||||
|
type priceBased struct {
|
||||||
|
pricingModel cloudprovider.PricingModel
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStrategy returns an expansion strategy that picks nodes based on price and preferred node type.
|
||||||
|
func NewStrategy(pricingModel cloudprovider.PricingModel) expander.Strategy {
|
||||||
|
return &priceBased{
|
||||||
|
pricingModel: pricingModel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BestOption selects option based on cost and preferred node type.
|
||||||
|
func (p *priceBased) BestOption(expansionOptions []expander.Option, nodeInfos map[string]*schedulercache.NodeInfo) *expander.Option {
|
||||||
|
var bestOption *expander.Option
|
||||||
|
bestOptionScore := 0.0
|
||||||
|
now := time.Now()
|
||||||
|
then := now.Add(time.Hour)
|
||||||
|
|
||||||
|
nextoption:
|
||||||
|
for i, option := range expansionOptions {
|
||||||
|
nodeInfo, found := nodeInfos[option.NodeGroup.Id()]
|
||||||
|
if !found {
|
||||||
|
glog.Warningf("No node info for %s", option.NodeGroup.Id())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nodePrice, err := p.pricingModel.NodePrice(nodeInfo.Node(), now, then)
|
||||||
|
if err != nil {
|
||||||
|
glog.Warningf("Failed to calculate node price for %s: %v", option.NodeGroup.Id(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalNodePrice := nodePrice * float64(option.NodeCount)
|
||||||
|
totalPodPrice := 0.0
|
||||||
|
for _, pod := range option.Pods {
|
||||||
|
podPrice, err := p.pricingModel.PodPrice(pod, now, then)
|
||||||
|
if err != nil {
|
||||||
|
glog.Warningf("Failed to calculate pod price for %s/%s: %v", pod.Namespace, pod.Name, err)
|
||||||
|
continue nextoption
|
||||||
|
}
|
||||||
|
totalPodPrice += podPrice
|
||||||
|
}
|
||||||
|
if totalPodPrice == 0 {
|
||||||
|
glog.Warningf("Total pod price is 0, skipping %s", option.NodeGroup.Id())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
optionScore := totalNodePrice / totalPodPrice
|
||||||
|
glog.V(5).Infof("Price of %s expansion is %f - ratio %f", option.NodeGroup.Id(), totalNodePrice)
|
||||||
|
|
||||||
|
if bestOption == nil || bestOptionScore > optionScore {
|
||||||
|
bestOption = &expansionOptions[i]
|
||||||
|
bestOptionScore = optionScore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestOption
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package price
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"k8s.io/autoscaler/cluster-autoscaler/expander"
|
||||||
|
|
||||||
|
"k8s.io/autoscaler/cluster-autoscaler/cloudprovider/test"
|
||||||
|
. "k8s.io/autoscaler/cluster-autoscaler/utils/test"
|
||||||
|
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testPricingModel struct {
|
||||||
|
nodePrice map[string]float64
|
||||||
|
podPrice map[string]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpm *testPricingModel) NodePrice(node *apiv1.Node, startTime time.Time, endTime time.Time) (float64, error) {
|
||||||
|
if price, found := tpm.nodePrice[node.Name]; found {
|
||||||
|
return price, nil
|
||||||
|
}
|
||||||
|
return 0.0, fmt.Errorf("price for node %v not found", node.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tpm *testPricingModel) PodPrice(node *apiv1.Pod, startTime time.Time, endTime time.Time) (float64, error) {
|
||||||
|
if price, found := tpm.podPrice[node.Name]; found {
|
||||||
|
return price, nil
|
||||||
|
}
|
||||||
|
return 0.0, fmt.Errorf("price for pod %v not found", node.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPriceExpander(t *testing.T) {
|
||||||
|
n1 := BuildTestNode("n1", 1000, 1000)
|
||||||
|
n2 := BuildTestNode("n2", 10000, 1000)
|
||||||
|
|
||||||
|
p1 := BuildTestPod("p1", 1000, 0)
|
||||||
|
p2 := BuildTestPod("p2", 500, 0)
|
||||||
|
|
||||||
|
provider := testprovider.NewTestCloudProvider(nil, nil)
|
||||||
|
provider.AddNodeGroup("ng1", 1, 10, 1)
|
||||||
|
provider.AddNodeGroup("ng2", 1, 10, 1)
|
||||||
|
provider.AddNode("ng1", n1)
|
||||||
|
provider.AddNode("ng2", n2)
|
||||||
|
ng1, _ := provider.NodeGroupForNode(n1)
|
||||||
|
ng2, _ := provider.NodeGroupForNode(n2)
|
||||||
|
|
||||||
|
ni1 := schedulercache.NewNodeInfo()
|
||||||
|
ni1.SetNode(n1)
|
||||||
|
ni2 := schedulercache.NewNodeInfo()
|
||||||
|
ni2.SetNode(n2)
|
||||||
|
nodeInfosForGroups := map[string]*schedulercache.NodeInfo{
|
||||||
|
"ng1": ni1, "ng2": ni2,
|
||||||
|
}
|
||||||
|
|
||||||
|
// All node groups accept the same set of pods
|
||||||
|
options := []expander.Option{
|
||||||
|
{
|
||||||
|
NodeGroup: ng1,
|
||||||
|
NodeCount: 2,
|
||||||
|
Pods: []*apiv1.Pod{p1, p2},
|
||||||
|
Debug: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NodeGroup: ng2,
|
||||||
|
NodeCount: 1,
|
||||||
|
Pods: []*apiv1.Pod{p1, p2},
|
||||||
|
Debug: "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// First node group is cheapter
|
||||||
|
assert.Equal(t, "1", NewStrategy(&testPricingModel{
|
||||||
|
podPrice: map[string]float64{
|
||||||
|
"p1": 20.0,
|
||||||
|
"p2": 10.0,
|
||||||
|
},
|
||||||
|
nodePrice: map[string]float64{
|
||||||
|
"n1": 20.0,
|
||||||
|
"n2": 200.0,
|
||||||
|
},
|
||||||
|
}).BestOption(options, nodeInfosForGroups).Debug)
|
||||||
|
|
||||||
|
// Second node group is cheapter
|
||||||
|
assert.Equal(t, "2", NewStrategy(&testPricingModel{
|
||||||
|
podPrice: map[string]float64{
|
||||||
|
"p1": 20.0,
|
||||||
|
"p2": 10.0,
|
||||||
|
},
|
||||||
|
nodePrice: map[string]float64{
|
||||||
|
"n1": 200.0,
|
||||||
|
"n2": 100.0,
|
||||||
|
},
|
||||||
|
}).BestOption(options, nodeInfosForGroups).Debug)
|
||||||
|
|
||||||
|
// First group accept 1 pod and second accepts 2.
|
||||||
|
options2 := []expander.Option{
|
||||||
|
{
|
||||||
|
NodeGroup: ng1,
|
||||||
|
NodeCount: 2,
|
||||||
|
Pods: []*apiv1.Pod{p1},
|
||||||
|
Debug: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NodeGroup: ng2,
|
||||||
|
NodeCount: 1,
|
||||||
|
Pods: []*apiv1.Pod{p1, p2},
|
||||||
|
Debug: "2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both node groups are equally expensive. However 2
|
||||||
|
// accept two pods.
|
||||||
|
assert.Equal(t, "2", NewStrategy(&testPricingModel{
|
||||||
|
podPrice: map[string]float64{
|
||||||
|
"p1": 20.0,
|
||||||
|
"p2": 10.0,
|
||||||
|
},
|
||||||
|
nodePrice: map[string]float64{
|
||||||
|
"n1": 200.0,
|
||||||
|
"n2": 200.0,
|
||||||
|
},
|
||||||
|
}).BestOption(options2, nodeInfosForGroups).Debug)
|
||||||
|
|
||||||
|
// Errors are expected
|
||||||
|
assert.Nil(t, NewStrategy(&testPricingModel{
|
||||||
|
podPrice: map[string]float64{},
|
||||||
|
nodePrice: map[string]float64{},
|
||||||
|
}).BestOption(options2, nodeInfosForGroups))
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue