k8s.io/apiserver@v0.31.1/pkg/storage/value/transformer_test.go (about) 1 /* 2 Copyright 2017 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 value 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "flag" 24 "fmt" 25 "regexp" 26 "strings" 27 "testing" 28 29 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 30 "k8s.io/component-base/metrics/legacyregistry" 31 "k8s.io/component-base/metrics/testutil" 32 "k8s.io/klog/v2" 33 ) 34 35 type testTransformer struct { 36 from, to []byte 37 err error 38 stale bool 39 receivedFrom, receivedTo []byte 40 } 41 42 func (t *testTransformer) TransformFromStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, stale bool, err error) { 43 t.receivedFrom = data 44 return t.from, t.stale, t.err 45 } 46 47 func (t *testTransformer) TransformToStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, err error) { 48 t.receivedTo = data 49 return t.to, t.err 50 } 51 52 func TestPrefixFrom(t *testing.T) { 53 testErr := fmt.Errorf("test error") 54 transformErr := fmt.Errorf("transform error") 55 transformers := []PrefixTransformer{ 56 {Prefix: []byte("first:"), Transformer: &testTransformer{from: []byte("value1")}}, 57 {Prefix: []byte("second:"), Transformer: &testTransformer{from: []byte("value2")}}, 58 {Prefix: []byte("fails:"), Transformer: &testTransformer{err: transformErr}}, 59 {Prefix: []byte("stale:"), Transformer: &testTransformer{from: []byte("value3"), stale: true}}, 60 } 61 p := NewPrefixTransformers(testErr, transformers...) 62 63 testCases := []struct { 64 input []byte 65 expect []byte 66 stale bool 67 err error 68 match int 69 }{ 70 {[]byte("first:value"), []byte("value1"), false, nil, 0}, 71 {[]byte("second:value"), []byte("value2"), true, nil, 1}, 72 {[]byte("third:value"), nil, false, testErr, -1}, 73 {[]byte("fails:value"), nil, false, transformErr, 2}, 74 {[]byte("stale:value"), []byte("value3"), true, nil, 3}, 75 } 76 for i, test := range testCases { 77 got, stale, err := p.TransformFromStorage(context.Background(), test.input, nil) 78 if err != test.err || stale != test.stale || !bytes.Equal(got, test.expect) { 79 t.Errorf("%d: unexpected out: %q %t %#v", i, string(got), stale, err) 80 continue 81 } 82 if test.match != -1 && !bytes.Equal([]byte("value"), transformers[test.match].Transformer.(*testTransformer).receivedFrom) { 83 t.Errorf("%d: unexpected value received by transformer: %s", i, transformers[test.match].Transformer.(*testTransformer).receivedFrom) 84 } 85 } 86 } 87 88 func TestPrefixTo(t *testing.T) { 89 testErr := fmt.Errorf("test error") 90 transformErr := fmt.Errorf("test error") 91 testCases := []struct { 92 transformers []PrefixTransformer 93 expect []byte 94 err error 95 }{ 96 {[]PrefixTransformer{{Prefix: []byte("first:"), Transformer: &testTransformer{to: []byte("value1")}}}, []byte("first:value1"), nil}, 97 {[]PrefixTransformer{{Prefix: []byte("second:"), Transformer: &testTransformer{to: []byte("value2")}}}, []byte("second:value2"), nil}, 98 {[]PrefixTransformer{{Prefix: []byte("fails:"), Transformer: &testTransformer{err: transformErr}}}, nil, transformErr}, 99 } 100 for i, test := range testCases { 101 p := NewPrefixTransformers(testErr, test.transformers...) 102 got, err := p.TransformToStorage(context.Background(), []byte("value"), nil) 103 if err != test.err || !bytes.Equal(got, test.expect) { 104 t.Errorf("%d: unexpected out: %q %#v", i, string(got), err) 105 continue 106 } 107 if !bytes.Equal([]byte("value"), test.transformers[0].Transformer.(*testTransformer).receivedTo) { 108 t.Errorf("%d: unexpected value received by transformer: %s", i, test.transformers[0].Transformer.(*testTransformer).receivedTo) 109 } 110 } 111 } 112 113 func TestPrefixFromMetrics(t *testing.T) { 114 testErr := fmt.Errorf("test error") 115 transformerErr := fmt.Errorf("test error") 116 identityTransformer := PrefixTransformer{Prefix: []byte{}, Transformer: &testTransformer{from: []byte("value1")}} 117 identityTransformerErr := PrefixTransformer{Prefix: []byte{}, Transformer: &testTransformer{err: transformerErr}} 118 otherTransformer := PrefixTransformer{Prefix: []byte("other:"), Transformer: &testTransformer{from: []byte("value1")}} 119 otherTransformerErr := PrefixTransformer{Prefix: []byte("other:"), Transformer: &testTransformer{err: transformerErr}} 120 121 testCases := []struct { 122 desc string 123 input []byte 124 prefix Transformer 125 metrics []string 126 want string 127 err error 128 }{ 129 { 130 desc: "identity prefix", 131 input: []byte("value"), 132 prefix: NewPrefixTransformers(testErr, identityTransformer, otherTransformer), 133 metrics: []string{ 134 "apiserver_storage_transformation_operations_total", 135 }, 136 want: ` 137 # HELP apiserver_storage_transformation_operations_total [ALPHA] Total number of transformations. Successful transformation will have a status 'OK' and a varied status string when the transformation fails. This status and transformation_type fields may be used for alerting on encryption/decryption failure using transformation_type from_storage for decryption and to_storage for encryption 138 # TYPE apiserver_storage_transformation_operations_total counter 139 apiserver_storage_transformation_operations_total{status="OK",transformation_type="from_storage",transformer_prefix="identity"} 1 140 `, 141 err: nil, 142 }, 143 { 144 desc: "other prefix (ok)", 145 input: []byte("other:value"), 146 prefix: NewPrefixTransformers(testErr, identityTransformerErr, otherTransformer), 147 metrics: []string{ 148 "apiserver_storage_transformation_operations_total", 149 }, 150 want: ` 151 # HELP apiserver_storage_transformation_operations_total [ALPHA] Total number of transformations. Successful transformation will have a status 'OK' and a varied status string when the transformation fails. This status and transformation_type fields may be used for alerting on encryption/decryption failure using transformation_type from_storage for decryption and to_storage for encryption 152 # TYPE apiserver_storage_transformation_operations_total counter 153 apiserver_storage_transformation_operations_total{status="OK",transformation_type="from_storage",transformer_prefix="other:"} 1 154 `, 155 err: nil, 156 }, 157 { 158 desc: "other prefix (error)", 159 input: []byte("other:value"), 160 prefix: NewPrefixTransformers(testErr, identityTransformerErr, otherTransformerErr), 161 metrics: []string{ 162 "apiserver_storage_transformation_operations_total", 163 }, 164 want: ` 165 # HELP apiserver_storage_transformation_operations_total [ALPHA] Total number of transformations. Successful transformation will have a status 'OK' and a varied status string when the transformation fails. This status and transformation_type fields may be used for alerting on encryption/decryption failure using transformation_type from_storage for decryption and to_storage for encryption 166 # TYPE apiserver_storage_transformation_operations_total counter 167 apiserver_storage_transformation_operations_total{status="unknown-non-grpc",transformation_type="from_storage",transformer_prefix="other:"} 1 168 `, 169 err: nil, 170 }, 171 { 172 desc: "unknown prefix", 173 input: []byte("foo:value"), 174 prefix: NewPrefixTransformers(testErr, identityTransformerErr, otherTransformer), 175 metrics: []string{ 176 "apiserver_storage_transformation_operations_total", 177 }, 178 want: ` 179 # HELP apiserver_storage_transformation_operations_total [ALPHA] Total number of transformations. Successful transformation will have a status 'OK' and a varied status string when the transformation fails. This status and transformation_type fields may be used for alerting on encryption/decryption failure using transformation_type from_storage for decryption and to_storage for encryption 180 # TYPE apiserver_storage_transformation_operations_total counter 181 apiserver_storage_transformation_operations_total{status="unknown-non-grpc",transformation_type="from_storage",transformer_prefix="unknown"} 1 182 `, 183 err: nil, 184 }, 185 } 186 187 RegisterMetrics() 188 transformerOperationsTotal.Reset() 189 190 for _, tc := range testCases { 191 t.Run(tc.desc, func(t *testing.T) { 192 tc.prefix.TransformFromStorage(context.Background(), tc.input, nil) 193 defer transformerOperationsTotal.Reset() 194 if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.want), tc.metrics...); err != nil { 195 t.Fatal(err) 196 } 197 }) 198 } 199 } 200 201 func TestPrefixToMetrics(t *testing.T) { 202 testErr := fmt.Errorf("test error") 203 transformerErr := fmt.Errorf("test error") 204 otherTransformer := PrefixTransformer{Prefix: []byte("other:"), Transformer: &testTransformer{from: []byte("value1")}} 205 otherTransformerErr := PrefixTransformer{Prefix: []byte("other:"), Transformer: &testTransformer{err: transformerErr}} 206 207 testCases := []struct { 208 desc string 209 input []byte 210 prefix Transformer 211 metrics []string 212 want string 213 err error 214 }{ 215 { 216 desc: "ok", 217 input: []byte("value"), 218 prefix: NewPrefixTransformers(testErr, otherTransformer), 219 metrics: []string{ 220 "apiserver_storage_transformation_operations_total", 221 }, 222 want: ` 223 # HELP apiserver_storage_transformation_operations_total [ALPHA] Total number of transformations. Successful transformation will have a status 'OK' and a varied status string when the transformation fails. This status and transformation_type fields may be used for alerting on encryption/decryption failure using transformation_type from_storage for decryption and to_storage for encryption 224 # TYPE apiserver_storage_transformation_operations_total counter 225 apiserver_storage_transformation_operations_total{status="OK",transformation_type="to_storage",transformer_prefix="other:"} 1 226 `, 227 err: nil, 228 }, 229 { 230 desc: "error", 231 input: []byte("value"), 232 prefix: NewPrefixTransformers(testErr, otherTransformerErr), 233 metrics: []string{ 234 "apiserver_storage_transformation_operations_total", 235 }, 236 want: ` 237 # HELP apiserver_storage_transformation_operations_total [ALPHA] Total number of transformations. Successful transformation will have a status 'OK' and a varied status string when the transformation fails. This status and transformation_type fields may be used for alerting on encryption/decryption failure using transformation_type from_storage for decryption and to_storage for encryption 238 # TYPE apiserver_storage_transformation_operations_total counter 239 apiserver_storage_transformation_operations_total{status="unknown-non-grpc",transformation_type="to_storage",transformer_prefix="other:"} 1 240 `, 241 err: nil, 242 }, 243 } 244 245 RegisterMetrics() 246 transformerOperationsTotal.Reset() 247 248 for _, tc := range testCases { 249 t.Run(tc.desc, func(t *testing.T) { 250 tc.prefix.TransformToStorage(context.Background(), tc.input, nil) 251 defer transformerOperationsTotal.Reset() 252 if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.want), tc.metrics...); err != nil { 253 t.Fatal(err) 254 } 255 }) 256 } 257 } 258 259 func TestLogTransformErr(t *testing.T) { 260 klog.InitFlags(nil) 261 tests := []struct { 262 name string 263 ctx context.Context 264 err error 265 message string 266 expectedLog string 267 }{ 268 { 269 name: "log error with request info", 270 ctx: genericapirequest.WithRequestInfo(testContext(t), &genericapirequest.RequestInfo{ 271 APIGroup: "awesome.bears.com", 272 APIVersion: "v1", 273 Resource: "pandas", 274 Subresource: "status", 275 Namespace: "kube-system", 276 Name: "panda", 277 Verb: "update", 278 }), 279 err: errors.New("encryption failed"), 280 message: "failed to encrypt data", 281 expectedLog: "\"failed to encrypt data\" err=\"encryption failed\" group=\"awesome.bears.com\" version=\"v1\" resource=\"pandas\" subresource=\"status\" verb=\"update\" namespace=\"kube-system\" name=\"panda\"\n", 282 }, 283 { 284 name: "log error without request info", 285 ctx: context.Background(), 286 err: errors.New("decryption failed"), 287 message: "failed to decrypt data", 288 expectedLog: "\"failed to decrypt data\" err=\"decryption failed\" group=\"\" version=\"\" resource=\"\" subresource=\"\" verb=\"\" namespace=\"\" name=\"\"\n", 289 }, 290 } 291 292 for _, tt := range tests { 293 t.Run(tt.name, func(t *testing.T) { 294 var buf bytes.Buffer 295 flag.Set("v", "6") 296 flag.Parse() 297 klog.SetOutput(&buf) 298 klog.LogToStderr(false) 299 defer klog.LogToStderr(true) 300 301 logTransformErr(tt.ctx, tt.err, tt.message) 302 303 // remove timestamp and goroutine id from log message to make it easier to compare 304 gotLog := regexp.MustCompile(`\w+ \d+:\d+:\d+\.\d+.*\d+.*transformer_test.go:\d+].`).ReplaceAllString(buf.String(), "") 305 306 if gotLog != tt.expectedLog { 307 t.Errorf("expected log message %q, got %q", tt.expectedLog, gotLog) 308 } 309 }) 310 } 311 } 312 313 func testContext(t *testing.T) context.Context { 314 ctx, cancel := context.WithCancel(context.Background()) 315 t.Cleanup(cancel) 316 return ctx 317 }