...

Source file src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy_test.go

Documentation: k8s.io/apiextensions-apiserver/pkg/registry/customresource

     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 customresource
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
    25  	v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/fields"
    29  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    30  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    31  )
    32  
    33  func generation1() map[string]interface{} {
    34  	return map[string]interface{}{
    35  		"generation": int64(1),
    36  	}
    37  }
    38  
    39  func generation2() map[string]interface{} {
    40  	return map[string]interface{}{
    41  		"generation": int64(2),
    42  	}
    43  }
    44  
    45  func TestStrategyPrepareForUpdate(t *testing.T) {
    46  	strategy := customResourceStrategy{}
    47  	tcs := []struct {
    48  		name          string
    49  		old           *unstructured.Unstructured
    50  		obj           *unstructured.Unstructured
    51  		statusEnabled bool
    52  		expected      *unstructured.Unstructured
    53  	}{
    54  		{
    55  			name:          "/status is enabled, spec changes increment generation",
    56  			statusEnabled: true,
    57  			old: &unstructured.Unstructured{
    58  				Object: map[string]interface{}{
    59  					"metadata": generation1(),
    60  					"spec":     "old",
    61  					"status":   "old",
    62  				},
    63  			},
    64  			obj: &unstructured.Unstructured{
    65  				Object: map[string]interface{}{
    66  					"metadata": generation1(),
    67  					"spec":     "new",
    68  					"status":   "old",
    69  				},
    70  			},
    71  			expected: &unstructured.Unstructured{
    72  				Object: map[string]interface{}{
    73  					"metadata": generation2(),
    74  					"spec":     "new",
    75  					"status":   "old",
    76  				},
    77  			},
    78  		},
    79  		{
    80  			name:          "/status is enabled, status changes do not increment generation, status changes removed",
    81  			statusEnabled: true,
    82  			old: &unstructured.Unstructured{
    83  				Object: map[string]interface{}{
    84  					"metadata": generation1(),
    85  					"spec":     "old",
    86  					"status":   "old",
    87  				},
    88  			},
    89  			obj: &unstructured.Unstructured{
    90  				Object: map[string]interface{}{
    91  					"metadata": generation1(),
    92  					"spec":     "old",
    93  					"status":   "new",
    94  				},
    95  			},
    96  			expected: &unstructured.Unstructured{
    97  				Object: map[string]interface{}{
    98  					"metadata": generation1(),
    99  					"spec":     "old",
   100  					"status":   "old",
   101  				},
   102  			},
   103  		},
   104  		{
   105  			name:          "/status is enabled, metadata changes do not increment generation",
   106  			statusEnabled: true,
   107  			old: &unstructured.Unstructured{
   108  				Object: map[string]interface{}{
   109  					"metadata": map[string]interface{}{
   110  						"generation": int64(1),
   111  						"other":      "old",
   112  					},
   113  					"spec":   "old",
   114  					"status": "old",
   115  				},
   116  			},
   117  			obj: &unstructured.Unstructured{
   118  				Object: map[string]interface{}{
   119  					"metadata": map[string]interface{}{
   120  						"generation": int64(1),
   121  						"other":      "new",
   122  					},
   123  					"spec":   "old",
   124  					"status": "old",
   125  				},
   126  			},
   127  			expected: &unstructured.Unstructured{
   128  				Object: map[string]interface{}{
   129  					"metadata": map[string]interface{}{
   130  						"generation": int64(1),
   131  						"other":      "new",
   132  					},
   133  					"spec":   "old",
   134  					"status": "old",
   135  				},
   136  			},
   137  		},
   138  		{
   139  			name:          "/status is disabled, spec changes increment generation",
   140  			statusEnabled: false,
   141  			old: &unstructured.Unstructured{
   142  				Object: map[string]interface{}{
   143  					"metadata": generation1(),
   144  					"spec":     "old",
   145  					"status":   "old",
   146  				},
   147  			},
   148  			obj: &unstructured.Unstructured{
   149  				Object: map[string]interface{}{
   150  					"metadata": generation1(),
   151  					"spec":     "new",
   152  					"status":   "old",
   153  				},
   154  			},
   155  			expected: &unstructured.Unstructured{
   156  				Object: map[string]interface{}{
   157  					"metadata": generation2(),
   158  					"spec":     "new",
   159  					"status":   "old",
   160  				},
   161  			},
   162  		},
   163  		{
   164  			name:          "/status is disabled, status changes increment generation",
   165  			statusEnabled: false,
   166  			old: &unstructured.Unstructured{
   167  				Object: map[string]interface{}{
   168  					"metadata": generation1(),
   169  					"spec":     "old",
   170  					"status":   "old",
   171  				},
   172  			},
   173  			obj: &unstructured.Unstructured{
   174  				Object: map[string]interface{}{
   175  					"metadata": generation1(),
   176  					"spec":     "old",
   177  					"status":   "new",
   178  				},
   179  			},
   180  			expected: &unstructured.Unstructured{
   181  				Object: map[string]interface{}{
   182  					"metadata": generation2(),
   183  					"spec":     "old",
   184  					"status":   "new",
   185  				},
   186  			},
   187  		},
   188  		{
   189  			name:          "/status is disabled, other top-level field changes increment generation",
   190  			statusEnabled: false,
   191  			old: &unstructured.Unstructured{
   192  				Object: map[string]interface{}{
   193  					"metadata": generation1(),
   194  					"spec":     "old",
   195  					"image":    "old",
   196  					"status":   "old",
   197  				},
   198  			},
   199  			obj: &unstructured.Unstructured{
   200  				Object: map[string]interface{}{
   201  					"metadata": generation1(),
   202  					"spec":     "old",
   203  					"image":    "new",
   204  					"status":   "old",
   205  				},
   206  			},
   207  			expected: &unstructured.Unstructured{
   208  				Object: map[string]interface{}{
   209  					"metadata": generation2(),
   210  					"spec":     "old",
   211  					"image":    "new",
   212  					"status":   "old",
   213  				},
   214  			},
   215  		},
   216  		{
   217  			name:          "/status is disabled, metadata changes do not increment generation",
   218  			statusEnabled: false,
   219  			old: &unstructured.Unstructured{
   220  				Object: map[string]interface{}{
   221  					"metadata": map[string]interface{}{
   222  						"generation": int64(1),
   223  						"other":      "old",
   224  					},
   225  					"spec":   "old",
   226  					"status": "old",
   227  				},
   228  			},
   229  			obj: &unstructured.Unstructured{
   230  				Object: map[string]interface{}{
   231  					"metadata": map[string]interface{}{
   232  						"generation": int64(1),
   233  						"other":      "new",
   234  					},
   235  					"spec":   "old",
   236  					"status": "old",
   237  				},
   238  			},
   239  			expected: &unstructured.Unstructured{
   240  				Object: map[string]interface{}{
   241  					"metadata": map[string]interface{}{
   242  						"generation": int64(1),
   243  						"other":      "new",
   244  					},
   245  					"spec":   "old",
   246  					"status": "old",
   247  				},
   248  			},
   249  		},
   250  	}
   251  	for _, tc := range tcs {
   252  		if tc.statusEnabled {
   253  			strategy.status = &apiextensions.CustomResourceSubresourceStatus{}
   254  		} else {
   255  			strategy.status = nil
   256  		}
   257  		strategy.PrepareForUpdate(context.TODO(), tc.obj, tc.old)
   258  		if !reflect.DeepEqual(tc.obj, tc.expected) {
   259  			t.Errorf("test %q failed: expected: %v, got %v", tc.name, tc.expected, tc.obj)
   260  		}
   261  	}
   262  }
   263  
   264  func TestSelectableFields(t *testing.T) {
   265  	tcs := []struct {
   266  		name             string
   267  		selectableFields []v1.SelectableField
   268  		obj              *unstructured.Unstructured
   269  		expectFields     fields.Set
   270  	}{
   271  		{
   272  			name: "valid path",
   273  			selectableFields: []v1.SelectableField{
   274  				{JSONPath: ".spec.foo"},
   275  			},
   276  			obj: &unstructured.Unstructured{
   277  				Object: map[string]interface{}{
   278  					"metadata": map[string]interface{}{
   279  						"name":       "example",
   280  						"generation": int64(1),
   281  						"other":      "new",
   282  					},
   283  					"spec": map[string]interface{}{
   284  						"foo": "x",
   285  					},
   286  				},
   287  			},
   288  			expectFields: map[string]string{"spec.foo": "x", "metadata.name": "example"},
   289  		},
   290  		{
   291  			name: "missing value",
   292  			selectableFields: []v1.SelectableField{
   293  				{JSONPath: ".spec.foo"},
   294  			},
   295  			obj: &unstructured.Unstructured{
   296  				Object: map[string]interface{}{
   297  					"metadata": map[string]interface{}{
   298  						"name":       "example",
   299  						"generation": int64(1),
   300  						"other":      "new",
   301  					},
   302  					"spec": map[string]interface{}{},
   303  				},
   304  			},
   305  			expectFields: map[string]string{"spec.foo": "", "metadata.name": "example"},
   306  		},
   307  	}
   308  
   309  	for _, tc := range tcs {
   310  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, apiextensionsfeatures.CustomResourceFieldSelectors, true)()
   311  		t.Run(tc.name, func(t *testing.T) {
   312  			strategy := customResourceStrategy{selectableFieldSet: prepareSelectableFields(tc.selectableFields)}
   313  
   314  			_, fields, err := strategy.GetAttrs(tc.obj)
   315  			if err != nil {
   316  				t.Fatal(err)
   317  			}
   318  			if !reflect.DeepEqual(tc.expectFields, fields) {
   319  				t.Errorf("Expected fields '%+#v' but got '%+#v'", tc.expectFields, fields)
   320  			}
   321  		})
   322  	}
   323  }
   324  

View as plain text