k8s.io/apiserver@v0.31.1/pkg/storage/cacher/caching_object_test.go (about) 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 cacher 18 19 import ( 20 "bytes" 21 "fmt" 22 "io" 23 "sync" 24 "sync/atomic" 25 "testing" 26 27 v1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/meta" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 ) 32 33 type mockEncoder struct { 34 identifier runtime.Identifier 35 expectedResult string 36 expectedError error 37 38 callsNumber int32 39 } 40 41 func newMockEncoder(id, result string, err error) *mockEncoder { 42 return &mockEncoder{ 43 identifier: runtime.Identifier(id), 44 expectedResult: result, 45 expectedError: err, 46 } 47 } 48 49 func (e *mockEncoder) encode(_ runtime.Object, w io.Writer) error { 50 atomic.AddInt32(&e.callsNumber, 1) 51 if e.expectedError != nil { 52 return e.expectedError 53 } 54 _, err := w.Write([]byte(e.expectedResult)) 55 return err 56 } 57 58 func TestCachingObject(t *testing.T) { 59 object, err := newCachingObject(&v1.Pod{}) 60 if err != nil { 61 t.Fatalf("couldn't create cachingObject: %v", err) 62 } 63 64 encoders := []*mockEncoder{ 65 newMockEncoder("1", "result1", nil), 66 newMockEncoder("2", "", fmt.Errorf("mock error")), 67 newMockEncoder("3", "result3", nil), 68 } 69 70 for _, encoder := range encoders { 71 buffer := bytes.NewBuffer(nil) 72 err := object.CacheEncode(encoder.identifier, encoder.encode, buffer) 73 if a, e := err, encoder.expectedError; e != a { 74 t.Errorf("%s: unexpected error: %v, expected: %v", encoder.identifier, a, e) 75 } 76 if a, e := buffer.String(), encoder.expectedResult; e != a { 77 t.Errorf("%s: unexpected result: %s, expected: %s", encoder.identifier, a, e) 78 } 79 } 80 for _, encoder := range encoders { 81 if encoder.callsNumber != 1 { 82 t.Errorf("%s: unexpected number of encode() calls: %d", encoder.identifier, encoder.callsNumber) 83 } 84 } 85 } 86 87 func TestCachingObjectFieldAccessors(t *testing.T) { 88 object, err := newCachingObject(&v1.Pod{}) 89 if err != nil { 90 t.Fatalf("couldn't create cachingObject: %v", err) 91 } 92 93 // Given accessors for all fields implement the same logic, 94 // we are choosing an arbitrary one to test. 95 namespace := "namespace" 96 object.SetNamespace(namespace) 97 98 encodeNamespace := func(obj runtime.Object, w io.Writer) error { 99 accessor, err := meta.Accessor(obj) 100 if err != nil { 101 t.Fatalf("failed to get accessor for %#v: %v", obj, err) 102 } 103 _, err = w.Write([]byte(accessor.GetNamespace())) 104 return err 105 } 106 buffer := bytes.NewBuffer(nil) 107 if err := object.CacheEncode("", encodeNamespace, buffer); err != nil { 108 t.Errorf("unexpected error: %v", err) 109 } 110 if a, e := buffer.String(), namespace; a != e { 111 t.Errorf("unexpected serialization: %s, expected: %s", a, e) 112 } 113 114 // GetObject should also set namespace. 115 buffer.Reset() 116 if err := encodeNamespace(object.GetObject(), buffer); err != nil { 117 t.Errorf("unexpected error: %v", err) 118 } 119 if a, e := buffer.String(), namespace; a != e { 120 t.Errorf("unexpected serialization: %s, expected: %s", a, e) 121 } 122 } 123 124 func TestCachingObjectRaces(t *testing.T) { 125 object, err := newCachingObject(&v1.Pod{}) 126 if err != nil { 127 t.Fatalf("couldn't create cachingObject: %v", err) 128 } 129 130 encoders := []*mockEncoder{} 131 for i := 0; i < 10; i++ { 132 encoder := newMockEncoder(fmt.Sprintf("%d", i), "result", nil) 133 encoders = append(encoders, encoder) 134 } 135 136 numWorkers := 1000 137 wg := &sync.WaitGroup{} 138 wg.Add(numWorkers) 139 140 for i := 0; i < numWorkers; i++ { 141 go func() { 142 defer wg.Done() 143 object.SetNamespace("namespace") 144 buffer := bytes.NewBuffer(nil) 145 for _, encoder := range encoders { 146 buffer.Reset() 147 if err := object.CacheEncode(encoder.identifier, encoder.encode, buffer); err != nil { 148 t.Errorf("unexpected error: %v", err) 149 } 150 if callsNumber := atomic.LoadInt32(&encoder.callsNumber); callsNumber != 1 { 151 t.Errorf("unexpected number of serializations: %d", callsNumber) 152 } 153 } 154 accessor, err := meta.Accessor(object.GetObject()) 155 if err != nil { 156 t.Errorf("failed to get accessor: %v", err) 157 return 158 } 159 if namespace := accessor.GetNamespace(); namespace != "namespace" { 160 t.Errorf("unexpected namespace: %s", namespace) 161 } 162 }() 163 } 164 wg.Wait() 165 } 166 167 func TestCachingObjectLazyDeepCopy(t *testing.T) { 168 pod := &v1.Pod{ 169 ObjectMeta: metav1.ObjectMeta{ 170 Name: "name", 171 ResourceVersion: "123", 172 }, 173 } 174 object, err := newCachingObject(pod) 175 if err != nil { 176 t.Fatalf("couldn't create cachingObject: %v", err) 177 } 178 179 if object.deepCopied != false { 180 t.Errorf("object deep-copied without the need") 181 } 182 183 object.SetResourceVersion("123") 184 if object.deepCopied != false { 185 t.Errorf("object deep-copied on no-op change") 186 } 187 object.SetResourceVersion("234") 188 if object.deepCopied != true { 189 t.Errorf("object not deep-copied on change") 190 } 191 }