Price-preferred node expander - part 1

This commit is contained in:
Marcin Wielgus 2017-05-29 23:45:24 +02:00
parent 6e1c5d23fd
commit fa87710b1e
2 changed files with 234 additions and 0 deletions

View File

@ -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
}

View File

@ -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))
}