...

Source file src/k8s.io/kubernetes/pkg/registry/storage/csinode/strategy_test.go

Documentation: k8s.io/kubernetes/pkg/registry/storage/csinode

     1  /*
     2  Copyright 2019 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 csinode
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/util/validation/field"
    25  	genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
    26  	"k8s.io/kubernetes/pkg/apis/storage"
    27  	utilpointer "k8s.io/utils/pointer"
    28  )
    29  
    30  func TestPrepareForCreate(t *testing.T) {
    31  	valid := getValidCSINode("foo")
    32  	emptyAllocatable := &storage.CSINode{
    33  		ObjectMeta: metav1.ObjectMeta{
    34  			Name: "foo",
    35  		},
    36  		Spec: storage.CSINodeSpec{
    37  			Drivers: []storage.CSINodeDriver{
    38  				{
    39  					Name:         "valid-driver-name",
    40  					NodeID:       "valid-node",
    41  					TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
    42  				},
    43  			},
    44  		},
    45  	}
    46  
    47  	volumeLimitsCases := []struct {
    48  		name     string
    49  		obj      *storage.CSINode
    50  		expected *storage.CSINode
    51  	}{
    52  		{
    53  			"empty allocatable",
    54  			emptyAllocatable,
    55  			emptyAllocatable,
    56  		},
    57  		{
    58  			"valid allocatable",
    59  			valid,
    60  			valid,
    61  		},
    62  	}
    63  
    64  	for _, test := range volumeLimitsCases {
    65  		t.Run(test.name, func(t *testing.T) {
    66  			testPrepareForCreate(t, test.obj, test.expected)
    67  		})
    68  	}
    69  }
    70  
    71  func testPrepareForCreate(t *testing.T, obj, expected *storage.CSINode) {
    72  	ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
    73  		APIGroup:   "storage.k8s.io",
    74  		APIVersion: "v1beta1",
    75  		Resource:   "csinodes",
    76  	})
    77  	Strategy.PrepareForCreate(ctx, obj)
    78  	if !reflect.DeepEqual(*expected, *obj) {
    79  		t.Errorf("Object mismatch! Expected:\n%#v\ngot:\n%#v", *expected, *obj)
    80  	}
    81  }
    82  
    83  func TestPrepareForUpdate(t *testing.T) {
    84  	valid := getValidCSINode("foo")
    85  	differentAllocatable := &storage.CSINode{
    86  		ObjectMeta: metav1.ObjectMeta{
    87  			Name: "foo",
    88  		},
    89  		Spec: storage.CSINodeSpec{
    90  			Drivers: []storage.CSINodeDriver{
    91  				{
    92  					Name:         "valid-driver-name",
    93  					NodeID:       "valid-node",
    94  					TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
    95  					Allocatable:  &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(20)},
    96  				},
    97  			},
    98  		},
    99  	}
   100  	emptyAllocatable := &storage.CSINode{
   101  		ObjectMeta: metav1.ObjectMeta{
   102  			Name: "foo",
   103  		},
   104  		Spec: storage.CSINodeSpec{
   105  			Drivers: []storage.CSINodeDriver{
   106  				{
   107  					Name:         "valid-driver-name",
   108  					NodeID:       "valid-node",
   109  					TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
   110  				},
   111  			},
   112  		},
   113  	}
   114  
   115  	volumeLimitsCases := []struct {
   116  		name     string
   117  		old      *storage.CSINode
   118  		new      *storage.CSINode
   119  		expected *storage.CSINode
   120  	}{
   121  		{
   122  			"allow empty allocatable when it's not set",
   123  			emptyAllocatable,
   124  			emptyAllocatable,
   125  			emptyAllocatable,
   126  		},
   127  		{
   128  			"allow valid allocatable when it's already set",
   129  			valid,
   130  			differentAllocatable,
   131  			differentAllocatable,
   132  		},
   133  		{
   134  			"allow valid allocatable when it's not set",
   135  			emptyAllocatable,
   136  			valid,
   137  			valid,
   138  		},
   139  	}
   140  
   141  	for _, test := range volumeLimitsCases {
   142  		t.Run(test.name, func(t *testing.T) {
   143  			testPrepareForUpdate(t, test.new, test.old, test.expected)
   144  		})
   145  	}
   146  }
   147  
   148  func testPrepareForUpdate(t *testing.T, obj, old, expected *storage.CSINode) {
   149  	ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
   150  		APIGroup:   "storage.k8s.io",
   151  		APIVersion: "v1beta1",
   152  		Resource:   "csinodes",
   153  	})
   154  	Strategy.PrepareForUpdate(ctx, obj, old)
   155  	if !reflect.DeepEqual(*expected, *obj) {
   156  		t.Errorf("Object mismatch! Expected:\n%#v\ngot:\n%#v", *expected, *obj)
   157  	}
   158  }
   159  
   160  func TestCSINodeStrategy(t *testing.T) {
   161  	ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
   162  		APIGroup:   "storage.k8s.io",
   163  		APIVersion: "v1beta1",
   164  		Resource:   "csinodes",
   165  	})
   166  	if Strategy.NamespaceScoped() {
   167  		t.Errorf("CSINode must not be namespace scoped")
   168  	}
   169  	if Strategy.AllowCreateOnUpdate() {
   170  		t.Errorf("CSINode should not allow create on update")
   171  	}
   172  
   173  	csiNode := getValidCSINode("valid-csinode")
   174  
   175  	Strategy.PrepareForCreate(ctx, csiNode)
   176  
   177  	errs := Strategy.Validate(ctx, csiNode)
   178  	if len(errs) != 0 {
   179  		t.Errorf("unexpected error validating %v", errs)
   180  	}
   181  
   182  	// Update of spec is allowed
   183  	newCSINode := csiNode.DeepCopy()
   184  	newCSINode.Spec.Drivers[0].NodeID = "valid-node-2"
   185  
   186  	Strategy.PrepareForUpdate(ctx, newCSINode, csiNode)
   187  
   188  	errs = Strategy.ValidateUpdate(ctx, newCSINode, csiNode)
   189  	if len(errs) == 0 {
   190  		t.Errorf("expected validation error")
   191  	}
   192  }
   193  
   194  func TestCSINodeValidation(t *testing.T) {
   195  	tests := []struct {
   196  		name        string
   197  		csiNode     *storage.CSINode
   198  		expectError bool
   199  	}{
   200  		{
   201  			"valid csinode",
   202  			getValidCSINode("foo"),
   203  			false,
   204  		},
   205  		{
   206  			"valid csinode with empty allocatable",
   207  			&storage.CSINode{
   208  				ObjectMeta: metav1.ObjectMeta{
   209  					Name: "foo",
   210  				},
   211  				Spec: storage.CSINodeSpec{
   212  					Drivers: []storage.CSINodeDriver{
   213  						{
   214  							Name:         "valid-driver-name",
   215  							NodeID:       "valid-node",
   216  							TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
   217  						},
   218  					},
   219  				},
   220  			},
   221  			false,
   222  		},
   223  		{
   224  			"valid csinode with missing volume limits",
   225  			&storage.CSINode{
   226  				ObjectMeta: metav1.ObjectMeta{
   227  					Name: "foo",
   228  				},
   229  				Spec: storage.CSINodeSpec{
   230  					Drivers: []storage.CSINodeDriver{
   231  						{
   232  							Name:         "valid-driver-name",
   233  							NodeID:       "valid-node",
   234  							TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
   235  							Allocatable:  &storage.VolumeNodeResources{Count: nil},
   236  						},
   237  					},
   238  				},
   239  			},
   240  			false,
   241  		},
   242  		{
   243  			"invalid driver name",
   244  			&storage.CSINode{
   245  				ObjectMeta: metav1.ObjectMeta{
   246  					Name: "foo",
   247  				},
   248  				Spec: storage.CSINodeSpec{
   249  					Drivers: []storage.CSINodeDriver{
   250  						{
   251  							Name:         "$csi-driver@",
   252  							NodeID:       "valid-node",
   253  							TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
   254  							Allocatable:  &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(10)},
   255  						},
   256  					},
   257  				},
   258  			},
   259  			true,
   260  		},
   261  		{
   262  			"empty node id",
   263  			&storage.CSINode{
   264  				ObjectMeta: metav1.ObjectMeta{
   265  					Name: "foo",
   266  				},
   267  				Spec: storage.CSINodeSpec{
   268  					Drivers: []storage.CSINodeDriver{
   269  						{
   270  							Name:         "valid-driver-name",
   271  							NodeID:       "",
   272  							TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
   273  							Allocatable:  &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(10)},
   274  						},
   275  					},
   276  				},
   277  			},
   278  			true,
   279  		},
   280  		{
   281  			"invalid allocatable with negative volumes limit",
   282  			&storage.CSINode{
   283  				ObjectMeta: metav1.ObjectMeta{
   284  					Name: "foo",
   285  				},
   286  				Spec: storage.CSINodeSpec{
   287  					Drivers: []storage.CSINodeDriver{
   288  						{
   289  							Name:         "valid-driver-name",
   290  							NodeID:       "valid-node",
   291  							TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
   292  							Allocatable:  &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(-1)},
   293  						},
   294  					},
   295  				},
   296  			},
   297  			true,
   298  		},
   299  		{
   300  			"invalid topology keys",
   301  			&storage.CSINode{
   302  				ObjectMeta: metav1.ObjectMeta{
   303  					Name: "foo",
   304  				},
   305  				Spec: storage.CSINodeSpec{
   306  					Drivers: []storage.CSINodeDriver{
   307  						{
   308  							Name:         "valid-driver-name",
   309  							NodeID:       "valid-node",
   310  							TopologyKeys: []string{"company.com/zone1", ""},
   311  							Allocatable:  &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(10)},
   312  						},
   313  					},
   314  				},
   315  			},
   316  			true,
   317  		},
   318  	}
   319  
   320  	for _, test := range tests {
   321  		t.Run(test.name, func(t *testing.T) {
   322  
   323  			testValidation := func(csiNode *storage.CSINode, apiVersion string) field.ErrorList {
   324  				ctx := genericapirequest.WithRequestInfo(genericapirequest.NewContext(), &genericapirequest.RequestInfo{
   325  					APIGroup:   "storage.k8s.io",
   326  					APIVersion: "v1beta1",
   327  					Resource:   "csinodes",
   328  				})
   329  				return Strategy.Validate(ctx, csiNode)
   330  			}
   331  
   332  			betaErr := testValidation(test.csiNode, "v1beta1")
   333  			if len(betaErr) > 0 && !test.expectError {
   334  				t.Errorf("Validation of v1beta1 object failed: %+v", betaErr)
   335  			}
   336  			if len(betaErr) == 0 && test.expectError {
   337  				t.Errorf("Validation of v1beta1 object unexpectedly succeeded")
   338  			}
   339  		})
   340  	}
   341  }
   342  
   343  func getValidCSINode(name string) *storage.CSINode {
   344  	return &storage.CSINode{
   345  		ObjectMeta: metav1.ObjectMeta{
   346  			Name: name,
   347  		},
   348  		Spec: storage.CSINodeSpec{
   349  			Drivers: []storage.CSINodeDriver{
   350  				{
   351  					Name:         "valid-driver-name",
   352  					NodeID:       "valid-node",
   353  					TopologyKeys: []string{"company.com/zone1", "company.com/zone2"},
   354  					Allocatable:  &storage.VolumeNodeResources{Count: utilpointer.Int32Ptr(10)},
   355  				},
   356  			},
   357  		},
   358  	}
   359  }
   360  

View as plain text