k8s.io/kubernetes@v1.29.3/pkg/controller/volume/attachdetach/statusupdater/node_status_updater_test.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package statusupdater 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 24 corev1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/types" 28 "k8s.io/client-go/informers" 29 "k8s.io/client-go/kubernetes/fake" 30 core "k8s.io/client-go/testing" 31 "k8s.io/klog/v2" 32 "k8s.io/kubernetes/pkg/controller" 33 "k8s.io/kubernetes/pkg/controller/volume/attachdetach/cache" 34 controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing" 35 volumetesting "k8s.io/kubernetes/pkg/volume/testing" 36 "testing" 37 ) 38 39 // setupNodeStatusUpdate creates all the needed objects for testing. 40 // the initial environment has 2 nodes with no volumes attached 41 // and adds one volume to attach to each node to the actual state of the world 42 func setupNodeStatusUpdate(logger klog.Logger, t *testing.T) (cache.ActualStateOfWorld, *fake.Clientset, NodeStatusUpdater) { 43 testNode1 := corev1.Node{ 44 TypeMeta: metav1.TypeMeta{ 45 Kind: "Node", 46 APIVersion: "v1", 47 }, 48 ObjectMeta: metav1.ObjectMeta{ 49 Name: "testnode-1", 50 }, 51 Status: corev1.NodeStatus{}, 52 } 53 testNode2 := corev1.Node{ 54 TypeMeta: metav1.TypeMeta{ 55 Kind: "Node", 56 APIVersion: "v1", 57 }, 58 ObjectMeta: metav1.ObjectMeta{ 59 Name: "testnode-2", 60 }, 61 Status: corev1.NodeStatus{}, 62 } 63 volumePluginMgr, _ := volumetesting.GetTestVolumePluginMgr(t) 64 asw := cache.NewActualStateOfWorld(volumePluginMgr) 65 fakeKubeClient := fake.NewSimpleClientset(&testNode1, &testNode2) 66 informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc()) 67 nodeInformer := informerFactory.Core().V1().Nodes() 68 nsu := NewNodeStatusUpdater(fakeKubeClient, nodeInformer.Lister(), asw) 69 70 err := nodeInformer.Informer().GetStore().Add(&testNode1) 71 if err != nil { 72 t.Fatalf(".Informer().GetStore().Add failed. Expected: <no error> Actual: <%v>", err) 73 } 74 err = nodeInformer.Informer().GetStore().Add(&testNode2) 75 if err != nil { 76 t.Fatalf(".Informer().GetStore().Add failed. Expected: <no error> Actual: <%v>", err) 77 } 78 79 volumeName1 := corev1.UniqueVolumeName("volume-name-1") 80 volumeName2 := corev1.UniqueVolumeName("volume-name-2") 81 volumeSpec1 := controllervolumetesting.GetTestVolumeSpec(string(volumeName1), volumeName1) 82 volumeSpec2 := controllervolumetesting.GetTestVolumeSpec(string(volumeName2), volumeName2) 83 84 nodeName1 := types.NodeName("testnode-1") 85 nodeName2 := types.NodeName("testnode-2") 86 devicePath := "fake/device/path" 87 88 _, err = asw.AddVolumeNode(logger, volumeName1, volumeSpec1, nodeName1, devicePath, true) 89 if err != nil { 90 t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", err) 91 } 92 _, err = asw.AddVolumeNode(logger, volumeName2, volumeSpec2, nodeName2, devicePath, true) 93 if err != nil { 94 t.Fatalf("AddVolumeNode failed. Expected: <no error> Actual: <%v>", err) 95 } 96 97 return asw, fakeKubeClient, nsu 98 } 99 100 // TestNodeStatusUpdater_UpdateNodeStatuses_TwoNodesUpdate calls setup 101 // calls UpdateNodeStatuses() 102 // check that asw.GetVolumesToReportAttached reports nothing left to attach 103 // checks that each node status.volumesAttached is of length 1 and contains the correct volume 104 func TestNodeStatusUpdater_UpdateNodeStatuses_TwoNodesUpdate(t *testing.T) { 105 ctx := context.Background() 106 logger := klog.FromContext(ctx) 107 asw, fakeKubeClient, nsu := setupNodeStatusUpdate(logger, t) 108 109 err := nsu.UpdateNodeStatuses(logger) 110 if err != nil { 111 t.Fatalf("UpdateNodeStatuses failed. Expected: <no error> Actual: <%v>", err) 112 } 113 114 needToReport := asw.GetVolumesToReportAttached(logger) 115 if len(needToReport) != 0 { 116 t.Fatalf("len(asw.GetVolumesToReportAttached()) Expected: <0> Actual: <%v>", len(needToReport)) 117 } 118 119 node, err := fakeKubeClient.CoreV1().Nodes().Get(ctx, "testnode-1", metav1.GetOptions{}) 120 if err != nil { 121 t.Fatalf("Nodes().Get failed. Expected: <no error> Actual: <%v>", err) 122 } 123 if len(node.Status.VolumesAttached) != 1 { 124 t.Fatalf("len(node.Status.VolumesAttached) Expected: <1> Actual: <%v>", len(node.Status.VolumesAttached)) 125 } 126 if node.Status.VolumesAttached[0].Name != "volume-name-1" { 127 t.Fatalf("volumeName Expected: <volume-name-1> Actual: <%s>", node.Status.VolumesAttached[0].Name) 128 } 129 130 node, err = fakeKubeClient.CoreV1().Nodes().Get(ctx, "testnode-2", metav1.GetOptions{}) 131 if err != nil { 132 t.Fatalf("Nodes().Get failed. Expected: <no error> Actual: <%v>", err) 133 } 134 if len(node.Status.VolumesAttached) != 1 { 135 t.Fatalf("len(node.Status.VolumesAttached) Expected: <1> Actual: <%v>", len(node.Status.VolumesAttached)) 136 } 137 if node.Status.VolumesAttached[0].Name != "volume-name-2" { 138 t.Fatalf("volumeName Expected: <volume-name-2> Actual: <%s>", node.Status.VolumesAttached[0].Name) 139 } 140 } 141 142 func TestNodeStatusUpdater_UpdateNodeStatuses_FailureInFirstUpdate(t *testing.T) { 143 ctx := context.Background() 144 logger := klog.FromContext(ctx) 145 asw, fakeKubeClient, nsu := setupNodeStatusUpdate(logger, t) 146 147 var failedNode string 148 failedOnce := false 149 failureErr := fmt.Errorf("test generated error") 150 fakeKubeClient.PrependReactor("patch", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) { 151 patchAction := action.(core.PatchAction) 152 if !failedOnce { 153 failedNode = patchAction.GetName() 154 failedOnce = true 155 return true, nil, failureErr 156 } 157 return false, nil, nil 158 }) 159 160 err := nsu.UpdateNodeStatuses(logger) 161 if errors.Is(err, failureErr) { 162 t.Fatalf("UpdateNodeStatuses failed. Expected: <test generated error> Actual: <%v>", err) 163 } 164 165 needToReport := asw.GetVolumesToReportAttached(logger) 166 if len(needToReport) != 1 { 167 t.Fatalf("len(asw.GetVolumesToReportAttached()) Expected: <1> Actual: <%v>", len(needToReport)) 168 } 169 if _, ok := needToReport[types.NodeName(failedNode)]; !ok { 170 t.Fatalf("GetVolumesToReportAttached() did not report correct node Expected: <%s> Actual: <%v>", failedNode, needToReport) 171 } 172 173 nodes, err := fakeKubeClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) 174 if err != nil { 175 t.Fatalf("Nodes().List failed. Expected: <no error> Actual: <%v>", err) 176 } 177 178 if len(nodes.Items) != 2 { 179 t.Fatalf("len(nodes.Items) Expected: <2> Actual: <%v>", len(nodes.Items)) 180 } 181 182 for _, node := range nodes.Items { 183 if node.Name == failedNode { 184 if len(node.Status.VolumesAttached) != 0 { 185 t.Fatalf("len(node.Status.VolumesAttached) Expected: <0> Actual: <%v>", len(node.Status.VolumesAttached)) 186 } 187 } else { 188 if len(node.Status.VolumesAttached) != 1 { 189 t.Fatalf("len(node.Status.VolumesAttached) Expected: <1> Actual: <%v>", len(node.Status.VolumesAttached)) 190 } 191 } 192 } 193 } 194 195 // TestNodeStatusUpdater_UpdateNodeStatusForNode calls setup 196 // calls UpdateNodeStatusesForNode on testnode-1 197 // check that asw.GetVolumesToReportAttached reports testnode-2 needs to be reported 198 // checks that testnode-1 status.volumesAttached is of length 1 and contains the correct volume 199 func TestNodeStatusUpdater_UpdateNodeStatusForNode(t *testing.T) { 200 ctx := context.Background() 201 logger := klog.FromContext(ctx) 202 asw, fakeKubeClient, nsu := setupNodeStatusUpdate(logger, t) 203 204 err := nsu.UpdateNodeStatusForNode(logger, "testnode-1") 205 if err != nil { 206 t.Fatalf("UpdateNodeStatuses failed. Expected: <no error> Actual: <%v>", err) 207 } 208 209 needToReport := asw.GetVolumesToReportAttached(logger) 210 if len(needToReport) != 1 { 211 t.Fatalf("len(asw.GetVolumesToReportAttached()) Expected: <1> Actual: <%v>", len(needToReport)) 212 } 213 if _, ok := needToReport["testnode-2"]; !ok { 214 t.Fatalf("GetVolumesToReportAttached() did not report correct node Expected: <testnode-2> Actual: <%v>", needToReport) 215 } 216 217 node, err := fakeKubeClient.CoreV1().Nodes().Get(ctx, "testnode-1", metav1.GetOptions{}) 218 if err != nil { 219 t.Fatalf("Nodes().Get failed. Expected: <no error> Actual: <%v>", err) 220 } 221 if len(node.Status.VolumesAttached) != 1 { 222 t.Fatalf("len(node.Status.VolumesAttached) Expected: <1> Actual: <%v>", len(node.Status.VolumesAttached)) 223 } 224 if node.Status.VolumesAttached[0].Name != "volume-name-1" { 225 t.Fatalf("volumeName Expected: <volume-name-1> Actual: <%s>", node.Status.VolumesAttached[0].Name) 226 } 227 }