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  }