...

Source file src/k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager/nodeinfomanager_test.go

Documentation: k8s.io/kubernetes/pkg/volume/csi/nodeinfomanager

     1  /*
     2  Copyright 2018 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 nodeinfomanager
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"math"
    24  	"os"
    25  	"reflect"
    26  	"testing"
    27  
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  	v1 "k8s.io/api/core/v1"
    32  	storage "k8s.io/api/storage/v1"
    33  	"k8s.io/apimachinery/pkg/api/errors"
    34  	"k8s.io/apimachinery/pkg/api/resource"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    38  	"k8s.io/client-go/kubernetes/fake"
    39  	clienttesting "k8s.io/client-go/testing"
    40  	utiltesting "k8s.io/client-go/util/testing"
    41  	"k8s.io/kubernetes/pkg/apis/core/helper"
    42  	volumetest "k8s.io/kubernetes/pkg/volume/testing"
    43  	"k8s.io/kubernetes/pkg/volume/util"
    44  	utilpointer "k8s.io/utils/pointer"
    45  )
    46  
    47  type testcase struct {
    48  	name             string
    49  	driverName       string
    50  	existingNode     *v1.Node
    51  	existingCSINode  *storage.CSINode
    52  	inputNodeID      string
    53  	inputTopology    map[string]string
    54  	inputVolumeLimit int64
    55  	expectedNode     *v1.Node
    56  	expectedCSINode  *storage.CSINode
    57  	expectFail       bool
    58  	hasModified      bool
    59  }
    60  
    61  type nodeIDMap map[string]string
    62  type topologyKeyMap map[string][]string
    63  type labelMap map[string]string
    64  
    65  // TestInstallCSIDriver tests InstallCSIDriver with various existing Node and/or CSINode objects.
    66  // The node IDs in all test cases below are the same between the Node annotation and CSINode.
    67  func TestInstallCSIDriver(t *testing.T) {
    68  	testcases := []testcase{
    69  		{
    70  			name:         "empty node",
    71  			driverName:   "com.example.csi.driver1",
    72  			existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
    73  			inputNodeID:  "com.example.csi/csi-node1",
    74  			inputTopology: map[string]string{
    75  				"com.example.csi/zone": "zoneA",
    76  			},
    77  			expectedNode: &v1.Node{
    78  				ObjectMeta: metav1.ObjectMeta{
    79  					Name:        "node1",
    80  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
    81  					Labels:      labelMap{"com.example.csi/zone": "zoneA"},
    82  				},
    83  			},
    84  			expectedCSINode: &storage.CSINode{
    85  				ObjectMeta: getCSINodeObjectMeta(),
    86  				Spec: storage.CSINodeSpec{
    87  					Drivers: []storage.CSINodeDriver{
    88  						{
    89  							Name:         "com.example.csi.driver1",
    90  							NodeID:       "com.example.csi/csi-node1",
    91  							TopologyKeys: []string{"com.example.csi/zone"},
    92  						},
    93  					},
    94  				},
    95  			},
    96  		},
    97  		{
    98  			name:       "pre-existing node info from the same driver",
    99  			driverName: "com.example.csi.driver1",
   100  			existingNode: generateNode(
   101  				nodeIDMap{
   102  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   103  				},
   104  				labelMap{
   105  					"com.example.csi/zone": "zoneA",
   106  				},
   107  				nil /*capacity*/),
   108  			existingCSINode: generateCSINode(
   109  				nodeIDMap{
   110  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   111  				},
   112  				nil, /* volumeLimits */
   113  				topologyKeyMap{
   114  					"com.example.csi.driver1": {"com.example.csi/zone"},
   115  				},
   116  			),
   117  			inputNodeID: "com.example.csi/csi-node1",
   118  			inputTopology: map[string]string{
   119  				"com.example.csi/zone": "zoneA",
   120  			},
   121  			expectedNode: &v1.Node{
   122  				ObjectMeta: metav1.ObjectMeta{
   123  					Name:        "node1",
   124  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   125  					Labels:      labelMap{"com.example.csi/zone": "zoneA"},
   126  				},
   127  			},
   128  			expectedCSINode: &storage.CSINode{
   129  				ObjectMeta: getCSINodeObjectMeta(),
   130  				Spec: storage.CSINodeSpec{
   131  					Drivers: []storage.CSINodeDriver{
   132  						{
   133  							Name:         "com.example.csi.driver1",
   134  							NodeID:       "com.example.csi/csi-node1",
   135  							TopologyKeys: []string{"com.example.csi/zone"},
   136  							Allocatable:  nil,
   137  						},
   138  					},
   139  				},
   140  			},
   141  		},
   142  		{
   143  			name:       "pre-existing node info from the same driver, but without topology info",
   144  			driverName: "com.example.csi.driver1",
   145  			existingNode: generateNode(
   146  				nodeIDMap{
   147  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   148  				},
   149  				nil /* labels */, nil /*capacity*/),
   150  			existingCSINode: generateCSINode(
   151  				nodeIDMap{
   152  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   153  				},
   154  				nil, /* volumeLimits */
   155  				nil, /* topologyKeys */
   156  			),
   157  			inputNodeID: "com.example.csi/csi-node1",
   158  			inputTopology: map[string]string{
   159  				"com.example.csi/zone": "zoneA",
   160  			},
   161  			expectedNode: &v1.Node{
   162  				ObjectMeta: metav1.ObjectMeta{
   163  					Name:        "node1",
   164  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   165  					Labels:      labelMap{"com.example.csi/zone": "zoneA"},
   166  				},
   167  			},
   168  			expectedCSINode: &storage.CSINode{
   169  				ObjectMeta: getCSINodeObjectMeta(),
   170  				Spec: storage.CSINodeSpec{
   171  					Drivers: []storage.CSINodeDriver{
   172  						{
   173  							Name:         "com.example.csi.driver1",
   174  							NodeID:       "com.example.csi/csi-node1",
   175  							TopologyKeys: []string{"com.example.csi/zone"},
   176  							Allocatable:  nil,
   177  						},
   178  					},
   179  				},
   180  			},
   181  		},
   182  		{
   183  			name:       "pre-existing node info from different driver",
   184  			driverName: "com.example.csi.driver1",
   185  			existingNode: generateNode(
   186  				nodeIDMap{
   187  					"net.example.storage.other-driver": "net.example.storage/test-node",
   188  				},
   189  				labelMap{
   190  					"net.example.storage/rack": "rack1",
   191  				}, nil /*capacity*/),
   192  			existingCSINode: generateCSINode(
   193  				nodeIDMap{
   194  					"net.example.storage.other-driver": "net.example.storage/test-node",
   195  				},
   196  				nil, /* volumeLimits */
   197  				topologyKeyMap{
   198  					"net.example.storage.other-driver": {"net.example.storage/rack"},
   199  				},
   200  			),
   201  			inputNodeID: "com.example.csi/csi-node1",
   202  			inputTopology: map[string]string{
   203  				"com.example.csi/zone": "zoneA",
   204  			},
   205  			expectedNode: &v1.Node{
   206  				ObjectMeta: metav1.ObjectMeta{
   207  					Name: "node1",
   208  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{
   209  						"com.example.csi.driver1":          "com.example.csi/csi-node1",
   210  						"net.example.storage.other-driver": "net.example.storage/test-node",
   211  					})},
   212  					Labels: labelMap{
   213  						"com.example.csi/zone":     "zoneA",
   214  						"net.example.storage/rack": "rack1",
   215  					},
   216  				},
   217  			},
   218  			expectedCSINode: &storage.CSINode{
   219  				ObjectMeta: getCSINodeObjectMeta(),
   220  				Spec: storage.CSINodeSpec{
   221  					Drivers: []storage.CSINodeDriver{
   222  						{
   223  							Name:         "net.example.storage.other-driver",
   224  							NodeID:       "net.example.storage/test-node",
   225  							TopologyKeys: []string{"net.example.storage/rack"},
   226  							Allocatable:  nil,
   227  						},
   228  						{
   229  							Name:         "com.example.csi.driver1",
   230  							NodeID:       "com.example.csi/csi-node1",
   231  							TopologyKeys: []string{"com.example.csi/zone"},
   232  							Allocatable:  nil,
   233  						},
   234  					},
   235  				},
   236  			},
   237  		},
   238  		{
   239  			name:       "pre-existing node info from the same driver, but different node ID and topology values; labels should conflict",
   240  			driverName: "com.example.csi.driver1",
   241  			existingNode: generateNode(
   242  				nodeIDMap{
   243  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   244  				},
   245  				labelMap{
   246  					"com.example.csi/zone": "zoneA",
   247  				}, nil /*capacity*/),
   248  			existingCSINode: generateCSINode(
   249  				nodeIDMap{
   250  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   251  				},
   252  				nil, /* volumeLimits */
   253  				topologyKeyMap{
   254  					"com.example.csi.driver1": {"com.example.csi/zone"},
   255  				},
   256  			),
   257  			inputNodeID: "com.example.csi/csi-node1",
   258  			inputTopology: map[string]string{
   259  				"com.example.csi/zone": "other-zone",
   260  			},
   261  			expectFail: true,
   262  		},
   263  		{
   264  			name:       "pre-existing node info from the same driver, but different node ID and topology keys; new labels should be added",
   265  			driverName: "com.example.csi.driver1",
   266  			existingNode: generateNode(
   267  				nodeIDMap{
   268  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   269  				},
   270  				labelMap{
   271  					"com.example.csi/zone": "zoneA",
   272  				}, nil /*capacity*/),
   273  			existingCSINode: generateCSINode(
   274  				nodeIDMap{
   275  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   276  				},
   277  				nil, /* volumeLimits */
   278  				topologyKeyMap{
   279  					"com.example.csi.driver1": {"com.example.csi/zone"},
   280  				},
   281  			),
   282  			inputNodeID: "com.example.csi/other-node",
   283  			inputTopology: map[string]string{
   284  				"com.example.csi/rack": "rack1",
   285  			},
   286  			expectedNode: &v1.Node{
   287  				ObjectMeta: metav1.ObjectMeta{
   288  					Name:        "node1",
   289  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/other-node"})},
   290  					Labels: labelMap{
   291  						"com.example.csi/zone": "zoneA",
   292  						"com.example.csi/rack": "rack1",
   293  					},
   294  				},
   295  			},
   296  			expectedCSINode: &storage.CSINode{
   297  				ObjectMeta: getCSINodeObjectMeta(),
   298  				Spec: storage.CSINodeSpec{
   299  					Drivers: []storage.CSINodeDriver{
   300  						{
   301  							Name:         "com.example.csi.driver1",
   302  							NodeID:       "com.example.csi/other-node",
   303  							TopologyKeys: []string{"com.example.csi/rack"},
   304  							Allocatable:  nil,
   305  						},
   306  					},
   307  				},
   308  			},
   309  		},
   310  		{
   311  			name:          "nil topology, empty node",
   312  			driverName:    "com.example.csi.driver1",
   313  			existingNode:  generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
   314  			inputNodeID:   "com.example.csi/csi-node1",
   315  			inputTopology: nil,
   316  			expectedNode: &v1.Node{
   317  				ObjectMeta: metav1.ObjectMeta{
   318  					Name:        "node1",
   319  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   320  				},
   321  			},
   322  			expectedCSINode: &storage.CSINode{
   323  				ObjectMeta: getCSINodeObjectMeta(),
   324  				Spec: storage.CSINodeSpec{
   325  					Drivers: []storage.CSINodeDriver{
   326  						{
   327  							Name:         "com.example.csi.driver1",
   328  							NodeID:       "com.example.csi/csi-node1",
   329  							TopologyKeys: nil,
   330  							Allocatable:  nil,
   331  						},
   332  					},
   333  				},
   334  			},
   335  		},
   336  		{
   337  			name:       "nil topology, pre-existing node info from the same driver",
   338  			driverName: "com.example.csi.driver1",
   339  			existingNode: generateNode(
   340  				nodeIDMap{
   341  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   342  				},
   343  				labelMap{
   344  					"com.example.csi/zone": "zoneA",
   345  				}, nil /*capacity*/),
   346  			existingCSINode: generateCSINode(
   347  				nodeIDMap{
   348  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   349  				},
   350  				nil, /* volumeLimits */
   351  				topologyKeyMap{
   352  					"com.example.csi.driver1": {"com.example.csi/zone"},
   353  				},
   354  			),
   355  			inputNodeID:   "com.example.csi/csi-node1",
   356  			inputTopology: nil,
   357  			expectedNode: &v1.Node{
   358  				ObjectMeta: metav1.ObjectMeta{
   359  					Name:        "node1",
   360  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   361  					Labels: labelMap{
   362  						"com.example.csi/zone": "zoneA",
   363  					},
   364  				},
   365  			},
   366  			expectedCSINode: &storage.CSINode{
   367  				ObjectMeta: getCSINodeObjectMeta(),
   368  				Spec: storage.CSINodeSpec{
   369  					Drivers: []storage.CSINodeDriver{
   370  						{
   371  							Name:         "com.example.csi.driver1",
   372  							NodeID:       "com.example.csi/csi-node1",
   373  							TopologyKeys: nil,
   374  							Allocatable:  nil,
   375  						},
   376  					},
   377  				},
   378  			},
   379  		},
   380  		{
   381  			name:       "nil topology, pre-existing node info from different driver",
   382  			driverName: "com.example.csi.driver1",
   383  			existingNode: generateNode(
   384  				nodeIDMap{
   385  					"net.example.storage.other-driver": "net.example.storage/test-node",
   386  				},
   387  				labelMap{
   388  					"net.example.storage/rack": "rack1",
   389  				}, nil /*capacity*/),
   390  			existingCSINode: generateCSINode(
   391  				nodeIDMap{
   392  					"net.example.storage.other-driver": "net.example.storage/test-node",
   393  				},
   394  				nil, /* volumeLimits */
   395  				topologyKeyMap{
   396  					"net.example.storage.other-driver": {"net.example.storage/rack"},
   397  				},
   398  			),
   399  			inputNodeID:   "com.example.csi/csi-node1",
   400  			inputTopology: nil,
   401  			expectedNode: &v1.Node{
   402  				ObjectMeta: metav1.ObjectMeta{
   403  					Name: "node1",
   404  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{
   405  						"com.example.csi.driver1":          "com.example.csi/csi-node1",
   406  						"net.example.storage.other-driver": "net.example.storage/test-node",
   407  					})},
   408  					Labels: labelMap{
   409  						"net.example.storage/rack": "rack1",
   410  					},
   411  				},
   412  			},
   413  			expectedCSINode: &storage.CSINode{
   414  				ObjectMeta: getCSINodeObjectMeta(),
   415  				Spec: storage.CSINodeSpec{
   416  					Drivers: []storage.CSINodeDriver{
   417  						{
   418  							Name:         "net.example.storage.other-driver",
   419  							NodeID:       "net.example.storage/test-node",
   420  							TopologyKeys: []string{"net.example.storage/rack"},
   421  							Allocatable:  nil,
   422  						},
   423  						{
   424  							Name:         "com.example.csi.driver1",
   425  							NodeID:       "com.example.csi/csi-node1",
   426  							TopologyKeys: nil,
   427  							Allocatable:  nil,
   428  						},
   429  					},
   430  				},
   431  			},
   432  		},
   433  		{
   434  			name:         "empty node ID",
   435  			driverName:   "com.example.csi.driver1",
   436  			existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
   437  			inputNodeID:  "",
   438  			expectFail:   true,
   439  		},
   440  		{
   441  			name:             "new node with valid max limit of volumes",
   442  			driverName:       "com.example.csi.driver1",
   443  			existingNode:     generateNode(nil /*nodeIDs*/, nil /*labels*/, nil /*capacity*/),
   444  			inputVolumeLimit: 10,
   445  			inputTopology:    nil,
   446  			inputNodeID:      "com.example.csi/csi-node1",
   447  			expectedNode: &v1.Node{
   448  				ObjectMeta: metav1.ObjectMeta{
   449  					Name:        "node1",
   450  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   451  				},
   452  			},
   453  			expectedCSINode: &storage.CSINode{
   454  				ObjectMeta: getCSINodeObjectMeta(),
   455  				Spec: storage.CSINodeSpec{
   456  					Drivers: []storage.CSINodeDriver{
   457  						{
   458  							Name:         "com.example.csi.driver1",
   459  							NodeID:       "com.example.csi/csi-node1",
   460  							TopologyKeys: nil,
   461  							Allocatable: &storage.VolumeNodeResources{
   462  								Count: utilpointer.Int32Ptr(10),
   463  							},
   464  						},
   465  					},
   466  				},
   467  			},
   468  		},
   469  		{
   470  			name:             "new node with max limit of volumes",
   471  			driverName:       "com.example.csi.driver1",
   472  			existingNode:     generateNode(nil /*nodeIDs*/, nil /*labels*/, nil /*capacity*/),
   473  			inputVolumeLimit: math.MaxInt32,
   474  			inputTopology:    nil,
   475  			inputNodeID:      "com.example.csi/csi-node1",
   476  			expectedNode: &v1.Node{
   477  				ObjectMeta: metav1.ObjectMeta{
   478  					Name:        "node1",
   479  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   480  				},
   481  			},
   482  			expectedCSINode: &storage.CSINode{
   483  				ObjectMeta: getCSINodeObjectMeta(),
   484  				Spec: storage.CSINodeSpec{
   485  					Drivers: []storage.CSINodeDriver{
   486  						{
   487  							Name:         "com.example.csi.driver1",
   488  							NodeID:       "com.example.csi/csi-node1",
   489  							TopologyKeys: nil,
   490  							Allocatable: &storage.VolumeNodeResources{
   491  								Count: utilpointer.Int32Ptr(math.MaxInt32),
   492  							},
   493  						},
   494  					},
   495  				},
   496  			},
   497  		},
   498  		{
   499  			name:             "new node with overflown max limit of volumes",
   500  			driverName:       "com.example.csi.driver1",
   501  			existingNode:     generateNode(nil /*nodeIDs*/, nil /*labels*/, nil /*capacity*/),
   502  			inputVolumeLimit: math.MaxInt32 + 1,
   503  			inputTopology:    nil,
   504  			inputNodeID:      "com.example.csi/csi-node1",
   505  			expectedNode: &v1.Node{
   506  				ObjectMeta: metav1.ObjectMeta{
   507  					Name:        "node1",
   508  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   509  				},
   510  			},
   511  			expectedCSINode: &storage.CSINode{
   512  				ObjectMeta: getCSINodeObjectMeta(),
   513  				Spec: storage.CSINodeSpec{
   514  					Drivers: []storage.CSINodeDriver{
   515  						{
   516  							Name:         "com.example.csi.driver1",
   517  							NodeID:       "com.example.csi/csi-node1",
   518  							TopologyKeys: nil,
   519  							Allocatable: &storage.VolumeNodeResources{
   520  								Count: utilpointer.Int32Ptr(math.MaxInt32),
   521  							},
   522  						},
   523  					},
   524  				},
   525  			},
   526  		},
   527  		{
   528  			name:             "new node without max limit of volumes",
   529  			driverName:       "com.example.csi.driver1",
   530  			existingNode:     generateNode(nil /*nodeIDs*/, nil /*labels*/, nil /*capacity*/),
   531  			inputVolumeLimit: 0,
   532  			inputTopology:    nil,
   533  			inputNodeID:      "com.example.csi/csi-node1",
   534  			expectedNode: &v1.Node{
   535  				ObjectMeta: metav1.ObjectMeta{
   536  					Name:        "node1",
   537  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   538  				},
   539  			},
   540  			expectedCSINode: &storage.CSINode{
   541  				ObjectMeta: getCSINodeObjectMeta(),
   542  				Spec: storage.CSINodeSpec{
   543  					Drivers: []storage.CSINodeDriver{
   544  						{
   545  							Name:         "com.example.csi.driver1",
   546  							NodeID:       "com.example.csi/csi-node1",
   547  							TopologyKeys: nil,
   548  						},
   549  					},
   550  				},
   551  			},
   552  		},
   553  		{
   554  			name:       "node with existing valid max limit of volumes",
   555  			driverName: "com.example.csi.driver1",
   556  			existingNode: generateNode(
   557  				nil, /*nodeIDs*/
   558  				nil, /*labels*/
   559  				map[v1.ResourceName]resource.Quantity{
   560  					v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
   561  				}),
   562  
   563  			existingCSINode: generateCSINode(
   564  				nodeIDMap{
   565  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   566  				},
   567  				generateVolumeLimits(10),
   568  				nil, /* topologyKeys */
   569  			),
   570  
   571  			inputVolumeLimit: 20,
   572  			inputTopology:    nil,
   573  			inputNodeID:      "com.example.csi/csi-node1",
   574  			expectedNode: &v1.Node{
   575  				ObjectMeta: metav1.ObjectMeta{
   576  					Name:        "node1",
   577  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"com.example.csi.driver1": "com.example.csi/csi-node1"})},
   578  				},
   579  				Status: v1.NodeStatus{
   580  					Capacity: v1.ResourceList{
   581  						v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
   582  					},
   583  					Allocatable: v1.ResourceList{
   584  						v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
   585  					},
   586  				},
   587  			},
   588  			expectedCSINode: &storage.CSINode{
   589  				ObjectMeta: getCSINodeObjectMeta(),
   590  				Spec: storage.CSINodeSpec{
   591  					Drivers: []storage.CSINodeDriver{
   592  						{
   593  							Name:         "com.example.csi.driver1",
   594  							NodeID:       "com.example.csi/csi-node1",
   595  							TopologyKeys: nil,
   596  							Allocatable:  generateVolumeLimits(20),
   597  						},
   598  					},
   599  				},
   600  			},
   601  		},
   602  	}
   603  
   604  	test(t, true /* addNodeInfo */, testcases)
   605  }
   606  
   607  func generateVolumeLimits(i int32) *storage.VolumeNodeResources {
   608  	return &storage.VolumeNodeResources{
   609  		Count: utilpointer.Int32Ptr(i),
   610  	}
   611  }
   612  
   613  // TestUninstallCSIDriver tests UninstallCSIDriver with various existing Node and/or CSINode objects.
   614  func TestUninstallCSIDriver(t *testing.T) {
   615  	testcases := []testcase{
   616  		{
   617  			name:         "empty node and empty CSINode",
   618  			driverName:   "com.example.csi.driver1",
   619  			existingNode: generateNode(nil /* nodeIDs */, nil /* labels */, nil /*capacity*/),
   620  			expectedNode: &v1.Node{
   621  				ObjectMeta: metav1.ObjectMeta{
   622  					Name: "node1",
   623  				},
   624  			},
   625  			expectedCSINode: &storage.CSINode{
   626  				ObjectMeta: getCSINodeObjectMeta(),
   627  				Spec:       storage.CSINodeSpec{},
   628  			},
   629  		},
   630  		{
   631  			name:       "pre-existing node info from the same driver",
   632  			driverName: "com.example.csi.driver1",
   633  			existingNode: generateNode(
   634  				nodeIDMap{
   635  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   636  				},
   637  				labelMap{
   638  					"com.example.csi/zone": "zoneA",
   639  				}, nil /*capacity*/),
   640  			existingCSINode: generateCSINode(
   641  				nodeIDMap{
   642  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   643  				},
   644  				nil, /* volumeLimits */
   645  				topologyKeyMap{
   646  					"com.example.csi.driver1": {"com.example.csi/zone"},
   647  				},
   648  			),
   649  			expectedNode: &v1.Node{
   650  				ObjectMeta: metav1.ObjectMeta{
   651  					Name:   "node1",
   652  					Labels: labelMap{"com.example.csi/zone": "zoneA"},
   653  				},
   654  			},
   655  			expectedCSINode: &storage.CSINode{
   656  				ObjectMeta: getCSINodeObjectMeta(),
   657  				Spec:       storage.CSINodeSpec{},
   658  			},
   659  			hasModified: true,
   660  		},
   661  		{
   662  			name:       "pre-existing node info from different driver",
   663  			driverName: "com.example.csi.driver1",
   664  			existingNode: generateNode(
   665  				nodeIDMap{
   666  					"net.example.storage.other-driver": "net.example.storage/csi-node1",
   667  				},
   668  				labelMap{
   669  					"net.example.storage/zone": "zoneA",
   670  				}, nil /*capacity*/),
   671  			existingCSINode: generateCSINode(
   672  				nodeIDMap{
   673  					"net.example.storage.other-driver": "net.example.storage/csi-node1",
   674  				},
   675  				nil, /* volumeLimits */
   676  				topologyKeyMap{
   677  					"net.example.storage.other-driver": {"net.example.storage/zone"},
   678  				},
   679  			),
   680  			expectedNode: &v1.Node{
   681  				ObjectMeta: metav1.ObjectMeta{
   682  					Name:        "node1",
   683  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"net.example.storage.other-driver": "net.example.storage/csi-node1"})},
   684  					Labels:      labelMap{"net.example.storage/zone": "zoneA"},
   685  				},
   686  			},
   687  			expectedCSINode: &storage.CSINode{
   688  				ObjectMeta: getCSINodeObjectMeta(),
   689  				Spec: storage.CSINodeSpec{
   690  					Drivers: []storage.CSINodeDriver{
   691  						{
   692  							Name:         "net.example.storage.other-driver",
   693  							NodeID:       "net.example.storage/csi-node1",
   694  							TopologyKeys: []string{"net.example.storage/zone"},
   695  						},
   696  					},
   697  				},
   698  			},
   699  			hasModified: false,
   700  		},
   701  		{
   702  			name:       "pre-existing info about the same driver in node, but empty CSINode",
   703  			driverName: "com.example.csi.driver1",
   704  			existingNode: generateNode(
   705  				nodeIDMap{
   706  					"com.example.csi.driver1": "com.example.csi/csi-node1",
   707  				},
   708  				nil /* labels */, nil /*capacity*/),
   709  			expectedNode: &v1.Node{
   710  				ObjectMeta: metav1.ObjectMeta{
   711  					Name: "node1",
   712  				},
   713  			},
   714  			expectedCSINode: &storage.CSINode{
   715  				ObjectMeta: getCSINodeObjectMeta(),
   716  				Spec:       storage.CSINodeSpec{},
   717  			},
   718  		},
   719  		{
   720  			name: "pre-existing info about a different driver in node, but empty CSINode",
   721  			existingNode: generateNode(
   722  				nodeIDMap{
   723  					"net.example.storage.other-driver": "net.example.storage/csi-node1",
   724  				},
   725  				nil /* labels */, nil /*capacity*/),
   726  			expectedNode: &v1.Node{
   727  				ObjectMeta: metav1.ObjectMeta{
   728  					Name:        "node1",
   729  					Annotations: map[string]string{annotationKeyNodeID: marshall(nodeIDMap{"net.example.storage.other-driver": "net.example.storage/csi-node1"})},
   730  				},
   731  			},
   732  			expectedCSINode: &storage.CSINode{
   733  				ObjectMeta: getCSINodeObjectMeta(),
   734  				Spec:       storage.CSINodeSpec{},
   735  			},
   736  		},
   737  		{
   738  			name:       "new node with valid max limit",
   739  			driverName: "com.example.csi.driver1",
   740  			existingNode: generateNode(
   741  				nil, /*nodeIDs*/
   742  				nil, /*labels*/
   743  				map[v1.ResourceName]resource.Quantity{
   744  					v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
   745  					v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
   746  				},
   747  			),
   748  			expectedNode: &v1.Node{
   749  				ObjectMeta: metav1.ObjectMeta{
   750  					Name: "node1",
   751  				},
   752  				Status: v1.NodeStatus{
   753  					Capacity: v1.ResourceList{
   754  						v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
   755  						v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
   756  					},
   757  					Allocatable: v1.ResourceList{
   758  						v1.ResourceCPU: *resource.NewScaledQuantity(4, -3),
   759  						v1.ResourceName(util.GetCSIAttachLimitKey("com.example.csi/driver1")): *resource.NewQuantity(10, resource.DecimalSI),
   760  					},
   761  				},
   762  			},
   763  			expectedCSINode: &storage.CSINode{
   764  				ObjectMeta: getCSINodeObjectMeta(),
   765  				Spec:       storage.CSINodeSpec{},
   766  			},
   767  			inputTopology: nil,
   768  			inputNodeID:   "com.example.csi/csi-node1",
   769  		},
   770  	}
   771  
   772  	test(t, false /* addNodeInfo */, testcases)
   773  }
   774  
   775  func TestSetMigrationAnnotation(t *testing.T) {
   776  	testcases := []struct {
   777  		name            string
   778  		migratedPlugins map[string](func() bool)
   779  		existingNode    *storage.CSINode
   780  		expectedNode    *storage.CSINode
   781  		expectModified  bool
   782  	}{
   783  		{
   784  			name: "nil migrated plugins",
   785  			existingNode: &storage.CSINode{
   786  				ObjectMeta: metav1.ObjectMeta{
   787  					Name: "node1",
   788  				},
   789  			},
   790  			expectedNode: &storage.CSINode{
   791  				ObjectMeta: metav1.ObjectMeta{
   792  					Name: "node1",
   793  				},
   794  			},
   795  		},
   796  		{
   797  			name: "one modified plugin",
   798  			migratedPlugins: map[string](func() bool){
   799  				"test": func() bool { return true },
   800  			},
   801  			existingNode: &storage.CSINode{
   802  				ObjectMeta: metav1.ObjectMeta{
   803  					Name: "node1",
   804  				},
   805  			},
   806  			expectedNode: &storage.CSINode{
   807  				ObjectMeta: metav1.ObjectMeta{
   808  					Name:        "node1",
   809  					Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
   810  				},
   811  			},
   812  			expectModified: true,
   813  		},
   814  		{
   815  			name: "existing plugin",
   816  			migratedPlugins: map[string](func() bool){
   817  				"test": func() bool { return true },
   818  			},
   819  			existingNode: &storage.CSINode{
   820  				ObjectMeta: metav1.ObjectMeta{
   821  					Name:        "node1",
   822  					Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
   823  				},
   824  			},
   825  			expectedNode: &storage.CSINode{
   826  				ObjectMeta: metav1.ObjectMeta{
   827  					Name:        "node1",
   828  					Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
   829  				},
   830  			},
   831  			expectModified: false,
   832  		},
   833  		{
   834  			name:            "remove plugin",
   835  			migratedPlugins: map[string](func() bool){},
   836  			existingNode: &storage.CSINode{
   837  				ObjectMeta: metav1.ObjectMeta{
   838  					Name:        "node1",
   839  					Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test"},
   840  				},
   841  			},
   842  			expectedNode: &storage.CSINode{
   843  				ObjectMeta: metav1.ObjectMeta{
   844  					Name:        "node1",
   845  					Annotations: map[string]string{},
   846  				},
   847  			},
   848  			expectModified: true,
   849  		},
   850  		{
   851  			name: "one modified plugin, other annotations stable",
   852  			migratedPlugins: map[string](func() bool){
   853  				"test": func() bool { return true },
   854  			},
   855  			existingNode: &storage.CSINode{
   856  				ObjectMeta: metav1.ObjectMeta{
   857  					Name:        "node1",
   858  					Annotations: map[string]string{"other": "annotation"},
   859  				},
   860  			},
   861  			expectedNode: &storage.CSINode{
   862  				ObjectMeta: metav1.ObjectMeta{
   863  					Name:        "node1",
   864  					Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test", "other": "annotation"},
   865  				},
   866  			},
   867  			expectModified: true,
   868  		},
   869  		{
   870  			name: "multiple plugins modified, other annotations stable",
   871  			migratedPlugins: map[string](func() bool){
   872  				"test": func() bool { return true },
   873  				"foo":  func() bool { return false },
   874  			},
   875  			existingNode: &storage.CSINode{
   876  				ObjectMeta: metav1.ObjectMeta{
   877  					Name:        "node1",
   878  					Annotations: map[string]string{"other": "annotation", v1.MigratedPluginsAnnotationKey: "foo"},
   879  				},
   880  			},
   881  			expectedNode: &storage.CSINode{
   882  				ObjectMeta: metav1.ObjectMeta{
   883  					Name:        "node1",
   884  					Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "test", "other": "annotation"},
   885  				},
   886  			},
   887  			expectModified: true,
   888  		},
   889  		{
   890  			name: "multiple plugins added, other annotations stable",
   891  			migratedPlugins: map[string](func() bool){
   892  				"test": func() bool { return true },
   893  				"foo":  func() bool { return true },
   894  			},
   895  			existingNode: &storage.CSINode{
   896  				ObjectMeta: metav1.ObjectMeta{
   897  					Name:        "node1",
   898  					Annotations: map[string]string{"other": "annotation"},
   899  				},
   900  			},
   901  			expectedNode: &storage.CSINode{
   902  				ObjectMeta: metav1.ObjectMeta{
   903  					Name:        "node1",
   904  					Annotations: map[string]string{v1.MigratedPluginsAnnotationKey: "foo,test", "other": "annotation"},
   905  				},
   906  			},
   907  			expectModified: true,
   908  		},
   909  	}
   910  
   911  	for _, tc := range testcases {
   912  		t.Logf("test case: %s", tc.name)
   913  
   914  		modified := setMigrationAnnotation(tc.migratedPlugins, tc.existingNode)
   915  		if modified != tc.expectModified {
   916  			t.Errorf("Expected modified to be %v but got %v instead", tc.expectModified, modified)
   917  		}
   918  
   919  		if !reflect.DeepEqual(tc.expectedNode, tc.existingNode) {
   920  			t.Errorf("Expected CSINode: %v, but got: %v", tc.expectedNode, tc.existingNode)
   921  		}
   922  	}
   923  }
   924  
   925  func TestInstallCSIDriverExistingAnnotation(t *testing.T) {
   926  	driverName := "com.example.csi/driver1"
   927  	nodeID := "com.example.csi/some-node"
   928  
   929  	testcases := []struct {
   930  		name         string
   931  		existingNode *v1.Node
   932  	}{
   933  		{
   934  			name: "pre-existing info about the same driver in node, but empty CSINode",
   935  			existingNode: generateNode(
   936  				nodeIDMap{
   937  					"com.example.csi/driver1": "com.example.csi/csi-node1",
   938  				},
   939  				nil /* labels */, nil /*capacity*/),
   940  		},
   941  		{
   942  			name: "pre-existing info about a different driver in node, but empty CSINode",
   943  			existingNode: generateNode(
   944  				nodeIDMap{
   945  					"net.example.storage/other-driver": "net.example.storage/test-node",
   946  				},
   947  				nil /* labels */, nil /*capacity*/),
   948  		},
   949  	}
   950  
   951  	for _, tc := range testcases {
   952  		t.Logf("test case: %q", tc.name)
   953  
   954  		// Arrange
   955  		nodeName := tc.existingNode.Name
   956  		client := fake.NewSimpleClientset(tc.existingNode)
   957  
   958  		tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
   959  		if err != nil {
   960  			t.Fatalf("can't create temp dir: %v", err)
   961  		}
   962  		defer os.RemoveAll(tmpDir)
   963  		host := volumetest.NewFakeVolumeHostWithCSINodeName(t,
   964  			tmpDir,
   965  			client,
   966  			nil,
   967  			nodeName,
   968  			nil,
   969  			nil,
   970  		)
   971  
   972  		nim := NewNodeInfoManager(types.NodeName(nodeName), host, nil)
   973  
   974  		// Act
   975  		_, err = nim.CreateCSINode()
   976  		if err != nil {
   977  			t.Errorf("expected no error from creating CSINodeinfo but got: %v", err)
   978  			continue
   979  		}
   980  		err = nim.InstallCSIDriver(driverName, nodeID, 0 /* maxVolumeLimit */, nil) // TODO test maxVolumeLimit
   981  		if err != nil {
   982  			t.Errorf("expected no error from InstallCSIDriver call but got: %v", err)
   983  			continue
   984  		}
   985  
   986  		// Assert
   987  		nodeInfo, err := client.StorageV1().CSINodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
   988  		if err != nil {
   989  			t.Errorf("error getting CSINode: %v", err)
   990  			continue
   991  		}
   992  
   993  		driver := nodeInfo.Spec.Drivers[0]
   994  		if driver.Name != driverName || driver.NodeID != nodeID {
   995  			t.Errorf("expected Driver to be %q and NodeID to be %q, but got: %q:%q", driverName, nodeID, driver.Name, driver.NodeID)
   996  		}
   997  	}
   998  }
   999  
  1000  func getClientSet(existingNode *v1.Node, existingCSINode *storage.CSINode) *fake.Clientset {
  1001  	objects := []runtime.Object{}
  1002  	if existingNode != nil {
  1003  		objects = append(objects, existingNode)
  1004  	}
  1005  	if existingCSINode != nil {
  1006  		objects = append(objects, existingCSINode)
  1007  	}
  1008  	return fake.NewSimpleClientset(objects...)
  1009  }
  1010  
  1011  func test(t *testing.T, addNodeInfo bool, testcases []testcase) {
  1012  	for _, tc := range testcases {
  1013  		t.Logf("test case: %q", tc.name)
  1014  
  1015  		//// Arrange
  1016  		nodeName := tc.existingNode.Name
  1017  		client := getClientSet(tc.existingNode, tc.existingCSINode)
  1018  
  1019  		tmpDir, err := utiltesting.MkTmpdir("nodeinfomanager-test")
  1020  		if err != nil {
  1021  			t.Fatalf("can't create temp dir: %v", err)
  1022  		}
  1023  		defer os.RemoveAll(tmpDir)
  1024  		host := volumetest.NewFakeVolumeHostWithCSINodeName(t,
  1025  			tmpDir,
  1026  			client,
  1027  			nil,
  1028  			nodeName,
  1029  			nil,
  1030  			nil,
  1031  		)
  1032  		nim := NewNodeInfoManager(types.NodeName(nodeName), host, nil)
  1033  
  1034  		//// Act
  1035  		nim.CreateCSINode()
  1036  		if addNodeInfo {
  1037  			err = nim.InstallCSIDriver(tc.driverName, tc.inputNodeID, tc.inputVolumeLimit, tc.inputTopology)
  1038  		} else {
  1039  			err = nim.UninstallCSIDriver(tc.driverName)
  1040  		}
  1041  
  1042  		//// Assert
  1043  		if tc.expectFail {
  1044  			if err == nil {
  1045  				t.Errorf("expected an error from InstallCSIDriver call but got none")
  1046  			}
  1047  			continue
  1048  		} else if err != nil {
  1049  			t.Errorf("expected no error from InstallCSIDriver call but got: %v", err)
  1050  			continue
  1051  		}
  1052  
  1053  		actions := client.Actions()
  1054  
  1055  		var node *v1.Node
  1056  		if action := hasPatchAction(actions); action != nil {
  1057  			node, err = applyNodeStatusPatch(tc.existingNode, action.(clienttesting.PatchActionImpl).GetPatch())
  1058  			assert.NoError(t, err)
  1059  		} else {
  1060  			node, err = client.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
  1061  			assert.NoError(t, err)
  1062  		}
  1063  
  1064  		if node == nil {
  1065  			t.Errorf("error getting node: %v", err)
  1066  			continue
  1067  		}
  1068  
  1069  		if !helper.Semantic.DeepEqual(node, tc.expectedNode) {
  1070  			t.Errorf("expected Node %v; got: %v", tc.expectedNode, node)
  1071  		}
  1072  
  1073  		// CSINode validation
  1074  		nodeInfo, err := client.StorageV1().CSINodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
  1075  		if err != nil {
  1076  			if !errors.IsNotFound(err) {
  1077  				t.Errorf("error getting CSINode: %v", err)
  1078  			}
  1079  			continue
  1080  		}
  1081  		if !helper.Semantic.DeepEqual(nodeInfo, tc.expectedCSINode) {
  1082  			t.Errorf("expected CSINode %v; got: %v", tc.expectedCSINode, nodeInfo)
  1083  		}
  1084  
  1085  		if !addNodeInfo && tc.existingCSINode != nil && tc.existingNode != nil {
  1086  			if tc.hasModified && helper.Semantic.DeepEqual(nodeInfo, tc.existingCSINode) {
  1087  				t.Errorf("existing CSINode %v; got: %v", tc.existingCSINode, nodeInfo)
  1088  			}
  1089  			if !tc.hasModified && !helper.Semantic.DeepEqual(nodeInfo, tc.existingCSINode) {
  1090  				t.Errorf("existing CSINode %v; got: %v", tc.existingCSINode, nodeInfo)
  1091  			}
  1092  		}
  1093  	}
  1094  }
  1095  
  1096  func generateNode(nodeIDs, labels map[string]string, capacity map[v1.ResourceName]resource.Quantity) *v1.Node {
  1097  	var annotations map[string]string
  1098  	if len(nodeIDs) > 0 {
  1099  		b, _ := json.Marshal(nodeIDs)
  1100  		annotations = map[string]string{annotationKeyNodeID: string(b)}
  1101  	}
  1102  	node := &v1.Node{
  1103  		ObjectMeta: metav1.ObjectMeta{
  1104  			Name:        "node1",
  1105  			Annotations: annotations,
  1106  			Labels:      labels,
  1107  		},
  1108  	}
  1109  
  1110  	if len(capacity) > 0 {
  1111  		node.Status.Capacity = v1.ResourceList(capacity)
  1112  		node.Status.Allocatable = v1.ResourceList(capacity)
  1113  	}
  1114  	return node
  1115  }
  1116  
  1117  func marshall(nodeIDs nodeIDMap) string {
  1118  	b, _ := json.Marshal(nodeIDs)
  1119  	return string(b)
  1120  }
  1121  
  1122  func generateCSINode(nodeIDs nodeIDMap, volumeLimits *storage.VolumeNodeResources, topologyKeys topologyKeyMap) *storage.CSINode {
  1123  	nodeDrivers := []storage.CSINodeDriver{}
  1124  	for k, nodeID := range nodeIDs {
  1125  		dspec := storage.CSINodeDriver{
  1126  			Name:        k,
  1127  			NodeID:      nodeID,
  1128  			Allocatable: volumeLimits,
  1129  		}
  1130  		if top, exists := topologyKeys[k]; exists {
  1131  			dspec.TopologyKeys = top
  1132  		}
  1133  		nodeDrivers = append(nodeDrivers, dspec)
  1134  	}
  1135  
  1136  	return &storage.CSINode{
  1137  		ObjectMeta: getCSINodeObjectMeta(),
  1138  		Spec: storage.CSINodeSpec{
  1139  			Drivers: nodeDrivers,
  1140  		},
  1141  	}
  1142  }
  1143  
  1144  func getCSINodeObjectMeta() metav1.ObjectMeta {
  1145  	return metav1.ObjectMeta{
  1146  		Name: "node1",
  1147  		OwnerReferences: []metav1.OwnerReference{
  1148  			{
  1149  				APIVersion: nodeKind.Version,
  1150  				Kind:       nodeKind.Kind,
  1151  				Name:       "node1",
  1152  			},
  1153  		},
  1154  	}
  1155  }
  1156  
  1157  func applyNodeStatusPatch(originalNode *v1.Node, patch []byte) (*v1.Node, error) {
  1158  	original, err := json.Marshal(originalNode)
  1159  	if err != nil {
  1160  		return nil, fmt.Errorf("failed to marshal original node %#v: %v", originalNode, err)
  1161  	}
  1162  	updated, err := strategicpatch.StrategicMergePatch(original, patch, v1.Node{})
  1163  	if err != nil {
  1164  		return nil, fmt.Errorf("failed to apply strategic merge patch %q on node %#v: %v",
  1165  			patch, originalNode, err)
  1166  	}
  1167  	updatedNode := &v1.Node{}
  1168  	if err := json.Unmarshal(updated, updatedNode); err != nil {
  1169  		return nil, fmt.Errorf("failed to unmarshal updated node %q: %v", updated, err)
  1170  	}
  1171  	return updatedNode, nil
  1172  }
  1173  
  1174  func hasPatchAction(actions []clienttesting.Action) clienttesting.Action {
  1175  	for _, action := range actions {
  1176  		if action.GetVerb() == "patch" {
  1177  			return action
  1178  		}
  1179  	}
  1180  	return nil
  1181  }
  1182  

View as plain text