Merge pull request #707 from kgrygiel/master

Use the builder pattern in tests to make them more readable and extensible.
This commit is contained in:
Beata Skiba 2018-03-14 12:19:09 +01:00 committed by GitHub
commit b709af17b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 287 additions and 97 deletions

View File

@ -36,7 +36,13 @@ func TestUpdateResourceRequests(t *testing.T) {
}
containerName := "container1"
labels := map[string]string{"app": "testingApp"}
vpa := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", "app = testingApp")
vpaBuilder := test.VerticalPodAutoscaler().
WithContainer(containerName).
WithTarget("2", "200M").
WithMinAllowed("1", "100M").
WithMaxAllowed("3", "1G").
WithSelector("app = testingApp")
vpa := vpaBuilder.Get()
uninitialized := test.BuildTestPod("test_uninitialized", containerName, "", "", nil, nil)
uninitialized.ObjectMeta.Labels = labels
@ -44,13 +50,11 @@ func TestUpdateResourceRequests(t *testing.T) {
initialized := test.BuildTestPod("test_initialized", containerName, "1", "100M", nil, nil)
initialized.ObjectMeta.Labels = labels
mismatchedVPA := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", "app = differentApp")
offVPA := test.BuildTestVerticalPodAutoscaler(containerName, "2.5", "1", "3", "250M", "100M", "1G", "app = testingApp")
offVPA.Spec.UpdatePolicy.UpdateMode = vpa_types.UpdateModeOff
mismatchedVPA := vpaBuilder.WithSelector("app = differentApp").Get()
offVPA := vpaBuilder.WithUpdateMode(vpa_types.UpdateModeOff).Get()
targetBelowMinVPA := test.BuildTestVerticalPodAutoscaler(containerName, "3", "4", "5", "150M", "300M", "1G", "app = testingApp")
targetAboveMaxVPA := test.BuildTestVerticalPodAutoscaler(containerName, "7", "4", "5", "2G", "300M", "1G", "app = testingApp")
targetBelowMinVPA := vpaBuilder.WithTarget("3", "150M").WithMinAllowed("4", "300M").WithMaxAllowed("5", "1G").Get()
targetAboveMaxVPA := vpaBuilder.WithTarget("7", "2G").WithMinAllowed("4", "300M").WithMaxAllowed("5", "1G").Get()
testCases := []testCase{{
pod: uninitialized,
@ -113,7 +117,6 @@ func TestUpdateResourceRequests(t *testing.T) {
memory, err := resource.ParseQuantity(tc.expectedMem)
assert.NoError(t, err)
assert.Equal(t, memory, requests[0][apiv1.ResourceMemory])
} else {
assert.Equal(t, len(requests), 0)
}

View File

@ -0,0 +1,82 @@
/*
Copyright 2018 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 test
import (
apiv1 "k8s.io/api/core/v1"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/poc.autoscaling.k8s.io/v1alpha1"
)
// RecommendationBuilder helps building test instances of RecommendedPodResources.
type RecommendationBuilder interface {
WithContainer(containerName string) RecommendationBuilder
WithTarget(cpu, memory string) RecommendationBuilder
WithMinRecommended(cpu, memory string) RecommendationBuilder
WithMaxRecommended(cpu, memory string) RecommendationBuilder
Get() *vpa_types.RecommendedPodResources
}
// Recommendation returns a new RecommendationBuilder.
func Recommendation() RecommendationBuilder {
return &recommendationBuilder{}
}
type recommendationBuilder struct {
containerName string
target apiv1.ResourceList
minRecommended apiv1.ResourceList
maxRecommended apiv1.ResourceList
}
func (b *recommendationBuilder) WithContainer(containerName string) RecommendationBuilder {
c := *b
c.containerName = containerName
return &c
}
func (b *recommendationBuilder) WithTarget(cpu, memory string) RecommendationBuilder {
c := *b
c.target = Resources(cpu, memory)
return &c
}
func (b *recommendationBuilder) WithMinRecommended(cpu, memory string) RecommendationBuilder {
c := *b
c.minRecommended = Resources(cpu, memory)
return &c
}
func (b *recommendationBuilder) WithMaxRecommended(cpu, memory string) RecommendationBuilder {
c := *b
c.maxRecommended = Resources(cpu, memory)
return &c
}
func (b *recommendationBuilder) Get() *vpa_types.RecommendedPodResources {
if b.containerName == "" {
panic("Must call WithContainer() before Get()")
}
return &vpa_types.RecommendedPodResources{
ContainerRecommendations: []vpa_types.RecommendedContainerResources{
{
Name: b.containerName,
Target: b.target,
MinRecommended: b.minRecommended,
MaxRecommended: b.maxRecommended,
},
}}
}

View File

@ -18,7 +18,6 @@ package test
import (
"fmt"
"log"
"time"
"github.com/stretchr/testify/mock"
@ -119,41 +118,6 @@ func BuildTestPolicy(containerName, minCPU, maxCPU, minMemory, maxMemory string)
}}
}
// BuildTestVerticalPodAutoscaler creates VerticalPodAutoscaler with specified policy constraints.
// TODO: Allow passing arbitrary MinRecommended and MaxRecommended values.
func BuildTestVerticalPodAutoscaler(containerName, targetCPU, minCPU, maxCPU, targetMemory, minMemory, maxMemory string, selector string) *vpa_types.VerticalPodAutoscaler {
resourcesPolicy := BuildTestPolicy(containerName, minCPU, maxCPU, minMemory, maxMemory)
labelSelector, err := metav1.ParseToLabelSelector(selector)
if err != nil {
log.Fatal(err)
}
recommendedResources := Resources(targetCPU, targetMemory)
return &vpa_types.VerticalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
Spec: vpa_types.VerticalPodAutoscalerSpec{
Selector: labelSelector,
UpdatePolicy: vpa_types.PodUpdatePolicy{},
ResourcePolicy: *resourcesPolicy,
},
Status: vpa_types.VerticalPodAutoscalerStatus{
Recommendation: vpa_types.RecommendedPodResources{
ContainerRecommendations: []vpa_types.RecommendedContainerResources{
{
Name: containerName,
Target: recommendedResources,
MinRecommended: recommendedResources,
MaxRecommended: recommendedResources,
},
},
},
},
}
}
// Resources creates a ResourceList with given amount of cpu and memory.
func Resources(cpu, mem string) apiv1.ResourceList {
result := make(apiv1.ResourceList)
@ -168,32 +132,6 @@ func Resources(cpu, mem string) apiv1.ResourceList {
return result
}
// Recommendation creates Recommendation with specified container name and Target resources.
func Recommendation(containerName, cpu, mem string) *vpa_types.RecommendedPodResources {
return &vpa_types.RecommendedPodResources{ContainerRecommendations: []vpa_types.RecommendedContainerResources{
{
Name: containerName,
Target: Resources(cpu, mem),
MinRecommended: Resources(cpu, mem),
MaxRecommended: Resources(cpu, mem),
}},
}
}
// AddMinRecommended extends a RecommendedPodResources object returned by Recommendation()
// with MinRecommended resources.
// TODO: Replace with the builder pattern.
func AddMinRecommended(recommendation *vpa_types.RecommendedPodResources, cpu, mem string) {
recommendation.ContainerRecommendations[0].MinRecommended = Resources(cpu, mem)
}
// AddMaxRecommended extends a RecommendedPodResources object returned by Recommendation()
// with MaxRecommended resources.
// TODO: Replace with the builder pattern.
func AddMaxRecommended(recommendation *vpa_types.RecommendedPodResources, cpu, mem string) {
recommendation.ContainerRecommendations[0].MaxRecommended = Resources(cpu, mem)
}
// RecommenderAPIMock is a mock of RecommenderAPI
type RecommenderAPIMock struct {
mock.Mock

View File

@ -0,0 +1,149 @@
/*
Copyright 2018 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 test
import (
"time"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/poc.autoscaling.k8s.io/v1alpha1"
)
// VerticalPodAutoscalerBuilder helps building test instances of VerticalPodAutoscaler.
type VerticalPodAutoscalerBuilder interface {
WithContainer(containerName string) VerticalPodAutoscalerBuilder
WithNamespace(namespace string) VerticalPodAutoscalerBuilder
WithSelector(labelSelector string) VerticalPodAutoscalerBuilder
WithUpdateMode(updateMode vpa_types.UpdateMode) VerticalPodAutoscalerBuilder
WithCreationTimestamp(timestamp time.Time) VerticalPodAutoscalerBuilder
WithMinAllowed(cpu, memory string) VerticalPodAutoscalerBuilder
WithMaxAllowed(cpu, memory string) VerticalPodAutoscalerBuilder
WithTarget(cpu, memory string) VerticalPodAutoscalerBuilder
WithMinRecommended(cpu, memory string) VerticalPodAutoscalerBuilder
WithMaxRecommended(cpu, memory string) VerticalPodAutoscalerBuilder
Get() *vpa_types.VerticalPodAutoscaler
}
// VerticalPodAutoscaler returns a new VerticalPodAutoscalerBuilder.
func VerticalPodAutoscaler() VerticalPodAutoscalerBuilder {
return &verticalPodAutoscalerBuilder{
recommendation: Recommendation(),
namespace: "default",
}
}
type verticalPodAutoscalerBuilder struct {
containerName string
namespace string
labelSelector *metav1.LabelSelector
updatePolicy vpa_types.PodUpdatePolicy
creationTimestamp time.Time
minAllowed apiv1.ResourceList
maxAllowed apiv1.ResourceList
recommendation RecommendationBuilder
}
func (b *verticalPodAutoscalerBuilder) WithContainer(containerName string) VerticalPodAutoscalerBuilder {
c := *b
c.containerName = containerName
return &c
}
func (b *verticalPodAutoscalerBuilder) WithNamespace(namespace string) VerticalPodAutoscalerBuilder {
c := *b
c.namespace = namespace
return &c
}
func (b *verticalPodAutoscalerBuilder) WithSelector(labelSelector string) VerticalPodAutoscalerBuilder {
c := *b
if labelSelector, err := metav1.ParseToLabelSelector(labelSelector); err != nil {
panic(err)
} else {
c.labelSelector = labelSelector
}
return &c
}
func (b *verticalPodAutoscalerBuilder) WithUpdateMode(updateMode vpa_types.UpdateMode) VerticalPodAutoscalerBuilder {
c := *b
c.updatePolicy.UpdateMode = updateMode
return &c
}
func (b *verticalPodAutoscalerBuilder) WithCreationTimestamp(timestamp time.Time) VerticalPodAutoscalerBuilder {
c := *b
c.creationTimestamp = timestamp
return &c
}
func (b *verticalPodAutoscalerBuilder) WithMinAllowed(cpu, memory string) VerticalPodAutoscalerBuilder {
c := *b
c.minAllowed = Resources(cpu, memory)
return &c
}
func (b *verticalPodAutoscalerBuilder) WithMaxAllowed(cpu, memory string) VerticalPodAutoscalerBuilder {
c := *b
c.maxAllowed = Resources(cpu, memory)
return &c
}
func (b *verticalPodAutoscalerBuilder) WithTarget(cpu, memory string) VerticalPodAutoscalerBuilder {
c := *b
c.recommendation = c.recommendation.WithTarget(cpu, memory)
return &c
}
func (b *verticalPodAutoscalerBuilder) WithMinRecommended(cpu, memory string) VerticalPodAutoscalerBuilder {
c := *b
c.recommendation = c.recommendation.WithMinRecommended(cpu, memory)
return &c
}
func (b *verticalPodAutoscalerBuilder) WithMaxRecommended(cpu, memory string) VerticalPodAutoscalerBuilder {
c := *b
c.recommendation = c.recommendation.WithMaxRecommended(cpu, memory)
return &c
}
func (b *verticalPodAutoscalerBuilder) Get() *vpa_types.VerticalPodAutoscaler {
if b.containerName == "" {
panic("Must call WithContainer() before Get()")
}
resourcePolicy := vpa_types.PodResourcePolicy{ContainerPolicies: []vpa_types.ContainerResourcePolicy{{
Name: b.containerName,
MinAllowed: b.minAllowed,
MaxAllowed: b.maxAllowed,
}}}
return &vpa_types.VerticalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Namespace: b.namespace,
CreationTimestamp: metav1.NewTime(b.creationTimestamp),
},
Spec: vpa_types.VerticalPodAutoscalerSpec{
Selector: b.labelSelector,
UpdatePolicy: b.updatePolicy,
ResourcePolicy: resourcePolicy,
},
Status: vpa_types.VerticalPodAutoscalerStatus{
Recommendation: *b.recommendation.WithContainer(b.containerName).Get(),
},
}
}

View File

@ -22,7 +22,6 @@ import (
"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
vpa_types "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/poc.autoscaling.k8s.io/v1alpha1"
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test"
)
@ -42,12 +41,16 @@ func TestPodMatchesVPA(t *testing.T) {
pod := test.BuildTestPod("test-pod", containerName, "1", "100M", nil, nil)
pod.Labels = map[string]string{"app": "testingApp"}
vpa := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", selector)
vpaBuilder := test.VerticalPodAutoscaler().
WithContainer(containerName).
WithTarget("2", "200M").
WithMinAllowed("1", "100M").
WithMaxAllowed("3", "1G").
WithSelector(selector)
otherNamespaceVPA := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", selector)
otherNamespaceVPA.Namespace = "other"
otherSelectorVPA := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", "app = other")
vpa := vpaBuilder.Get()
otherNamespaceVPA := vpaBuilder.WithNamespace("other").Get()
otherSelectorVPA := vpaBuilder.WithSelector("app = other").Get()
testCases := []testCase{
{pod, vpa, true},
@ -66,12 +69,15 @@ func TestGetControllingVPAForPod(t *testing.T) {
pod := test.BuildTestPod("test-pod", containerName, "1", "100M", nil, nil)
pod.Labels = map[string]string{"app": "testingApp"}
vpaA := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", selector)
vpaA.CreationTimestamp = metav1.NewTime(time.Unix(5, 0))
vpaB := test.BuildTestVerticalPodAutoscaler(containerName, "20", "10", "30", "200M", "100M", "1G", selector)
vpaB.CreationTimestamp = metav1.NewTime(time.Unix(10, 0))
nonMatchingVPA := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", "app = other")
nonMatchingVPA.CreationTimestamp = metav1.NewTime(time.Unix(2, 0))
vpaBuilder := test.VerticalPodAutoscaler().
WithContainer(containerName).
WithTarget("2", "200M").
WithMinAllowed("1", "100M").
WithMaxAllowed("3", "1G").
WithSelector(selector)
vpaA := vpaBuilder.WithCreationTimestamp(time.Unix(5, 0)).Get()
vpaB := vpaBuilder.WithCreationTimestamp(time.Unix(10, 0)).Get()
nonMatchingVPA := vpaBuilder.WithCreationTimestamp(time.Unix(2, 0)).WithSelector("app = other").Get()
chosen := GetControllingVPAForPod(pod, []*vpa_types.VerticalPodAutoscaler{vpaB, vpaA, nonMatchingVPA})
assert.Equal(t, vpaA, chosen)

View File

@ -41,7 +41,7 @@ func TestSortPriority(t *testing.T) {
pod3 := test.BuildTestPod("POD3", containerName, "1", "", nil, nil)
pod4 := test.BuildTestPod("POD4", containerName, "3", "", nil, nil)
recommendation := test.Recommendation(containerName, "10", "")
recommendation := test.Recommendation().WithContainer(containerName).WithTarget("10", "").Get()
timestampNow := pod1.Status.StartTime.Time.Add(time.Hour * 24)
calculator.AddPod(pod1, recommendation, timestampNow)
@ -59,7 +59,7 @@ func TestSortPriorityMultiResource(t *testing.T) {
pod1 := test.BuildTestPod("POD1", containerName, "4", "60M", nil, nil)
pod2 := test.BuildTestPod("POD2", containerName, "3", "90M", nil, nil)
recommendation := test.Recommendation(containerName, "6", "100M")
recommendation := test.Recommendation().WithContainer(containerName).WithTarget("6", "100M").Get()
timestampNow := pod1.Status.StartTime.Time.Add(time.Hour * 24)
calculator.AddPod(pod1, recommendation, timestampNow)
@ -88,7 +88,7 @@ func TestSortPriorityMultiContainers(t *testing.T) {
container2 := test.BuildTestContainer(containerName2, "2", "20M")
pod2.Spec.Containers = append(pod2.Spec.Containers, container2)
recommendation := test.Recommendation(containerName, "6", "20M")
recommendation := test.Recommendation().WithContainer(containerName).WithTarget("6", "20M").Get()
cpuRec, _ := resource.ParseQuantity("4")
memRec, _ := resource.ParseQuantity("20M")
container2rec := vpa_types.RecommendedContainerResources{
@ -119,7 +119,7 @@ func TestSortPriorityResourcesDecrease(t *testing.T) {
pod2 := test.BuildTestPod("POD2", containerName, "7", "", nil, nil)
pod3 := test.BuildTestPod("POD3", containerName, "10", "", nil, nil)
recommendation := test.Recommendation(containerName, "5", "")
recommendation := test.Recommendation().WithContainer(containerName).WithTarget("5", "").Get()
timestampNow := pod1.Status.StartTime.Time.Add(time.Hour * 24)
calculator.AddPod(pod1, recommendation, timestampNow)
@ -139,7 +139,7 @@ func TestUpdateNotRequired(t *testing.T) {
pod1 := test.BuildTestPod("POD1", containerName, "4", "", nil, nil)
recommendation := test.Recommendation(containerName, "4", "")
recommendation := test.Recommendation().WithContainer(containerName).WithTarget("4", "").Get()
timestampNow := pod1.Status.StartTime.Time.Add(time.Hour * 24)
calculator.AddPod(pod1, recommendation, timestampNow)
@ -153,7 +153,7 @@ func TestUpdateRequiredOnMilliQuantities(t *testing.T) {
pod1 := test.BuildTestPod("POD1", containerName, "10m", "", nil, nil)
recommendation := test.Recommendation(containerName, "900m", "")
recommendation := test.Recommendation().WithContainer(containerName).WithTarget("900m", "").Get()
timestampNow := pod1.Status.StartTime.Time.Add(time.Hour * 24)
calculator.AddPod(pod1, recommendation, timestampNow)
@ -168,7 +168,7 @@ func TestUsePolicy(t *testing.T) {
pod1 := test.BuildTestPod("POD1", containerName, "4", "10M", nil, nil)
recommendation := test.Recommendation(containerName, "5", "5M")
recommendation := test.Recommendation().WithContainer(containerName).WithTarget("5", "5M").Get()
timestampNow := pod1.Status.StartTime.Time.Add(time.Hour * 24)
calculator.AddPod(pod1, recommendation, timestampNow)
@ -191,10 +191,11 @@ func TestUpdateLonglivedPods(t *testing.T) {
test.BuildTestPod("POD3", containerName, "7", "", nil, nil),
}
recommendation := test.Recommendation(containerName, "5", "")
// Both pods are within the recommended range.
test.AddMinRecommended(recommendation, "1", "")
test.AddMaxRecommended(recommendation, "6", "")
recommendation := test.Recommendation().WithContainer(containerName).
WithTarget("5", "").
WithMinRecommended("1", "").
WithMaxRecommended("6", "").Get()
// Pretend that the test pods started 13 hours ago.
timestampNow := pods[0].Status.StartTime.Time.Add(time.Hour * 13)
@ -218,10 +219,11 @@ func TestUpdateShortlivedPods(t *testing.T) {
test.BuildTestPod("POD3", containerName, "7", "", nil, nil),
}
recommendation := test.Recommendation(containerName, "5", "")
// Both pods are within the recommended range.
test.AddMinRecommended(recommendation, "1", "")
test.AddMaxRecommended(recommendation, "6", "")
recommendation := test.Recommendation().WithContainer(containerName).
WithTarget("5", "").
WithMinRecommended("1", "").
WithMaxRecommended("6", "").Get()
// Pretend that the test pods started 11 hours ago.
timestampNow := pods[0].Status.StartTime.Time.Add(time.Hour * 11)

View File

@ -60,7 +60,12 @@ func TestRunOnce(t *testing.T) {
podLister := &test.PodListerMock{}
podLister.On("List").Return(pods, nil)
vpaObj := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", selector)
vpaObj := test.VerticalPodAutoscaler().
WithContainer(containerName).
WithTarget("2", "200M").
WithMinAllowed("1", "100M").
WithMaxAllowed("3", "1G").
WithSelector(selector).Get()
vpaObj.Spec.UpdatePolicy.UpdateMode = vpa_types.UpdateModeAuto
vpaLister.On("List").Return([]*vpa_types.VerticalPodAutoscaler{vpaObj}, nil).Once()
@ -95,7 +100,12 @@ func TestVPAOff(t *testing.T) {
podLister := &test.PodListerMock{}
podLister.On("List").Return(pods, nil)
vpaObj := test.BuildTestVerticalPodAutoscaler(containerName, "2", "1", "3", "200M", "100M", "1G", selector)
vpaObj := test.VerticalPodAutoscaler().
WithContainer(containerName).
WithTarget("2", "200M").
WithMinAllowed("1", "100M").
WithMaxAllowed("3", "1G").
WithSelector(selector).Get()
vpaObj.Namespace = "default"
vpaObj.Spec.UpdatePolicy.UpdateMode = vpa_types.UpdateModeInitial
vpaLister.On("List").Return([]*vpa_types.VerticalPodAutoscaler{vpaObj}, nil).Once()