1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720 |
- /*
- 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 nodestatus
- import (
- "errors"
- "fmt"
- "net"
- "sort"
- "strconv"
- "testing"
- "time"
- cadvisorapiv1 "github.com/google/cadvisor/info/v1"
- "k8s.io/api/core/v1"
- apiequality "k8s.io/apimachinery/pkg/api/equality"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/diff"
- "k8s.io/apimachinery/pkg/util/rand"
- "k8s.io/apimachinery/pkg/util/uuid"
- fakecloud "k8s.io/cloud-provider/fake"
- "k8s.io/kubernetes/pkg/kubelet/cm"
- kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
- kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
- "k8s.io/kubernetes/pkg/kubelet/events"
- "k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
- "k8s.io/kubernetes/pkg/version"
- "k8s.io/kubernetes/pkg/volume"
- volumetest "k8s.io/kubernetes/pkg/volume/testing"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- const (
- testKubeletHostname = "127.0.0.1"
- )
- // TODO(mtaufen): below is ported from the old kubelet_node_status_test.go code, potentially add more test coverage for NodeAddress setter in future
- func TestNodeAddress(t *testing.T) {
- cases := []struct {
- name string
- hostnameOverride bool
- nodeIP net.IP
- nodeAddresses []v1.NodeAddress
- expectedAddresses []v1.NodeAddress
- shouldError bool
- }{
- {
- name: "A single InternalIP",
- nodeIP: net.ParseIP("10.1.1.1"),
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- shouldError: false,
- },
- {
- name: "NodeIP is external",
- nodeIP: net.ParseIP("55.55.55.55"),
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- shouldError: false,
- },
- {
- // Accommodating #45201 and #49202
- name: "InternalIP and ExternalIP are the same",
- nodeIP: net.ParseIP("55.55.55.55"),
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "44.44.44.44"},
- {Type: v1.NodeExternalIP, Address: "44.44.44.44"},
- {Type: v1.NodeInternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- shouldError: false,
- },
- {
- name: "An Internal/ExternalIP, an Internal/ExternalDNS",
- nodeIP: net.ParseIP("10.1.1.1"),
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeInternalDNS, Address: "ip-10-1-1-1.us-west-2.compute.internal"},
- {Type: v1.NodeExternalDNS, Address: "ec2-55-55-55-55.us-west-2.compute.amazonaws.com"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeInternalDNS, Address: "ip-10-1-1-1.us-west-2.compute.internal"},
- {Type: v1.NodeExternalDNS, Address: "ec2-55-55-55-55.us-west-2.compute.amazonaws.com"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- shouldError: false,
- },
- {
- name: "An Internal with multiple internal IPs",
- nodeIP: net.ParseIP("10.1.1.1"),
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeInternalIP, Address: "10.2.2.2"},
- {Type: v1.NodeInternalIP, Address: "10.3.3.3"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- shouldError: false,
- },
- {
- name: "An InternalIP that isn't valid: should error",
- nodeIP: net.ParseIP("10.2.2.2"),
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname},
- },
- expectedAddresses: nil,
- shouldError: true,
- },
- {
- name: "no cloud reported hostnames",
- nodeAddresses: []v1.NodeAddress{},
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeHostName, Address: testKubeletHostname}, // detected hostname is auto-added in the absence of cloud-reported hostnames
- },
- shouldError: false,
- },
- {
- name: "cloud reports hostname, no override",
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: "cloud-host"},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: "cloud-host"}, // cloud-reported hostname wins over detected hostname
- },
- shouldError: false,
- },
- {
- name: "cloud reports hostname, overridden",
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeHostName, Address: "cloud-host"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeHostName, Address: testKubeletHostname}, // hostname-override wins over cloud-reported hostname
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- },
- hostnameOverride: true,
- shouldError: false,
- },
- {
- name: "cloud doesn't report hostname, no override, detected hostname mismatch",
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- // detected hostname is not auto-added if it doesn't match any cloud-reported addresses
- },
- shouldError: false,
- },
- {
- name: "cloud doesn't report hostname, no override, detected hostname match",
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeExternalDNS, Address: testKubeletHostname}, // cloud-reported address value matches detected hostname
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeExternalDNS, Address: testKubeletHostname},
- {Type: v1.NodeHostName, Address: testKubeletHostname}, // detected hostname gets auto-added
- },
- shouldError: false,
- },
- {
- name: "cloud doesn't report hostname, hostname override, hostname mismatch",
- nodeAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- },
- expectedAddresses: []v1.NodeAddress{
- {Type: v1.NodeInternalIP, Address: "10.1.1.1"},
- {Type: v1.NodeExternalIP, Address: "55.55.55.55"},
- {Type: v1.NodeHostName, Address: testKubeletHostname}, // overridden hostname gets auto-added
- },
- hostnameOverride: true,
- shouldError: false,
- },
- }
- for _, testCase := range cases {
- t.Run(testCase.name, func(t *testing.T) {
- // testCase setup
- existingNode := &v1.Node{
- ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname, Annotations: make(map[string]string)},
- Spec: v1.NodeSpec{},
- }
- nodeIP := testCase.nodeIP
- nodeIPValidator := func(nodeIP net.IP) error {
- return nil
- }
- hostname := testKubeletHostname
- externalCloudProvider := false
- cloud := &fakecloud.Cloud{
- Addresses: testCase.nodeAddresses,
- Err: nil,
- }
- nodeAddressesFunc := func() ([]v1.NodeAddress, error) {
- return testCase.nodeAddresses, nil
- }
- // construct setter
- setter := NodeAddress(nodeIP,
- nodeIPValidator,
- hostname,
- testCase.hostnameOverride,
- externalCloudProvider,
- cloud,
- nodeAddressesFunc)
- // call setter on existing node
- err := setter(existingNode)
- if err != nil && !testCase.shouldError {
- t.Fatalf("unexpected error: %v", err)
- } else if err != nil && testCase.shouldError {
- // expected an error, and got one, so just return early here
- return
- }
- // Sort both sets for consistent equality
- sortNodeAddresses(testCase.expectedAddresses)
- sortNodeAddresses(existingNode.Status.Addresses)
- assert.True(t, apiequality.Semantic.DeepEqual(testCase.expectedAddresses, existingNode.Status.Addresses),
- "Diff: %s", diff.ObjectDiff(testCase.expectedAddresses, existingNode.Status.Addresses))
- })
- }
- }
- func TestMachineInfo(t *testing.T) {
- const nodeName = "test-node"
- type dprc struct {
- capacity v1.ResourceList
- allocatable v1.ResourceList
- inactive []string
- }
- cases := []struct {
- desc string
- node *v1.Node
- maxPods int
- podsPerCore int
- machineInfo *cadvisorapiv1.MachineInfo
- machineInfoError error
- capacity v1.ResourceList
- devicePluginResourceCapacity dprc
- nodeAllocatableReservation v1.ResourceList
- expectNode *v1.Node
- expectEvents []testEvent
- }{
- {
- desc: "machine identifiers, basic capacity and allocatable",
- node: &v1.Node{},
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- MachineID: "MachineID",
- SystemUUID: "SystemUUID",
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- NodeInfo: v1.NodeSystemInfo{
- MachineID: "MachineID",
- SystemUUID: "SystemUUID",
- },
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "podsPerCore greater than zero, but less than maxPods/cores",
- node: &v1.Node{},
- maxPods: 10,
- podsPerCore: 4,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "podsPerCore greater than maxPods/cores",
- node: &v1.Node{},
- maxPods: 10,
- podsPerCore: 6,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "allocatable should equal capacity minus reservations",
- node: &v1.Node{},
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- nodeAllocatableReservation: v1.ResourceList{
- // reserve 1 unit for each resource
- v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
- v1.ResourceEphemeralStorage: *resource.NewQuantity(1, resource.BinarySI),
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(1999, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1023, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(109, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "allocatable memory does not double-count hugepages reservations",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- // it's impossible on any real system to reserve 1 byte,
- // but we just need to test that the setter does the math
- v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1, resource.BinarySI),
- },
- },
- },
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- // memory has 1-unit difference for hugepages reservation
- v1.ResourceMemory: *resource.NewQuantity(1023, resource.BinarySI),
- v1.ResourceHugePagesPrefix + "test": *resource.NewQuantity(1, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "negative capacity resources should be set to 0 in allocatable",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- "negative-resource": *resource.NewQuantity(-1, resource.BinarySI),
- },
- },
- },
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- "negative-resource": *resource.NewQuantity(-1, resource.BinarySI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- "negative-resource": *resource.NewQuantity(0, resource.BinarySI),
- },
- },
- },
- },
- {
- desc: "ephemeral storage is reflected in capacity and allocatable",
- node: &v1.Node{},
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- capacity: v1.ResourceList{
- v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
- },
- },
- },
- },
- {
- desc: "device plugin resources are reflected in capacity and allocatable",
- node: &v1.Node{},
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- devicePluginResourceCapacity: dprc{
- capacity: v1.ResourceList{
- "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
- },
- allocatable: v1.ResourceList{
- "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
- },
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- "device-plugin": *resource.NewQuantity(1, resource.BinarySI),
- },
- },
- },
- },
- {
- desc: "inactive device plugin resources should have their capacity set to 0",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- "inactive": *resource.NewQuantity(1, resource.BinarySI),
- },
- },
- },
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- devicePluginResourceCapacity: dprc{
- inactive: []string{"inactive"},
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- "inactive": *resource.NewQuantity(0, resource.BinarySI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- "inactive": *resource.NewQuantity(0, resource.BinarySI),
- },
- },
- },
- },
- {
- desc: "extended resources not present in capacity are removed from allocatable",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Allocatable: v1.ResourceList{
- "example.com/extended": *resource.NewQuantity(1, resource.BinarySI),
- },
- },
- },
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "on failure to get machine info, allocatable and capacity for memory and cpu are set to 0, pods to maxPods",
- node: &v1.Node{},
- maxPods: 110,
- // podsPerCore is not accounted for when getting machine info fails
- podsPerCore: 1,
- machineInfoError: fmt.Errorf("foo"),
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI),
- v1.ResourceMemory: resource.MustParse("0Gi"),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(0, resource.DecimalSI),
- v1.ResourceMemory: resource.MustParse("0Gi"),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "node reboot event is recorded",
- node: &v1.Node{
- Status: v1.NodeStatus{
- NodeInfo: v1.NodeSystemInfo{
- BootID: "foo",
- },
- },
- },
- maxPods: 110,
- machineInfo: &cadvisorapiv1.MachineInfo{
- BootID: "bar",
- NumCores: 2,
- MemoryCapacity: 1024,
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- NodeInfo: v1.NodeSystemInfo{
- BootID: "bar",
- },
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(1024, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI),
- },
- },
- },
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeWarning,
- event: events.NodeRebooted,
- message: fmt.Sprintf("Node %s has been rebooted, boot id: %s", nodeName, "bar"),
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- machineInfoFunc := func() (*cadvisorapiv1.MachineInfo, error) {
- return tc.machineInfo, tc.machineInfoError
- }
- capacityFunc := func() v1.ResourceList {
- return tc.capacity
- }
- devicePluginResourceCapacityFunc := func() (v1.ResourceList, v1.ResourceList, []string) {
- c := tc.devicePluginResourceCapacity
- return c.capacity, c.allocatable, c.inactive
- }
- nodeAllocatableReservationFunc := func() v1.ResourceList {
- return tc.nodeAllocatableReservation
- }
- events := []testEvent{}
- recordEventFunc := func(eventType, event, message string) {
- events = append(events, testEvent{
- eventType: eventType,
- event: event,
- message: message,
- })
- }
- // construct setter
- setter := MachineInfo(nodeName, tc.maxPods, tc.podsPerCore, machineInfoFunc, capacityFunc,
- devicePluginResourceCapacityFunc, nodeAllocatableReservationFunc, recordEventFunc)
- // call setter on node
- if err := setter(tc.node); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected node
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, tc.node),
- "Diff: %s", diff.ObjectDiff(tc.expectNode, tc.node))
- // check expected events
- require.Equal(t, len(tc.expectEvents), len(events))
- for i := range tc.expectEvents {
- assert.Equal(t, tc.expectEvents[i], events[i])
- }
- })
- }
- }
- func TestVersionInfo(t *testing.T) {
- cases := []struct {
- desc string
- node *v1.Node
- versionInfo *cadvisorapiv1.VersionInfo
- versionInfoError error
- runtimeType string
- runtimeVersion kubecontainer.Version
- runtimeVersionError error
- expectNode *v1.Node
- expectError error
- }{
- {
- desc: "versions set in node info",
- node: &v1.Node{},
- versionInfo: &cadvisorapiv1.VersionInfo{
- KernelVersion: "KernelVersion",
- ContainerOsVersion: "ContainerOSVersion",
- },
- runtimeType: "RuntimeType",
- runtimeVersion: &kubecontainertest.FakeVersion{
- Version: "RuntimeVersion",
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- NodeInfo: v1.NodeSystemInfo{
- KernelVersion: "KernelVersion",
- OSImage: "ContainerOSVersion",
- ContainerRuntimeVersion: "RuntimeType://RuntimeVersion",
- KubeletVersion: version.Get().String(),
- KubeProxyVersion: version.Get().String(),
- },
- },
- },
- },
- {
- desc: "error getting version info",
- node: &v1.Node{},
- versionInfoError: fmt.Errorf("foo"),
- expectNode: &v1.Node{},
- expectError: fmt.Errorf("error getting version info: foo"),
- },
- {
- desc: "error getting runtime version results in Unknown runtime",
- node: &v1.Node{},
- versionInfo: &cadvisorapiv1.VersionInfo{},
- runtimeType: "RuntimeType",
- runtimeVersionError: fmt.Errorf("foo"),
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- NodeInfo: v1.NodeSystemInfo{
- ContainerRuntimeVersion: "RuntimeType://Unknown",
- KubeletVersion: version.Get().String(),
- KubeProxyVersion: version.Get().String(),
- },
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- versionInfoFunc := func() (*cadvisorapiv1.VersionInfo, error) {
- return tc.versionInfo, tc.versionInfoError
- }
- runtimeTypeFunc := func() string {
- return tc.runtimeType
- }
- runtimeVersionFunc := func() (kubecontainer.Version, error) {
- return tc.runtimeVersion, tc.runtimeVersionError
- }
- // construct setter
- setter := VersionInfo(versionInfoFunc, runtimeTypeFunc, runtimeVersionFunc)
- // call setter on node
- err := setter(tc.node)
- require.Equal(t, tc.expectError, err)
- // check expected node
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, tc.node),
- "Diff: %s", diff.ObjectDiff(tc.expectNode, tc.node))
- })
- }
- }
- func TestImages(t *testing.T) {
- const (
- minImageSize = 23 * 1024 * 1024
- maxImageSize = 1000 * 1024 * 1024
- )
- cases := []struct {
- desc string
- maxImages int32
- imageList []kubecontainer.Image
- imageListError error
- expectError error
- }{
- {
- desc: "max images enforced",
- maxImages: 1,
- imageList: makeImageList(2, 1, minImageSize, maxImageSize),
- },
- {
- desc: "no max images cap for -1",
- maxImages: -1,
- imageList: makeImageList(2, 1, minImageSize, maxImageSize),
- },
- {
- desc: "max names per image enforced",
- maxImages: -1,
- imageList: makeImageList(1, MaxNamesPerImageInNodeStatus+1, minImageSize, maxImageSize),
- },
- {
- desc: "images are sorted by size, descending",
- maxImages: -1,
- // makeExpectedImageList will sort them for expectedNode when the test case is run
- imageList: []kubecontainer.Image{{Size: 3}, {Size: 1}, {Size: 4}, {Size: 2}},
- },
- {
- desc: "repo digests and tags both show up in image names",
- maxImages: -1,
- // makeExpectedImageList will use both digests and tags
- imageList: []kubecontainer.Image{
- {
- RepoDigests: []string{"foo", "bar"},
- RepoTags: []string{"baz", "quux"},
- },
- },
- },
- {
- desc: "error getting image list, image list on node is reset to empty",
- maxImages: -1,
- imageListError: fmt.Errorf("foo"),
- expectError: fmt.Errorf("error getting image list: foo"),
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- imageListFunc := func() ([]kubecontainer.Image, error) {
- // today, imageListFunc is expected to return a sorted list,
- // but we may choose to sort in the setter at some future point
- // (e.g. if the image cache stopped sorting for us)
- sort.Sort(sliceutils.ByImageSize(tc.imageList))
- return tc.imageList, tc.imageListError
- }
- // construct setter
- setter := Images(tc.maxImages, imageListFunc)
- // call setter on node
- node := &v1.Node{}
- err := setter(node)
- require.Equal(t, tc.expectError, err)
- // check expected node, image list should be reset to empty when there is an error
- expectNode := &v1.Node{}
- if err == nil {
- expectNode.Status.Images = makeExpectedImageList(tc.imageList, tc.maxImages, MaxNamesPerImageInNodeStatus)
- }
- assert.True(t, apiequality.Semantic.DeepEqual(expectNode, node),
- "Diff: %s", diff.ObjectDiff(expectNode, node))
- })
- }
- }
- func TestReadyCondition(t *testing.T) {
- now := time.Now()
- before := now.Add(-time.Second)
- nowFunc := func() time.Time { return now }
- withCapacity := &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceCPU: *resource.NewMilliQuantity(2000, resource.DecimalSI),
- v1.ResourceMemory: *resource.NewQuantity(10E9, resource.BinarySI),
- v1.ResourcePods: *resource.NewQuantity(100, resource.DecimalSI),
- v1.ResourceEphemeralStorage: *resource.NewQuantity(5000, resource.BinarySI),
- },
- },
- }
- cases := []struct {
- desc string
- node *v1.Node
- runtimeErrors error
- networkErrors error
- storageErrors error
- appArmorValidateHostFunc func() error
- cmStatus cm.Status
- expectConditions []v1.NodeCondition
- expectEvents []testEvent
- }{
- {
- desc: "new, ready",
- node: withCapacity.DeepCopy(),
- expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)},
- // TODO(mtaufen): The current behavior is that we don't send an event for the initial NodeReady condition,
- // the reason for this is unclear, so we may want to actually send an event, and change these test cases
- // to ensure an event is sent.
- },
- {
- desc: "new, ready: apparmor validator passed",
- node: withCapacity.DeepCopy(),
- appArmorValidateHostFunc: func() error { return nil },
- expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status. AppArmor enabled", now, now)},
- },
- {
- desc: "new, ready: apparmor validator failed",
- node: withCapacity.DeepCopy(),
- appArmorValidateHostFunc: func() error { return fmt.Errorf("foo") },
- // absence of an additional message is understood to mean that AppArmor is disabled
- expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)},
- },
- {
- desc: "new, ready: soft requirement warning",
- node: withCapacity.DeepCopy(),
- cmStatus: cm.Status{
- SoftRequirements: fmt.Errorf("foo"),
- },
- expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status. WARNING: foo", now, now)},
- },
- {
- desc: "new, not ready: storage errors",
- node: withCapacity.DeepCopy(),
- storageErrors: errors.New("some storage error"),
- expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "some storage error", now, now)},
- },
- {
- desc: "new, not ready: runtime and network errors",
- node: withCapacity.DeepCopy(),
- runtimeErrors: errors.New("runtime"),
- networkErrors: errors.New("network"),
- expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "[runtime, network]", now, now)},
- },
- {
- desc: "new, not ready: missing capacities",
- node: &v1.Node{},
- expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "Missing node capacity for resources: cpu, memory, pods, ephemeral-storage", now, now)},
- },
- // the transition tests ensure timestamps are set correctly, no need to test the entire condition matrix in this section
- {
- desc: "transition to ready",
- node: func() *v1.Node {
- node := withCapacity.DeepCopy()
- node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(false, "", before, before)}
- return node
- }(),
- expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: events.NodeReady,
- },
- },
- },
- {
- desc: "transition to not ready",
- node: func() *v1.Node {
- node := withCapacity.DeepCopy()
- node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(true, "", before, before)}
- return node
- }(),
- runtimeErrors: errors.New("foo"),
- expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "foo", now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: events.NodeNotReady,
- },
- },
- },
- {
- desc: "ready, no transition",
- node: func() *v1.Node {
- node := withCapacity.DeepCopy()
- node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(true, "", before, before)}
- return node
- }(),
- expectConditions: []v1.NodeCondition{*makeReadyCondition(true, "kubelet is posting ready status", before, now)},
- expectEvents: []testEvent{},
- },
- {
- desc: "not ready, no transition",
- node: func() *v1.Node {
- node := withCapacity.DeepCopy()
- node.Status.Conditions = []v1.NodeCondition{*makeReadyCondition(false, "", before, before)}
- return node
- }(),
- runtimeErrors: errors.New("foo"),
- expectConditions: []v1.NodeCondition{*makeReadyCondition(false, "foo", before, now)},
- expectEvents: []testEvent{},
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- runtimeErrorsFunc := func() error {
- return tc.runtimeErrors
- }
- networkErrorsFunc := func() error {
- return tc.networkErrors
- }
- storageErrorsFunc := func() error {
- return tc.storageErrors
- }
- cmStatusFunc := func() cm.Status {
- return tc.cmStatus
- }
- events := []testEvent{}
- recordEventFunc := func(eventType, event string) {
- events = append(events, testEvent{
- eventType: eventType,
- event: event,
- })
- }
- // construct setter
- setter := ReadyCondition(nowFunc, runtimeErrorsFunc, networkErrorsFunc, storageErrorsFunc, tc.appArmorValidateHostFunc, cmStatusFunc, recordEventFunc)
- // call setter on node
- if err := setter(tc.node); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected condition
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
- "Diff: %s", diff.ObjectDiff(tc.expectConditions, tc.node.Status.Conditions))
- // check expected events
- require.Equal(t, len(tc.expectEvents), len(events))
- for i := range tc.expectEvents {
- assert.Equal(t, tc.expectEvents[i], events[i])
- }
- })
- }
- }
- func TestMemoryPressureCondition(t *testing.T) {
- now := time.Now()
- before := now.Add(-time.Second)
- nowFunc := func() time.Time { return now }
- cases := []struct {
- desc string
- node *v1.Node
- pressure bool
- expectConditions []v1.NodeCondition
- expectEvents []testEvent
- }{
- {
- desc: "new, no pressure",
- node: &v1.Node{},
- pressure: false,
- expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasSufficientMemory",
- },
- },
- },
- {
- desc: "new, pressure",
- node: &v1.Node{},
- pressure: true,
- expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasInsufficientMemory",
- },
- },
- },
- {
- desc: "transition to pressure",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, before)},
- },
- },
- pressure: true,
- expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasInsufficientMemory",
- },
- },
- },
- {
- desc: "transition to no pressure",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, before)},
- },
- },
- pressure: false,
- expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasSufficientMemory",
- },
- },
- },
- {
- desc: "pressure, no transition",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, before)},
- },
- },
- pressure: true,
- expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(true, before, now)},
- expectEvents: []testEvent{},
- },
- {
- desc: "no pressure, no transition",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, before)},
- },
- },
- pressure: false,
- expectConditions: []v1.NodeCondition{*makeMemoryPressureCondition(false, before, now)},
- expectEvents: []testEvent{},
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- events := []testEvent{}
- recordEventFunc := func(eventType, event string) {
- events = append(events, testEvent{
- eventType: eventType,
- event: event,
- })
- }
- pressureFunc := func() bool {
- return tc.pressure
- }
- // construct setter
- setter := MemoryPressureCondition(nowFunc, pressureFunc, recordEventFunc)
- // call setter on node
- if err := setter(tc.node); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected condition
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
- "Diff: %s", diff.ObjectDiff(tc.expectConditions, tc.node.Status.Conditions))
- // check expected events
- require.Equal(t, len(tc.expectEvents), len(events))
- for i := range tc.expectEvents {
- assert.Equal(t, tc.expectEvents[i], events[i])
- }
- })
- }
- }
- func TestPIDPressureCondition(t *testing.T) {
- now := time.Now()
- before := now.Add(-time.Second)
- nowFunc := func() time.Time { return now }
- cases := []struct {
- desc string
- node *v1.Node
- pressure bool
- expectConditions []v1.NodeCondition
- expectEvents []testEvent
- }{
- {
- desc: "new, no pressure",
- node: &v1.Node{},
- pressure: false,
- expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasSufficientPID",
- },
- },
- },
- {
- desc: "new, pressure",
- node: &v1.Node{},
- pressure: true,
- expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasInsufficientPID",
- },
- },
- },
- {
- desc: "transition to pressure",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)},
- },
- },
- pressure: true,
- expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasInsufficientPID",
- },
- },
- },
- {
- desc: "transition to no pressure",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)},
- },
- },
- pressure: false,
- expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasSufficientPID",
- },
- },
- },
- {
- desc: "pressure, no transition",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, before)},
- },
- },
- pressure: true,
- expectConditions: []v1.NodeCondition{*makePIDPressureCondition(true, before, now)},
- expectEvents: []testEvent{},
- },
- {
- desc: "no pressure, no transition",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, before)},
- },
- },
- pressure: false,
- expectConditions: []v1.NodeCondition{*makePIDPressureCondition(false, before, now)},
- expectEvents: []testEvent{},
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- events := []testEvent{}
- recordEventFunc := func(eventType, event string) {
- events = append(events, testEvent{
- eventType: eventType,
- event: event,
- })
- }
- pressureFunc := func() bool {
- return tc.pressure
- }
- // construct setter
- setter := PIDPressureCondition(nowFunc, pressureFunc, recordEventFunc)
- // call setter on node
- if err := setter(tc.node); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected condition
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
- "Diff: %s", diff.ObjectDiff(tc.expectConditions, tc.node.Status.Conditions))
- // check expected events
- require.Equal(t, len(tc.expectEvents), len(events))
- for i := range tc.expectEvents {
- assert.Equal(t, tc.expectEvents[i], events[i])
- }
- })
- }
- }
- func TestDiskPressureCondition(t *testing.T) {
- now := time.Now()
- before := now.Add(-time.Second)
- nowFunc := func() time.Time { return now }
- cases := []struct {
- desc string
- node *v1.Node
- pressure bool
- expectConditions []v1.NodeCondition
- expectEvents []testEvent
- }{
- {
- desc: "new, no pressure",
- node: &v1.Node{},
- pressure: false,
- expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasNoDiskPressure",
- },
- },
- },
- {
- desc: "new, pressure",
- node: &v1.Node{},
- pressure: true,
- expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasDiskPressure",
- },
- },
- },
- {
- desc: "transition to pressure",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, before)},
- },
- },
- pressure: true,
- expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasDiskPressure",
- },
- },
- },
- {
- desc: "transition to no pressure",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, before)},
- },
- },
- pressure: false,
- expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, now, now)},
- expectEvents: []testEvent{
- {
- eventType: v1.EventTypeNormal,
- event: "NodeHasNoDiskPressure",
- },
- },
- },
- {
- desc: "pressure, no transition",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, before)},
- },
- },
- pressure: true,
- expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(true, before, now)},
- expectEvents: []testEvent{},
- },
- {
- desc: "no pressure, no transition",
- node: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, before)},
- },
- },
- pressure: false,
- expectConditions: []v1.NodeCondition{*makeDiskPressureCondition(false, before, now)},
- expectEvents: []testEvent{},
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- events := []testEvent{}
- recordEventFunc := func(eventType, event string) {
- events = append(events, testEvent{
- eventType: eventType,
- event: event,
- })
- }
- pressureFunc := func() bool {
- return tc.pressure
- }
- // construct setter
- setter := DiskPressureCondition(nowFunc, pressureFunc, recordEventFunc)
- // call setter on node
- if err := setter(tc.node); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected condition
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectConditions, tc.node.Status.Conditions),
- "Diff: %s", diff.ObjectDiff(tc.expectConditions, tc.node.Status.Conditions))
- // check expected events
- require.Equal(t, len(tc.expectEvents), len(events))
- for i := range tc.expectEvents {
- assert.Equal(t, tc.expectEvents[i], events[i])
- }
- })
- }
- }
- func TestVolumesInUse(t *testing.T) {
- withVolumesInUse := &v1.Node{
- Status: v1.NodeStatus{
- VolumesInUse: []v1.UniqueVolumeName{"foo"},
- },
- }
- cases := []struct {
- desc string
- node *v1.Node
- synced bool
- volumesInUse []v1.UniqueVolumeName
- expectVolumesInUse []v1.UniqueVolumeName
- }{
- {
- desc: "synced",
- node: withVolumesInUse.DeepCopy(),
- synced: true,
- volumesInUse: []v1.UniqueVolumeName{"bar"},
- expectVolumesInUse: []v1.UniqueVolumeName{"bar"},
- },
- {
- desc: "not synced",
- node: withVolumesInUse.DeepCopy(),
- synced: false,
- volumesInUse: []v1.UniqueVolumeName{"bar"},
- expectVolumesInUse: []v1.UniqueVolumeName{"foo"},
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- syncedFunc := func() bool {
- return tc.synced
- }
- volumesInUseFunc := func() []v1.UniqueVolumeName {
- return tc.volumesInUse
- }
- // construct setter
- setter := VolumesInUse(syncedFunc, volumesInUseFunc)
- // call setter on node
- if err := setter(tc.node); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected volumes
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectVolumesInUse, tc.node.Status.VolumesInUse),
- "Diff: %s", diff.ObjectDiff(tc.expectVolumesInUse, tc.node.Status.VolumesInUse))
- })
- }
- }
- func TestVolumeLimits(t *testing.T) {
- const (
- volumeLimitKey = "attachable-volumes-fake-provider"
- volumeLimitVal = 16
- )
- var cases = []struct {
- desc string
- volumePluginList []volume.VolumePluginWithAttachLimits
- expectNode *v1.Node
- }{
- {
- desc: "translate limits to capacity and allocatable for plugins that return successfully from GetVolumeLimits",
- volumePluginList: []volume.VolumePluginWithAttachLimits{
- &volumetest.FakeVolumePlugin{
- VolumeLimits: map[string]int64{volumeLimitKey: volumeLimitVal},
- },
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- volumeLimitKey: *resource.NewQuantity(volumeLimitVal, resource.DecimalSI),
- },
- Allocatable: v1.ResourceList{
- volumeLimitKey: *resource.NewQuantity(volumeLimitVal, resource.DecimalSI),
- },
- },
- },
- },
- {
- desc: "skip plugins that return errors from GetVolumeLimits",
- volumePluginList: []volume.VolumePluginWithAttachLimits{
- &volumetest.FakeVolumePlugin{
- VolumeLimitsError: fmt.Errorf("foo"),
- },
- },
- expectNode: &v1.Node{},
- },
- {
- desc: "no plugins",
- expectNode: &v1.Node{},
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- volumePluginListFunc := func() []volume.VolumePluginWithAttachLimits {
- return tc.volumePluginList
- }
- // construct setter
- setter := VolumeLimits(volumePluginListFunc)
- // call setter on node
- node := &v1.Node{}
- if err := setter(node); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected node
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, node),
- "Diff: %s", diff.ObjectDiff(tc.expectNode, node))
- })
- }
- }
- func TestRemoveOutOfDiskCondition(t *testing.T) {
- now := time.Now()
- var cases = []struct {
- desc string
- inputNode *v1.Node
- expectNode *v1.Node
- }{
- {
- desc: "should remove stale OutOfDiskCondition from node status",
- inputNode: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{
- *makeMemoryPressureCondition(false, now, now),
- {
- Type: v1.NodeOutOfDisk,
- Status: v1.ConditionFalse,
- },
- *makeDiskPressureCondition(false, now, now),
- },
- },
- },
- expectNode: &v1.Node{
- Status: v1.NodeStatus{
- Conditions: []v1.NodeCondition{
- *makeMemoryPressureCondition(false, now, now),
- *makeDiskPressureCondition(false, now, now),
- },
- },
- },
- },
- }
- for _, tc := range cases {
- t.Run(tc.desc, func(t *testing.T) {
- // construct setter
- setter := RemoveOutOfDiskCondition()
- // call setter on node
- if err := setter(tc.inputNode); err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
- // check expected node
- assert.True(t, apiequality.Semantic.DeepEqual(tc.expectNode, tc.inputNode),
- "Diff: %s", diff.ObjectDiff(tc.expectNode, tc.inputNode))
- })
- }
- }
- // Test Helpers:
- // sortableNodeAddress is a type for sorting []v1.NodeAddress
- type sortableNodeAddress []v1.NodeAddress
- func (s sortableNodeAddress) Len() int { return len(s) }
- func (s sortableNodeAddress) Less(i, j int) bool {
- return (string(s[i].Type) + s[i].Address) < (string(s[j].Type) + s[j].Address)
- }
- func (s sortableNodeAddress) Swap(i, j int) { s[j], s[i] = s[i], s[j] }
- func sortNodeAddresses(addrs sortableNodeAddress) {
- sort.Sort(addrs)
- }
- // testEvent is used to record events for tests
- type testEvent struct {
- eventType string
- event string
- message string
- }
- // makeImageList randomly generates a list of images with the given count
- func makeImageList(numImages, numTags, minSize, maxSize int32) []kubecontainer.Image {
- images := make([]kubecontainer.Image, numImages)
- for i := range images {
- image := &images[i]
- image.ID = string(uuid.NewUUID())
- image.RepoTags = makeImageTags(numTags)
- image.Size = rand.Int63nRange(int64(minSize), int64(maxSize+1))
- }
- return images
- }
- func makeExpectedImageList(imageList []kubecontainer.Image, maxImages, maxNames int32) []v1.ContainerImage {
- // copy the imageList, we do not want to mutate it in-place and accidentally edit a test case
- images := make([]kubecontainer.Image, len(imageList))
- copy(images, imageList)
- // sort images by size
- sort.Sort(sliceutils.ByImageSize(images))
- // convert to []v1.ContainerImage and truncate the list of names
- expectedImages := make([]v1.ContainerImage, len(images))
- for i := range images {
- image := &images[i]
- expectedImage := &expectedImages[i]
- names := append(image.RepoDigests, image.RepoTags...)
- if len(names) > int(maxNames) {
- names = names[0:maxNames]
- }
- expectedImage.Names = names
- expectedImage.SizeBytes = image.Size
- }
- // -1 means no limit, truncate result list if necessary.
- if maxImages > -1 &&
- int(maxImages) < len(expectedImages) {
- return expectedImages[0:maxImages]
- }
- return expectedImages
- }
- func makeImageTags(num int32) []string {
- tags := make([]string, num)
- for i := range tags {
- tags[i] = "k8s.gcr.io:v" + strconv.Itoa(i)
- }
- return tags
- }
- func makeReadyCondition(ready bool, message string, transition, heartbeat time.Time) *v1.NodeCondition {
- if ready {
- return &v1.NodeCondition{
- Type: v1.NodeReady,
- Status: v1.ConditionTrue,
- Reason: "KubeletReady",
- Message: message,
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
- return &v1.NodeCondition{
- Type: v1.NodeReady,
- Status: v1.ConditionFalse,
- Reason: "KubeletNotReady",
- Message: message,
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
- func makeMemoryPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
- if pressure {
- return &v1.NodeCondition{
- Type: v1.NodeMemoryPressure,
- Status: v1.ConditionTrue,
- Reason: "KubeletHasInsufficientMemory",
- Message: "kubelet has insufficient memory available",
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
- return &v1.NodeCondition{
- Type: v1.NodeMemoryPressure,
- Status: v1.ConditionFalse,
- Reason: "KubeletHasSufficientMemory",
- Message: "kubelet has sufficient memory available",
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
- func makePIDPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
- if pressure {
- return &v1.NodeCondition{
- Type: v1.NodePIDPressure,
- Status: v1.ConditionTrue,
- Reason: "KubeletHasInsufficientPID",
- Message: "kubelet has insufficient PID available",
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
- return &v1.NodeCondition{
- Type: v1.NodePIDPressure,
- Status: v1.ConditionFalse,
- Reason: "KubeletHasSufficientPID",
- Message: "kubelet has sufficient PID available",
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
- func makeDiskPressureCondition(pressure bool, transition, heartbeat time.Time) *v1.NodeCondition {
- if pressure {
- return &v1.NodeCondition{
- Type: v1.NodeDiskPressure,
- Status: v1.ConditionTrue,
- Reason: "KubeletHasDiskPressure",
- Message: "kubelet has disk pressure",
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
- return &v1.NodeCondition{
- Type: v1.NodeDiskPressure,
- Status: v1.ConditionFalse,
- Reason: "KubeletHasNoDiskPressure",
- Message: "kubelet has no disk pressure",
- LastTransitionTime: metav1.NewTime(transition),
- LastHeartbeatTime: metav1.NewTime(heartbeat),
- }
- }
|