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  }