123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419 |
- /*
- 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 eviction
- import (
- "fmt"
- "reflect"
- "testing"
- "time"
- "k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/api/resource"
- "k8s.io/kubernetes/pkg/api/unversioned"
- statsapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats"
- "k8s.io/kubernetes/pkg/quota"
- "k8s.io/kubernetes/pkg/types"
- )
- func quantityMustParse(value string) *resource.Quantity {
- q := resource.MustParse(value)
- return &q
- }
- func TestParseThresholdConfig(t *testing.T) {
- gracePeriod, _ := time.ParseDuration("30s")
- testCases := map[string]struct {
- evictionHard string
- evictionSoft string
- evictionSoftGracePeriod string
- evictionMinReclaim string
- expectErr bool
- expectThresholds []Threshold
- }{
- "no values": {
- evictionHard: "",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: false,
- expectThresholds: []Threshold{},
- },
- "all flag values": {
- evictionHard: "memory.available<150Mi",
- evictionSoft: "memory.available<300Mi",
- evictionSoftGracePeriod: "memory.available=30s",
- evictionMinReclaim: "memory.available=0",
- expectErr: false,
- expectThresholds: []Threshold{
- {
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("150Mi"),
- },
- MinReclaim: quantityMustParse("0"),
- },
- {
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("300Mi"),
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("0"),
- },
- },
- },
- "all flag values in percentages": {
- evictionHard: "memory.available<10%",
- evictionSoft: "memory.available<30%",
- evictionSoftGracePeriod: "memory.available=30s",
- evictionMinReclaim: "memory.available=0",
- expectErr: false,
- expectThresholds: []Threshold{
- {
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.1,
- },
- MinReclaim: quantityMustParse("0"),
- },
- {
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.3,
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("0"),
- },
- },
- },
- "disk flag values": {
- evictionHard: "imagefs.available<150Mi,nodefs.available<100Mi",
- evictionSoft: "imagefs.available<300Mi,nodefs.available<200Mi",
- evictionSoftGracePeriod: "imagefs.available=30s,nodefs.available=30s",
- evictionMinReclaim: "imagefs.available=2Gi,nodefs.available=1Gi",
- expectErr: false,
- expectThresholds: []Threshold{
- {
- Signal: SignalImageFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("150Mi"),
- },
- MinReclaim: quantityMustParse("2Gi"),
- },
- {
- Signal: SignalNodeFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("100Mi"),
- },
- MinReclaim: quantityMustParse("1Gi"),
- },
- {
- Signal: SignalImageFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("300Mi"),
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("2Gi"),
- },
- {
- Signal: SignalNodeFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("200Mi"),
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("1Gi"),
- },
- },
- },
- "disk flag values in percentages": {
- evictionHard: "imagefs.available<15%,nodefs.available<10.5%",
- evictionSoft: "imagefs.available<30%,nodefs.available<20.5%",
- evictionSoftGracePeriod: "imagefs.available=30s,nodefs.available=30s",
- evictionMinReclaim: "imagefs.available=2Gi,nodefs.available=1Gi",
- expectErr: false,
- expectThresholds: []Threshold{
- {
- Signal: SignalImageFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.15,
- },
- MinReclaim: quantityMustParse("2Gi"),
- },
- {
- Signal: SignalNodeFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.105,
- },
- MinReclaim: quantityMustParse("1Gi"),
- },
- {
- Signal: SignalImageFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.3,
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("2Gi"),
- },
- {
- Signal: SignalNodeFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.205,
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("1Gi"),
- },
- },
- },
- "inode flag values": {
- evictionHard: "imagefs.inodesFree<150Mi,nodefs.inodesFree<100Mi",
- evictionSoft: "imagefs.inodesFree<300Mi,nodefs.inodesFree<200Mi",
- evictionSoftGracePeriod: "imagefs.inodesFree=30s,nodefs.inodesFree=30s",
- evictionMinReclaim: "imagefs.inodesFree=2Gi,nodefs.inodesFree=1Gi",
- expectErr: false,
- expectThresholds: []Threshold{
- {
- Signal: SignalImageFsInodesFree,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("150Mi"),
- },
- MinReclaim: quantityMustParse("2Gi"),
- },
- {
- Signal: SignalNodeFsInodesFree,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("100Mi"),
- },
- MinReclaim: quantityMustParse("1Gi"),
- },
- {
- Signal: SignalImageFsInodesFree,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("300Mi"),
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("2Gi"),
- },
- {
- Signal: SignalNodeFsInodesFree,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("200Mi"),
- },
- GracePeriod: gracePeriod,
- MinReclaim: quantityMustParse("1Gi"),
- },
- },
- },
- "invalid-signal": {
- evictionHard: "mem.available<150Mi",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "hard-signal-negative": {
- evictionHard: "memory.available<-150Mi",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "hard-signal-negative-percentage": {
- evictionHard: "memory.available<-15%",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "soft-signal-negative": {
- evictionHard: "",
- evictionSoft: "memory.available<-150Mi",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "duplicate-signal": {
- evictionHard: "memory.available<150Mi,memory.available<100Mi",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "valid-and-invalid-signal": {
- evictionHard: "memory.available<150Mi,invalid.foo<150Mi",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "soft-no-grace-period": {
- evictionHard: "",
- evictionSoft: "memory.available<150Mi",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "soft-neg-grace-period": {
- evictionHard: "",
- evictionSoft: "memory.available<150Mi",
- evictionSoftGracePeriod: "memory.available=-30s",
- evictionMinReclaim: "",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "neg-reclaim": {
- evictionHard: "",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "memory.available=-300Mi",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- "duplicate-reclaim": {
- evictionHard: "",
- evictionSoft: "",
- evictionSoftGracePeriod: "",
- evictionMinReclaim: "memory.available=-300Mi,memory.available=-100Mi",
- expectErr: true,
- expectThresholds: []Threshold{},
- },
- }
- for testName, testCase := range testCases {
- thresholds, err := ParseThresholdConfig(testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod, testCase.evictionMinReclaim)
- if testCase.expectErr != (err != nil) {
- t.Errorf("Err not as expected, test: %v, error expected: %v, actual: %v", testName, testCase.expectErr, err)
- }
- if !thresholdsEqual(testCase.expectThresholds, thresholds) {
- t.Errorf("thresholds not as expected, test: %v, expected: %v, actual: %v", testName, testCase.expectThresholds, thresholds)
- }
- }
- }
- func thresholdsEqual(expected []Threshold, actual []Threshold) bool {
- if len(expected) != len(actual) {
- return false
- }
- for _, aThreshold := range expected {
- equal := false
- for _, bThreshold := range actual {
- if thresholdEqual(aThreshold, bThreshold) {
- equal = true
- }
- }
- if !equal {
- return false
- }
- }
- for _, aThreshold := range actual {
- equal := false
- for _, bThreshold := range expected {
- if thresholdEqual(aThreshold, bThreshold) {
- equal = true
- }
- }
- if !equal {
- return false
- }
- }
- return true
- }
- func thresholdEqual(a Threshold, b Threshold) bool {
- return a.GracePeriod == b.GracePeriod &&
- a.Operator == b.Operator &&
- a.Signal == b.Signal &&
- a.MinReclaim.Cmp(*b.MinReclaim) == 0 &&
- compareThresholdValue(a.Value, b.Value)
- }
- // TestOrderedByQoS ensures we order BestEffort < Burstable < Guaranteed
- func TestOrderedByQoS(t *testing.T) {
- bestEffort := newPod("best-effort", []api.Container{
- newContainer("best-effort", newResourceList("", ""), newResourceList("", "")),
- }, nil)
- burstable := newPod("burstable", []api.Container{
- newContainer("burstable", newResourceList("100m", "100Mi"), newResourceList("200m", "200Mi")),
- }, nil)
- guaranteed := newPod("guaranteed", []api.Container{
- newContainer("guaranteed", newResourceList("200m", "200Mi"), newResourceList("200m", "200Mi")),
- }, nil)
- pods := []*api.Pod{guaranteed, burstable, bestEffort}
- orderedBy(qosComparator).Sort(pods)
- expected := []*api.Pod{bestEffort, burstable, guaranteed}
- for i := range expected {
- if pods[i] != expected[i] {
- t.Errorf("Expected pod: %s, but got: %s", expected[i].Name, pods[i].Name)
- }
- }
- }
- // TestOrderedByDisk ensures we order pods by greediest disk consumer
- func TestOrderedByDisk(t *testing.T) {
- pod1 := newPod("best-effort-high", []api.Container{
- newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod2 := newPod("best-effort-low", []api.Container{
- newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod3 := newPod("burstable-high", []api.Container{
- newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod4 := newPod("burstable-low", []api.Container{
- newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod5 := newPod("guaranteed-high", []api.Container{
- newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod6 := newPod("guaranteed-low", []api.Container{
- newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- stats := map[*api.Pod]statsapi.PodStats{
- pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 200Mi
- pod2: newPodDiskStats(pod2, resource.MustParse("100Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 300Mi
- pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 400Mi
- pod4: newPodDiskStats(pod4, resource.MustParse("300Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 450Mi
- pod5: newPodDiskStats(pod5, resource.MustParse("400Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 550Mi
- pod6: newPodDiskStats(pod6, resource.MustParse("500Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 650Mi
- }
- statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
- result, found := stats[pod]
- return result, found
- }
- pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
- orderedBy(disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceDisk)).Sort(pods)
- expected := []*api.Pod{pod6, pod5, pod4, pod3, pod2, pod1}
- for i := range expected {
- if pods[i] != expected[i] {
- t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
- }
- }
- }
- // TestOrderedByQoSDisk ensures we order pods by qos and then greediest disk consumer
- func TestOrderedByQoSDisk(t *testing.T) {
- pod1 := newPod("best-effort-high", []api.Container{
- newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod2 := newPod("best-effort-low", []api.Container{
- newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod3 := newPod("burstable-high", []api.Container{
- newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod4 := newPod("burstable-low", []api.Container{
- newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod5 := newPod("guaranteed-high", []api.Container{
- newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- pod6 := newPod("guaranteed-low", []api.Container{
- newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, []api.Volume{
- newVolume("local-volume", api.VolumeSource{
- EmptyDir: &api.EmptyDirVolumeSource{},
- }),
- })
- stats := map[*api.Pod]statsapi.PodStats{
- pod1: newPodDiskStats(pod1, resource.MustParse("50Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 200Mi
- pod2: newPodDiskStats(pod2, resource.MustParse("100Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 300Mi
- pod3: newPodDiskStats(pod3, resource.MustParse("200Mi"), resource.MustParse("150Mi"), resource.MustParse("50Mi")), // 400Mi
- pod4: newPodDiskStats(pod4, resource.MustParse("300Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 450Mi
- pod5: newPodDiskStats(pod5, resource.MustParse("400Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 550Mi
- pod6: newPodDiskStats(pod6, resource.MustParse("500Mi"), resource.MustParse("100Mi"), resource.MustParse("50Mi")), // 650Mi
- }
- statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
- result, found := stats[pod]
- return result, found
- }
- pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
- orderedBy(qosComparator, disk(statsFn, []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceDisk)).Sort(pods)
- expected := []*api.Pod{pod2, pod1, pod4, pod3, pod6, pod5}
- for i := range expected {
- if pods[i] != expected[i] {
- t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
- }
- }
- }
- // TestOrderedByMemory ensures we order pods by greediest memory consumer relative to request.
- func TestOrderedByMemory(t *testing.T) {
- pod1 := newPod("best-effort-high", []api.Container{
- newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
- }, nil)
- pod2 := newPod("best-effort-low", []api.Container{
- newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
- }, nil)
- pod3 := newPod("burstable-high", []api.Container{
- newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, nil)
- pod4 := newPod("burstable-low", []api.Container{
- newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, nil)
- pod5 := newPod("guaranteed-high", []api.Container{
- newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, nil)
- pod6 := newPod("guaranteed-low", []api.Container{
- newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, nil)
- stats := map[*api.Pod]statsapi.PodStats{
- pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
- pod2: newPodMemoryStats(pod2, resource.MustParse("300Mi")), // 300 relative to request
- pod3: newPodMemoryStats(pod3, resource.MustParse("800Mi")), // 700 relative to request
- pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")), // 200 relative to request
- pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
- pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
- }
- statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
- result, found := stats[pod]
- return result, found
- }
- pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
- orderedBy(memory(statsFn)).Sort(pods)
- expected := []*api.Pod{pod3, pod1, pod2, pod4, pod5, pod6}
- for i := range expected {
- if pods[i] != expected[i] {
- t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
- }
- }
- }
- // TestOrderedByQoSMemory ensures we order by qosComparator and then memory consumption relative to request.
- func TestOrderedByQoSMemory(t *testing.T) {
- pod1 := newPod("best-effort-high", []api.Container{
- newContainer("best-effort-high", newResourceList("", ""), newResourceList("", "")),
- }, nil)
- pod2 := newPod("best-effort-low", []api.Container{
- newContainer("best-effort-low", newResourceList("", ""), newResourceList("", "")),
- }, nil)
- pod3 := newPod("burstable-high", []api.Container{
- newContainer("burstable-high", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, nil)
- pod4 := newPod("burstable-low", []api.Container{
- newContainer("burstable-low", newResourceList("100m", "100Mi"), newResourceList("200m", "1Gi")),
- }, nil)
- pod5 := newPod("guaranteed-high", []api.Container{
- newContainer("guaranteed-high", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, nil)
- pod6 := newPod("guaranteed-low", []api.Container{
- newContainer("guaranteed-low", newResourceList("100m", "1Gi"), newResourceList("100m", "1Gi")),
- }, nil)
- stats := map[*api.Pod]statsapi.PodStats{
- pod1: newPodMemoryStats(pod1, resource.MustParse("500Mi")), // 500 relative to request
- pod2: newPodMemoryStats(pod2, resource.MustParse("50Mi")), // 50 relative to request
- pod3: newPodMemoryStats(pod3, resource.MustParse("50Mi")), // -50 relative to request
- pod4: newPodMemoryStats(pod4, resource.MustParse("300Mi")), // 200 relative to request
- pod5: newPodMemoryStats(pod5, resource.MustParse("800Mi")), // -200 relative to request
- pod6: newPodMemoryStats(pod6, resource.MustParse("200Mi")), // -800 relative to request
- }
- statsFn := func(pod *api.Pod) (statsapi.PodStats, bool) {
- result, found := stats[pod]
- return result, found
- }
- pods := []*api.Pod{pod1, pod2, pod3, pod4, pod5, pod6}
- expected := []*api.Pod{pod1, pod2, pod4, pod3, pod5, pod6}
- orderedBy(qosComparator, memory(statsFn)).Sort(pods)
- for i := range expected {
- if pods[i] != expected[i] {
- t.Errorf("Expected pod[%d]: %s, but got: %s", i, expected[i].Name, pods[i].Name)
- }
- }
- }
- type fakeSummaryProvider struct {
- result *statsapi.Summary
- }
- func (f *fakeSummaryProvider) Get() (*statsapi.Summary, error) {
- return f.result, nil
- }
- // newPodStats returns a pod stat where each container is using the specified working set
- // each pod must have a Name, UID, Namespace
- func newPodStats(pod *api.Pod, containerWorkingSetBytes int64) statsapi.PodStats {
- result := statsapi.PodStats{
- PodRef: statsapi.PodReference{
- Name: pod.Name,
- Namespace: pod.Namespace,
- UID: string(pod.UID),
- },
- }
- val := uint64(containerWorkingSetBytes)
- for range pod.Spec.Containers {
- result.Containers = append(result.Containers, statsapi.ContainerStats{
- Memory: &statsapi.MemoryStats{
- WorkingSetBytes: &val,
- },
- })
- }
- return result
- }
- func TestMakeSignalObservations(t *testing.T) {
- podMaker := func(name, namespace, uid string, numContainers int) *api.Pod {
- pod := &api.Pod{}
- pod.Name = name
- pod.Namespace = namespace
- pod.UID = types.UID(uid)
- pod.Spec = api.PodSpec{}
- for i := 0; i < numContainers; i++ {
- pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
- Name: fmt.Sprintf("ctr%v", i),
- })
- }
- return pod
- }
- nodeAvailableBytes := uint64(1024 * 1024 * 1024)
- nodeWorkingSetBytes := uint64(1024 * 1024 * 1024)
- imageFsAvailableBytes := uint64(1024 * 1024)
- imageFsCapacityBytes := uint64(1024 * 1024 * 2)
- nodeFsAvailableBytes := uint64(1024)
- nodeFsCapacityBytes := uint64(1024 * 2)
- imageFsInodesFree := uint64(1024)
- imageFsInodes := uint64(1024 * 1024)
- nodeFsInodesFree := uint64(1024)
- nodeFsInodes := uint64(1024 * 1024)
- fakeStats := &statsapi.Summary{
- Node: statsapi.NodeStats{
- Memory: &statsapi.MemoryStats{
- AvailableBytes: &nodeAvailableBytes,
- WorkingSetBytes: &nodeWorkingSetBytes,
- },
- Runtime: &statsapi.RuntimeStats{
- ImageFs: &statsapi.FsStats{
- AvailableBytes: &imageFsAvailableBytes,
- CapacityBytes: &imageFsCapacityBytes,
- InodesFree: &imageFsInodesFree,
- Inodes: &imageFsInodes,
- },
- },
- Fs: &statsapi.FsStats{
- AvailableBytes: &nodeFsAvailableBytes,
- CapacityBytes: &nodeFsCapacityBytes,
- InodesFree: &nodeFsInodesFree,
- Inodes: &nodeFsInodes,
- },
- },
- Pods: []statsapi.PodStats{},
- }
- provider := &fakeSummaryProvider{
- result: fakeStats,
- }
- pods := []*api.Pod{
- podMaker("pod1", "ns1", "uuid1", 1),
- podMaker("pod1", "ns2", "uuid2", 1),
- podMaker("pod3", "ns3", "uuid3", 1),
- }
- containerWorkingSetBytes := int64(1024 * 1024)
- for _, pod := range pods {
- fakeStats.Pods = append(fakeStats.Pods, newPodStats(pod, containerWorkingSetBytes))
- }
- actualObservations, statsFunc, err := makeSignalObservations(provider)
- if err != nil {
- t.Errorf("Unexpected err: %v", err)
- }
- memQuantity, found := actualObservations[SignalMemoryAvailable]
- if !found {
- t.Errorf("Expected available memory observation: %v", err)
- }
- if expectedBytes := int64(nodeAvailableBytes); memQuantity.available.Value() != expectedBytes {
- t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.available.Value())
- }
- if expectedBytes := int64(nodeWorkingSetBytes + nodeAvailableBytes); memQuantity.capacity.Value() != expectedBytes {
- t.Errorf("Expected %v, actual: %v", expectedBytes, memQuantity.capacity.Value())
- }
- nodeFsQuantity, found := actualObservations[SignalNodeFsAvailable]
- if !found {
- t.Errorf("Expected available nodefs observation: %v", err)
- }
- if expectedBytes := int64(nodeFsAvailableBytes); nodeFsQuantity.available.Value() != expectedBytes {
- t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.available.Value())
- }
- if expectedBytes := int64(nodeFsCapacityBytes); nodeFsQuantity.capacity.Value() != expectedBytes {
- t.Errorf("Expected %v, actual: %v", expectedBytes, nodeFsQuantity.capacity.Value())
- }
- nodeFsInodesQuantity, found := actualObservations[SignalNodeFsInodesFree]
- if !found {
- t.Errorf("Expected inodes free nodefs observation: %v", err)
- }
- if expected := int64(nodeFsInodesFree); nodeFsInodesQuantity.available.Value() != expected {
- t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.available.Value())
- }
- if expected := int64(nodeFsInodes); nodeFsInodesQuantity.capacity.Value() != expected {
- t.Errorf("Expected %v, actual: %v", expected, nodeFsInodesQuantity.capacity.Value())
- }
- imageFsQuantity, found := actualObservations[SignalImageFsAvailable]
- if !found {
- t.Errorf("Expected available imagefs observation: %v", err)
- }
- if expectedBytes := int64(imageFsAvailableBytes); imageFsQuantity.available.Value() != expectedBytes {
- t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.available.Value())
- }
- if expectedBytes := int64(imageFsCapacityBytes); imageFsQuantity.capacity.Value() != expectedBytes {
- t.Errorf("Expected %v, actual: %v", expectedBytes, imageFsQuantity.capacity.Value())
- }
- imageFsInodesQuantity, found := actualObservations[SignalImageFsInodesFree]
- if !found {
- t.Errorf("Expected inodes free imagefs observation: %v", err)
- }
- if expected := int64(imageFsInodesFree); imageFsInodesQuantity.available.Value() != expected {
- t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.available.Value())
- }
- if expected := int64(imageFsInodes); imageFsInodesQuantity.capacity.Value() != expected {
- t.Errorf("Expected %v, actual: %v", expected, imageFsInodesQuantity.capacity.Value())
- }
- for _, pod := range pods {
- podStats, found := statsFunc(pod)
- if !found {
- t.Errorf("Pod stats were not found for pod %v", pod.UID)
- }
- for _, container := range podStats.Containers {
- actual := int64(*container.Memory.WorkingSetBytes)
- if containerWorkingSetBytes != actual {
- t.Errorf("Container working set expected %v, actual: %v", containerWorkingSetBytes, actual)
- }
- }
- }
- }
- func TestThresholdsMet(t *testing.T) {
- hardThreshold := Threshold{
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("1Gi"),
- },
- MinReclaim: quantityMustParse("500Mi"),
- }
- testCases := map[string]struct {
- enforceMinReclaim bool
- thresholds []Threshold
- observations signalObservations
- result []Threshold
- }{
- "empty": {
- enforceMinReclaim: false,
- thresholds: []Threshold{},
- observations: signalObservations{},
- result: []Threshold{},
- },
- "threshold-met-memory": {
- enforceMinReclaim: false,
- thresholds: []Threshold{hardThreshold},
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("500Mi"),
- },
- },
- result: []Threshold{hardThreshold},
- },
- "threshold-not-met": {
- enforceMinReclaim: false,
- thresholds: []Threshold{hardThreshold},
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("2Gi"),
- },
- },
- result: []Threshold{},
- },
- "threshold-met-with-min-reclaim": {
- enforceMinReclaim: true,
- thresholds: []Threshold{hardThreshold},
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("1.05Gi"),
- },
- },
- result: []Threshold{hardThreshold},
- },
- "threshold-not-met-with-min-reclaim": {
- enforceMinReclaim: true,
- thresholds: []Threshold{hardThreshold},
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("2Gi"),
- },
- },
- result: []Threshold{},
- },
- }
- for testName, testCase := range testCases {
- actual := thresholdsMet(testCase.thresholds, testCase.observations, testCase.enforceMinReclaim)
- if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestPercentageThresholdsMet(t *testing.T) {
- specifiecThresholds := []Threshold{
- {
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.2,
- },
- },
- {
- Signal: SignalNodeFsAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Percentage: 0.3,
- },
- },
- }
- testCases := map[string]struct {
- thresholds []Threshold
- observations signalObservations
- result []Threshold
- }{
- "BothMet": {
- thresholds: specifiecThresholds,
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("100Mi"),
- capacity: quantityMustParse("1000Mi"),
- },
- SignalNodeFsAvailable: signalObservation{
- available: quantityMustParse("100Gi"),
- capacity: quantityMustParse("1000Gi"),
- },
- },
- result: specifiecThresholds,
- },
- "NoneMet": {
- thresholds: specifiecThresholds,
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("300Mi"),
- capacity: quantityMustParse("1000Mi"),
- },
- SignalNodeFsAvailable: signalObservation{
- available: quantityMustParse("400Gi"),
- capacity: quantityMustParse("1000Gi"),
- },
- },
- result: []Threshold{},
- },
- "DiskMet": {
- thresholds: specifiecThresholds,
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("300Mi"),
- capacity: quantityMustParse("1000Mi"),
- },
- SignalNodeFsAvailable: signalObservation{
- available: quantityMustParse("100Gi"),
- capacity: quantityMustParse("1000Gi"),
- },
- },
- result: []Threshold{specifiecThresholds[1]},
- },
- "MemoryMet": {
- thresholds: specifiecThresholds,
- observations: signalObservations{
- SignalMemoryAvailable: signalObservation{
- available: quantityMustParse("100Mi"),
- capacity: quantityMustParse("1000Mi"),
- },
- SignalNodeFsAvailable: signalObservation{
- available: quantityMustParse("400Gi"),
- capacity: quantityMustParse("1000Gi"),
- },
- },
- result: []Threshold{specifiecThresholds[0]},
- },
- }
- for testName, testCase := range testCases {
- actual := thresholdsMet(testCase.thresholds, testCase.observations, false)
- if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestThresholdsFirstObservedAt(t *testing.T) {
- hardThreshold := Threshold{
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("1Gi"),
- },
- }
- now := unversioned.Now()
- oldTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute))
- testCases := map[string]struct {
- thresholds []Threshold
- lastObservedAt thresholdsObservedAt
- now time.Time
- result thresholdsObservedAt
- }{
- "empty": {
- thresholds: []Threshold{},
- lastObservedAt: thresholdsObservedAt{},
- now: now.Time,
- result: thresholdsObservedAt{},
- },
- "no-previous-observation": {
- thresholds: []Threshold{hardThreshold},
- lastObservedAt: thresholdsObservedAt{},
- now: now.Time,
- result: thresholdsObservedAt{
- hardThreshold: now.Time,
- },
- },
- "previous-observation": {
- thresholds: []Threshold{hardThreshold},
- lastObservedAt: thresholdsObservedAt{
- hardThreshold: oldTime.Time,
- },
- now: now.Time,
- result: thresholdsObservedAt{
- hardThreshold: oldTime.Time,
- },
- },
- }
- for testName, testCase := range testCases {
- actual := thresholdsFirstObservedAt(testCase.thresholds, testCase.lastObservedAt, testCase.now)
- if !reflect.DeepEqual(actual, testCase.result) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestThresholdsMetGracePeriod(t *testing.T) {
- now := unversioned.Now()
- hardThreshold := Threshold{
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("1Gi"),
- },
- }
- softThreshold := Threshold{
- Signal: SignalMemoryAvailable,
- Operator: OpLessThan,
- Value: ThresholdValue{
- Quantity: quantityMustParse("2Gi"),
- },
- GracePeriod: 1 * time.Minute,
- }
- oldTime := unversioned.NewTime(now.Time.Add(-2 * time.Minute))
- testCases := map[string]struct {
- observedAt thresholdsObservedAt
- now time.Time
- result []Threshold
- }{
- "empty": {
- observedAt: thresholdsObservedAt{},
- now: now.Time,
- result: []Threshold{},
- },
- "hard-threshold-met": {
- observedAt: thresholdsObservedAt{
- hardThreshold: now.Time,
- },
- now: now.Time,
- result: []Threshold{hardThreshold},
- },
- "soft-threshold-not-met": {
- observedAt: thresholdsObservedAt{
- softThreshold: now.Time,
- },
- now: now.Time,
- result: []Threshold{},
- },
- "soft-threshold-met": {
- observedAt: thresholdsObservedAt{
- softThreshold: oldTime.Time,
- },
- now: now.Time,
- result: []Threshold{softThreshold},
- },
- }
- for testName, testCase := range testCases {
- actual := thresholdsMetGracePeriod(testCase.observedAt, now.Time)
- if !thresholdList(actual).Equal(thresholdList(testCase.result)) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestNodeConditions(t *testing.T) {
- testCases := map[string]struct {
- inputs []Threshold
- result []api.NodeConditionType
- }{
- "empty-list": {
- inputs: []Threshold{},
- result: []api.NodeConditionType{},
- },
- "memory.available": {
- inputs: []Threshold{
- {Signal: SignalMemoryAvailable},
- },
- result: []api.NodeConditionType{api.NodeMemoryPressure},
- },
- }
- for testName, testCase := range testCases {
- actual := nodeConditions(testCase.inputs)
- if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestNodeConditionsLastObservedAt(t *testing.T) {
- now := unversioned.Now()
- oldTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute))
- testCases := map[string]struct {
- nodeConditions []api.NodeConditionType
- lastObservedAt nodeConditionsObservedAt
- now time.Time
- result nodeConditionsObservedAt
- }{
- "no-previous-observation": {
- nodeConditions: []api.NodeConditionType{api.NodeMemoryPressure},
- lastObservedAt: nodeConditionsObservedAt{},
- now: now.Time,
- result: nodeConditionsObservedAt{
- api.NodeMemoryPressure: now.Time,
- },
- },
- "previous-observation": {
- nodeConditions: []api.NodeConditionType{api.NodeMemoryPressure},
- lastObservedAt: nodeConditionsObservedAt{
- api.NodeMemoryPressure: oldTime.Time,
- },
- now: now.Time,
- result: nodeConditionsObservedAt{
- api.NodeMemoryPressure: now.Time,
- },
- },
- "old-observation": {
- nodeConditions: []api.NodeConditionType{},
- lastObservedAt: nodeConditionsObservedAt{
- api.NodeMemoryPressure: oldTime.Time,
- },
- now: now.Time,
- result: nodeConditionsObservedAt{
- api.NodeMemoryPressure: oldTime.Time,
- },
- },
- }
- for testName, testCase := range testCases {
- actual := nodeConditionsLastObservedAt(testCase.nodeConditions, testCase.lastObservedAt, testCase.now)
- if !reflect.DeepEqual(actual, testCase.result) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestNodeConditionsObservedSince(t *testing.T) {
- now := unversioned.Now()
- observedTime := unversioned.NewTime(now.Time.Add(-1 * time.Minute))
- testCases := map[string]struct {
- observedAt nodeConditionsObservedAt
- period time.Duration
- now time.Time
- result []api.NodeConditionType
- }{
- "in-period": {
- observedAt: nodeConditionsObservedAt{
- api.NodeMemoryPressure: observedTime.Time,
- },
- period: 2 * time.Minute,
- now: now.Time,
- result: []api.NodeConditionType{api.NodeMemoryPressure},
- },
- "out-of-period": {
- observedAt: nodeConditionsObservedAt{
- api.NodeMemoryPressure: observedTime.Time,
- },
- period: 30 * time.Second,
- now: now.Time,
- result: []api.NodeConditionType{},
- },
- }
- for testName, testCase := range testCases {
- actual := nodeConditionsObservedSince(testCase.observedAt, testCase.period, testCase.now)
- if !nodeConditionList(actual).Equal(nodeConditionList(testCase.result)) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestHasNodeConditions(t *testing.T) {
- testCases := map[string]struct {
- inputs []api.NodeConditionType
- item api.NodeConditionType
- result bool
- }{
- "has-condition": {
- inputs: []api.NodeConditionType{api.NodeReady, api.NodeOutOfDisk, api.NodeMemoryPressure},
- item: api.NodeMemoryPressure,
- result: true,
- },
- "does-not-have-condition": {
- inputs: []api.NodeConditionType{api.NodeReady, api.NodeOutOfDisk},
- item: api.NodeMemoryPressure,
- result: false,
- },
- }
- for testName, testCase := range testCases {
- if actual := hasNodeCondition(testCase.inputs, testCase.item); actual != testCase.result {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, testCase.result, actual)
- }
- }
- }
- func TestGetStarvedResources(t *testing.T) {
- testCases := map[string]struct {
- inputs []Threshold
- result []api.ResourceName
- }{
- "memory.available": {
- inputs: []Threshold{
- {Signal: SignalMemoryAvailable},
- },
- result: []api.ResourceName{api.ResourceMemory},
- },
- "imagefs.available": {
- inputs: []Threshold{
- {Signal: SignalImageFsAvailable},
- },
- result: []api.ResourceName{resourceImageFs},
- },
- "nodefs.available": {
- inputs: []Threshold{
- {Signal: SignalNodeFsAvailable},
- },
- result: []api.ResourceName{resourceNodeFs},
- },
- }
- for testName, testCase := range testCases {
- actual := getStarvedResources(testCase.inputs)
- actualSet := quota.ToSet(actual)
- expectedSet := quota.ToSet(testCase.result)
- if !actualSet.Equal(expectedSet) {
- t.Errorf("Test case: %s, expected: %v, actual: %v", testName, expectedSet, actualSet)
- }
- }
- }
- func testParsePercentage(t *testing.T) {
- testCases := map[string]struct {
- hasError bool
- value float32
- }{
- "blah": {
- hasError: true,
- },
- "25.5%": {
- value: 0.255,
- },
- "foo%": {
- hasError: true,
- },
- "12%345": {
- hasError: true,
- },
- }
- for input, expected := range testCases {
- value, err := parsePercentage(input)
- if (err != nil) != expected.hasError {
- t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.hasError, err != nil)
- }
- if value != expected.value {
- t.Errorf("Test case: %s, expected: %v, actual: %v", input, expected.value, value)
- }
- }
- }
- func testCompareThresholdValue(t *testing.T) {
- testCases := []struct {
- a, b ThresholdValue
- equal bool
- }{
- {
- a: ThresholdValue{
- Quantity: resource.NewQuantity(123, resource.BinarySI),
- },
- b: ThresholdValue{
- Quantity: resource.NewQuantity(123, resource.BinarySI),
- },
- equal: true,
- },
- {
- a: ThresholdValue{
- Quantity: resource.NewQuantity(123, resource.BinarySI),
- },
- b: ThresholdValue{
- Quantity: resource.NewQuantity(456, resource.BinarySI),
- },
- equal: false,
- },
- {
- a: ThresholdValue{
- Quantity: resource.NewQuantity(123, resource.BinarySI),
- },
- b: ThresholdValue{
- Percentage: 0.1,
- },
- equal: false,
- },
- {
- a: ThresholdValue{
- Percentage: 0.1,
- },
- b: ThresholdValue{
- Percentage: 0.1,
- },
- equal: true,
- },
- {
- a: ThresholdValue{
- Percentage: 0.2,
- },
- b: ThresholdValue{
- Percentage: 0.1,
- },
- equal: false,
- },
- }
- for i, testCase := range testCases {
- if compareThresholdValue(testCase.a, testCase.b) != testCase.equal ||
- compareThresholdValue(testCase.b, testCase.a) != testCase.equal {
- t.Errorf("Test case: %v failed", i)
- }
- }
- }
- // newPodInodeStats returns stats with specified usage amounts.
- // TODO: in future, this should take a value for inodesUsed per container.
- func newPodInodeStats(pod *api.Pod) statsapi.PodStats {
- result := statsapi.PodStats{
- PodRef: statsapi.PodReference{
- Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
- },
- }
- for range pod.Spec.Containers {
- result.Containers = append(result.Containers, statsapi.ContainerStats{
- Rootfs: &statsapi.FsStats{},
- })
- }
- return result
- }
- // newPodDiskStats returns stats with specified usage amounts.
- func newPodDiskStats(pod *api.Pod, rootFsUsed, logsUsed, perLocalVolumeUsed resource.Quantity) statsapi.PodStats {
- result := statsapi.PodStats{
- PodRef: statsapi.PodReference{
- Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
- },
- }
- rootFsUsedBytes := uint64(rootFsUsed.Value())
- logsUsedBytes := uint64(logsUsed.Value())
- for range pod.Spec.Containers {
- result.Containers = append(result.Containers, statsapi.ContainerStats{
- Rootfs: &statsapi.FsStats{
- UsedBytes: &rootFsUsedBytes,
- },
- Logs: &statsapi.FsStats{
- UsedBytes: &logsUsedBytes,
- },
- })
- }
- perLocalVolumeUsedBytes := uint64(perLocalVolumeUsed.Value())
- for _, volumeName := range localVolumeNames(pod) {
- result.VolumeStats = append(result.VolumeStats, statsapi.VolumeStats{
- Name: volumeName,
- FsStats: statsapi.FsStats{
- UsedBytes: &perLocalVolumeUsedBytes,
- },
- })
- }
- return result
- }
- func newPodMemoryStats(pod *api.Pod, workingSet resource.Quantity) statsapi.PodStats {
- result := statsapi.PodStats{
- PodRef: statsapi.PodReference{
- Name: pod.Name, Namespace: pod.Namespace, UID: string(pod.UID),
- },
- }
- for range pod.Spec.Containers {
- workingSetBytes := uint64(workingSet.Value())
- result.Containers = append(result.Containers, statsapi.ContainerStats{
- Memory: &statsapi.MemoryStats{
- WorkingSetBytes: &workingSetBytes,
- },
- })
- }
- return result
- }
- func newResourceList(cpu, memory string) api.ResourceList {
- res := api.ResourceList{}
- if cpu != "" {
- res[api.ResourceCPU] = resource.MustParse(cpu)
- }
- if memory != "" {
- res[api.ResourceMemory] = resource.MustParse(memory)
- }
- return res
- }
- func newResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
- res := api.ResourceRequirements{}
- res.Requests = requests
- res.Limits = limits
- return res
- }
- func newContainer(name string, requests api.ResourceList, limits api.ResourceList) api.Container {
- return api.Container{
- Name: name,
- Resources: newResourceRequirements(requests, limits),
- }
- }
- func newVolume(name string, volumeSource api.VolumeSource) api.Volume {
- return api.Volume{
- Name: name,
- VolumeSource: volumeSource,
- }
- }
- func newPod(name string, containers []api.Container, volumes []api.Volume) *api.Pod {
- return &api.Pod{
- ObjectMeta: api.ObjectMeta{
- Name: name,
- },
- Spec: api.PodSpec{
- Containers: containers,
- Volumes: volumes,
- },
- }
- }
- // nodeConditionList is a simple alias to support equality checking independent of order
- type nodeConditionList []api.NodeConditionType
- // Equal adds the ability to check equality between two lists of node conditions.
- func (s1 nodeConditionList) Equal(s2 nodeConditionList) bool {
- if len(s1) != len(s2) {
- return false
- }
- for _, item := range s1 {
- if !hasNodeCondition(s2, item) {
- return false
- }
- }
- return true
- }
- // thresholdList is a simple alias to support equality checking independent of order
- type thresholdList []Threshold
- // Equal adds the ability to check equality between two lists of node conditions.
- func (s1 thresholdList) Equal(s2 thresholdList) bool {
- if len(s1) != len(s2) {
- return false
- }
- for _, item := range s1 {
- if !hasThreshold(s2, item) {
- return false
- }
- }
- return true
- }
|