...

Source file src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_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  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    26  	structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"sigs.k8s.io/yaml"
    30  )
    31  
    32  func TestPrepareForUpdate(t *testing.T) {
    33  	strategy := statusStrategy{}
    34  	tcs := []struct {
    35  		old      *unstructured.Unstructured
    36  		obj      *unstructured.Unstructured
    37  		expected *unstructured.Unstructured
    38  	}{
    39  		{
    40  			// changes to spec are ignored
    41  			old: &unstructured.Unstructured{
    42  				Object: map[string]interface{}{
    43  					"spec": "old",
    44  				},
    45  			},
    46  			obj: &unstructured.Unstructured{
    47  				Object: map[string]interface{}{
    48  					"spec": "new",
    49  				},
    50  			},
    51  			expected: &unstructured.Unstructured{
    52  				Object: map[string]interface{}{
    53  					"spec": "old",
    54  				},
    55  			},
    56  		},
    57  		{
    58  			// changes to other places are also ignored
    59  			old: &unstructured.Unstructured{
    60  				Object: map[string]interface{}{
    61  					"spec": "old",
    62  				},
    63  			},
    64  			obj: &unstructured.Unstructured{
    65  				Object: map[string]interface{}{
    66  					"new": "new",
    67  				},
    68  			},
    69  			expected: &unstructured.Unstructured{
    70  				Object: map[string]interface{}{
    71  					"spec": "old",
    72  				},
    73  			},
    74  		},
    75  		{
    76  			// delete status
    77  			old: &unstructured.Unstructured{
    78  				Object: map[string]interface{}{
    79  					"spec":   "old",
    80  					"status": "old",
    81  				},
    82  			},
    83  			obj: &unstructured.Unstructured{
    84  				Object: map[string]interface{}{
    85  					"spec": "old",
    86  				},
    87  			},
    88  			expected: &unstructured.Unstructured{
    89  				Object: map[string]interface{}{
    90  					"spec": "old",
    91  				},
    92  			},
    93  		},
    94  		{
    95  			// update status
    96  			old: &unstructured.Unstructured{
    97  				Object: map[string]interface{}{
    98  					"spec":   "old",
    99  					"status": "old",
   100  				},
   101  			},
   102  			obj: &unstructured.Unstructured{
   103  				Object: map[string]interface{}{
   104  					"status": "new",
   105  				},
   106  			},
   107  			expected: &unstructured.Unstructured{
   108  				Object: map[string]interface{}{
   109  					"spec":   "old",
   110  					"status": "new",
   111  				},
   112  			},
   113  		},
   114  		{
   115  			// update status and other parts
   116  			old: &unstructured.Unstructured{
   117  				Object: map[string]interface{}{
   118  					"spec":   "old",
   119  					"status": "old",
   120  				},
   121  			},
   122  			obj: &unstructured.Unstructured{
   123  				Object: map[string]interface{}{
   124  					"spec":   "new",
   125  					"new":    "new",
   126  					"status": "new",
   127  				},
   128  			},
   129  			expected: &unstructured.Unstructured{
   130  				Object: map[string]interface{}{
   131  					"spec":   "old",
   132  					"status": "new",
   133  				},
   134  			},
   135  		},
   136  	}
   137  	for index, tc := range tcs {
   138  		strategy.PrepareForUpdate(context.TODO(), tc.obj, tc.old)
   139  		if !reflect.DeepEqual(tc.obj, tc.expected) {
   140  			t.Errorf("test %d failed: expected: %v, got %v", index, tc.expected, tc.obj)
   141  		}
   142  	}
   143  }
   144  
   145  const listTypeResourceSchema = `
   146  apiVersion: apiextensions.k8s.io/v1
   147  kind: CustomResourceDefinition
   148  metadata:
   149    name: foos.test
   150  spec:
   151    group: test
   152    names:
   153      kind: Foo
   154      listKind: FooList
   155      plural: foos
   156      singular: foo
   157    scope: Cluster
   158    versions:
   159    - name: v1
   160      schema:
   161        openAPIV3Schema:
   162          type: object
   163          properties:
   164            numArray:
   165              type: array
   166              x-kubernetes-list-type: set
   167              items:
   168                type: object
   169      served: true
   170      storage: true
   171    - name: v2
   172      schema:
   173        openAPIV3Schema:
   174          type: object
   175          properties:
   176            numArray2:
   177              type: array
   178  `
   179  
   180  func TestStatusStrategyValidateUpdate(t *testing.T) {
   181  	crdV1 := &apiextensionsv1.CustomResourceDefinition{}
   182  	err := yaml.Unmarshal([]byte(listTypeResourceSchema), &crdV1)
   183  	if err != nil {
   184  		t.Fatalf("unexpected decoding error: %v", err)
   185  	}
   186  	t.Logf("crd v1 details: %v", crdV1)
   187  	crd := &apiextensions.CustomResourceDefinition{}
   188  	if err = apiextensionsv1.Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(crdV1, crd, nil); err != nil {
   189  		t.Fatalf("unexpected convert error: %v", err)
   190  	}
   191  	t.Logf("crd details: %v", crd)
   192  
   193  	strategy := statusStrategy{}
   194  	kind := schema.GroupVersionKind{
   195  		Version: crd.Spec.Versions[0].Name,
   196  		Kind:    crd.Spec.Names.Kind,
   197  		Group:   crd.Spec.Group,
   198  	}
   199  	strategy.customResourceStrategy.validator.kind = kind
   200  	ss, _ := structuralschema.NewStructural(crd.Spec.Versions[0].Schema.OpenAPIV3Schema)
   201  	strategy.structuralSchema = ss
   202  
   203  	ctx := context.TODO()
   204  
   205  	tcs := []struct {
   206  		name    string
   207  		old     *unstructured.Unstructured
   208  		obj     *unstructured.Unstructured
   209  		isValid bool
   210  	}{
   211  		{
   212  			name:    "bothValid",
   213  			old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "numArray": []interface{}{1, 2}}},
   214  			obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "numArray": []interface{}{1, 3}, "metadata": map[string]interface{}{"resourceVersion": "1"}}},
   215  			isValid: true,
   216  		},
   217  		{
   218  			name:    "change to invalid",
   219  			old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "old", "numArray": []interface{}{1, 2}}},
   220  			obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "new", "numArray": []interface{}{1, 1}, "metadata": map[string]interface{}{"resourceVersion": "1"}}},
   221  			isValid: false,
   222  		},
   223  		{
   224  			name:    "change to valid",
   225  			old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "new", "numArray": []interface{}{1, 1}}},
   226  			obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "old", "numArray": []interface{}{1, 2}, "metadata": map[string]interface{}{"resourceVersion": "1"}}},
   227  			isValid: true,
   228  		},
   229  		{
   230  			name:    "keeps invalid",
   231  			old:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "new", "numArray": []interface{}{1, 1}}},
   232  			obj:     &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "test/v1", "kind": "Foo", "spec": "old", "numArray": []interface{}{1, 1}, "metadata": map[string]interface{}{"resourceVersion": "1"}}},
   233  			isValid: true,
   234  		},
   235  	}
   236  
   237  	for _, tc := range tcs {
   238  		errs := strategy.ValidateUpdate(ctx, tc.obj, tc.old)
   239  		if tc.isValid && len(errs) > 0 {
   240  			t.Errorf("%v: unexpected error: %v", tc.name, errs)
   241  		}
   242  		if !tc.isValid && len(errs) == 0 {
   243  			t.Errorf("%v: unexpected non-error", tc.name)
   244  		}
   245  	}
   246  }
   247  

View as plain text