kubernetes webhook_converter_test 源码
kubernetes webhook_converter_test 代码
文件路径:/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion/webhook_converter_test.go
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion
import (
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/validation"
)
func TestRestoreObjectMeta(t *testing.T) {
tests := []struct {
name string
original map[string]interface{}
converted map[string]interface{}
expected map[string]interface{}
expectedError bool
}{
{"no converted metadata",
map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
map[string]interface{}{"spec": map[string]interface{}{}},
map[string]interface{}{"spec": map[string]interface{}{}},
true,
},
{"invalid converted metadata",
map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": []interface{}{"foo"}},
map[string]interface{}{"metadata": []interface{}{"foo"}},
true,
},
{"no original metadata",
map[string]interface{}{"spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
false,
},
{"invalid original metadata",
map[string]interface{}{"metadata": []interface{}{"foo"}},
map[string]interface{}{"metadata": map[string]interface{}{}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": []interface{}{"foo"}, "spec": map[string]interface{}{}},
true,
},
{"changed label, annotations and non-label",
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
"labels": map[string]interface{}{"a": "A", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "2"},
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "abc",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
false,
},
{"added labels and annotations",
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "abc",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
false,
},
{"added labels and annotations, with nil before",
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
"labels": nil,
"annotations": nil,
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "abc",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
false,
},
{"removed labels and annotations",
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "abc",
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
}, "spec": map[string]interface{}{}},
false,
},
{"nil'ed labels and annotations",
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
"labels": map[string]interface{}{"a": "AA", "b": "B"},
"annotations": map[string]interface{}{"a": "1", "b": "22"},
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "abc",
"labels": nil,
"annotations": nil,
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
}, "spec": map[string]interface{}{}},
false,
},
{"added labels and annotations",
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "abc",
"labels": map[string]interface{}{"a": nil, "b": "B"},
"annotations": map[string]interface{}{"a": nil, "b": "22"},
}, "spec": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{
"foo": "bar",
}, "spec": map[string]interface{}{}},
true,
},
{"invalid label key",
map[string]interface{}{"metadata": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"some/non-qualified/label": "x"}}},
map[string]interface{}{"metadata": map[string]interface{}{}},
true,
},
{"invalid annotation key",
map[string]interface{}{"metadata": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"some/non-qualified/label": "x"}}},
map[string]interface{}{"metadata": map[string]interface{}{}},
true,
},
{"invalid label value",
map[string]interface{}{"metadata": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"foo": "üäö"}}},
map[string]interface{}{"metadata": map[string]interface{}{}},
true,
},
{"too big label value",
map[string]interface{}{"metadata": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{"labels": map[string]interface{}{"foo": strings.Repeat("x", validation.LabelValueMaxLength+1)}}},
map[string]interface{}{"metadata": map[string]interface{}{}},
true,
},
{"too big annotation value",
map[string]interface{}{"metadata": map[string]interface{}{}},
map[string]interface{}{"metadata": map[string]interface{}{"annotations": map[string]interface{}{"foo": strings.Repeat("x", 256*(1<<10)+1)}}},
map[string]interface{}{"metadata": map[string]interface{}{}},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := restoreObjectMeta(&unstructured.Unstructured{Object: tt.original}, &unstructured.Unstructured{Object: tt.converted}); err == nil && tt.expectedError {
t.Fatalf("expected error, but didn't get one")
} else if err != nil && !tt.expectedError {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(tt.converted, tt.expected) {
t.Errorf("unexpected result: %s", diff.ObjectDiff(tt.expected, tt.converted))
}
})
}
}
func TestGetObjectsToConvert(t *testing.T) {
v1Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v1", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv1"}}}
v2Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v2", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv2"}}}
v3Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v3", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv3"}}}
testcases := []struct {
Name string
Object runtime.Object
APIVersion string
ExpectObjects []runtime.RawExtension
}{
{
Name: "empty list",
Object: &unstructured.UnstructuredList{},
APIVersion: "foo/v1",
ExpectObjects: nil,
},
{
Name: "one-item list, in desired version",
Object: &unstructured.UnstructuredList{
Items: []unstructured.Unstructured{*v1Object},
},
APIVersion: "foo/v1",
ExpectObjects: nil,
},
{
Name: "one-item list, not in desired version",
Object: &unstructured.UnstructuredList{
Items: []unstructured.Unstructured{*v2Object},
},
APIVersion: "foo/v1",
ExpectObjects: []runtime.RawExtension{{Object: v2Object}},
},
{
Name: "multi-item list, in desired version",
Object: &unstructured.UnstructuredList{
Items: []unstructured.Unstructured{*v1Object, *v1Object, *v1Object},
},
APIVersion: "foo/v1",
ExpectObjects: nil,
},
{
Name: "multi-item list, mixed versions",
Object: &unstructured.UnstructuredList{
Items: []unstructured.Unstructured{*v1Object, *v2Object, *v3Object},
},
APIVersion: "foo/v1",
ExpectObjects: []runtime.RawExtension{{Object: v2Object}, {Object: v3Object}},
},
{
Name: "single item, in desired version",
Object: v1Object,
APIVersion: "foo/v1",
ExpectObjects: nil,
},
{
Name: "single item, not in desired version",
Object: v2Object,
APIVersion: "foo/v1",
ExpectObjects: []runtime.RawExtension{{Object: v2Object}},
},
}
for _, tc := range testcases {
t.Run(tc.Name, func(t *testing.T) {
if objects := getObjectsToConvert(tc.Object, tc.APIVersion); !reflect.DeepEqual(objects, tc.ExpectObjects) {
t.Errorf("unexpected diff: %s", cmp.Diff(tc.ExpectObjects, objects))
}
})
}
}
func TestCreateConversionReviewObjects(t *testing.T) {
objects := []runtime.RawExtension{
{Object: &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v2", "Kind": "Widget"}}},
}
testcases := []struct {
Name string
Versions []string
ExpectRequest runtime.Object
ExpectResponse runtime.Object
ExpectErr string
}{
{
Name: "no supported versions",
Versions: []string{"vx"},
ExpectErr: "no supported conversion review versions",
},
{
Name: "v1",
Versions: []string{"v1", "v1beta1", "v2"},
ExpectRequest: &v1.ConversionReview{
Request: &v1.ConversionRequest{UID: "uid", DesiredAPIVersion: "foo/v1", Objects: objects},
Response: &v1.ConversionResponse{},
},
ExpectResponse: &v1.ConversionReview{},
},
{
Name: "v1beta1",
Versions: []string{"v1beta1", "v1", "v2"},
ExpectRequest: &v1beta1.ConversionReview{
Request: &v1beta1.ConversionRequest{UID: "uid", DesiredAPIVersion: "foo/v1", Objects: objects},
Response: &v1beta1.ConversionResponse{},
},
ExpectResponse: &v1beta1.ConversionReview{},
},
}
for _, tc := range testcases {
t.Run(tc.Name, func(t *testing.T) {
request, response, err := createConversionReviewObjects(tc.Versions, objects, "foo/v1", "uid")
if err == nil && len(tc.ExpectErr) > 0 {
t.Errorf("expected error, got none")
} else if err != nil && len(tc.ExpectErr) == 0 {
t.Errorf("unexpected error %v", err)
} else if err != nil && !strings.Contains(err.Error(), tc.ExpectErr) {
t.Errorf("expected error containing %q, got %v", tc.ExpectErr, err)
}
if e, a := tc.ExpectRequest, request; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected diff: %s", cmp.Diff(e, a))
}
if e, a := tc.ExpectResponse, response; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected diff: %s", cmp.Diff(e, a))
}
})
}
}
func TestGetConvertedObjectsFromResponse(t *testing.T) {
v1Object := &unstructured.Unstructured{Object: map[string]interface{}{"apiVersion": "foo/v1", "kind": "Widget", "metadata": map[string]interface{}{"name": "myv1"}}}
testcases := []struct {
Name string
Response runtime.Object
ExpectObjects []runtime.RawExtension
ExpectErr string
}{
{
Name: "nil response",
Response: nil,
ExpectErr: "unrecognized response type",
},
{
Name: "unknown type",
Response: &unstructured.Unstructured{},
ExpectErr: "unrecognized response type",
},
{
Name: "minimal valid v1beta1",
Response: &v1beta1.ConversionReview{
// apiVersion/kind were not validated originally, preserve backward compatibility
Response: &v1beta1.ConversionResponse{
// uid was not validated originally, preserve backward compatibility
Result: metav1.Status{Status: metav1.StatusSuccess},
},
},
ExpectObjects: nil,
},
{
Name: "valid v1beta1 with objects",
Response: &v1beta1.ConversionReview{
// apiVersion/kind were not validated originally, preserve backward compatibility
Response: &v1beta1.ConversionResponse{
// uid was not validated originally, preserve backward compatibility
Result: metav1.Status{Status: metav1.StatusSuccess},
ConvertedObjects: []runtime.RawExtension{{Object: v1Object}},
},
},
ExpectObjects: []runtime.RawExtension{{Object: v1Object}},
},
{
Name: "error v1beta1, empty status",
Response: &v1beta1.ConversionReview{
Response: &v1beta1.ConversionResponse{
Result: metav1.Status{Status: ""},
},
},
ExpectErr: `response.result.status was '', not 'Success'`,
},
{
Name: "error v1beta1, failure status",
Response: &v1beta1.ConversionReview{
Response: &v1beta1.ConversionResponse{
Result: metav1.Status{Status: metav1.StatusFailure},
},
},
ExpectErr: `response.result.status was 'Failure', not 'Success'`,
},
{
Name: "error v1beta1, custom status",
Response: &v1beta1.ConversionReview{
Response: &v1beta1.ConversionResponse{
Result: metav1.Status{Status: metav1.StatusFailure, Message: "some failure message"},
},
},
ExpectErr: `some failure message`,
},
{
Name: "invalid v1beta1, no response",
Response: &v1beta1.ConversionReview{},
ExpectErr: "no response provided",
},
{
Name: "minimal valid v1",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusSuccess},
},
},
ExpectObjects: nil,
},
{
Name: "valid v1 with objects",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusSuccess},
ConvertedObjects: []runtime.RawExtension{{Object: v1Object}},
},
},
ExpectObjects: []runtime.RawExtension{{Object: v1Object}},
},
{
Name: "invalid v1, no uid",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
Result: metav1.Status{Status: metav1.StatusSuccess},
},
},
ExpectErr: `expected response.uid="uid"`,
},
{
Name: "invalid v1, no apiVersion",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusSuccess},
},
},
ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
},
{
Name: "invalid v1, no kind",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusSuccess},
},
},
ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
},
{
Name: "invalid v1, mismatched apiVersion",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v2", Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusSuccess},
},
},
ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
},
{
Name: "invalid v1, mismatched kind",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview2"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusSuccess},
},
},
ExpectErr: `expected webhook response of apiextensions.k8s.io/v1, Kind=ConversionReview`,
},
{
Name: "error v1, empty status",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: ""},
},
},
ExpectErr: `response.result.status was '', not 'Success'`,
},
{
Name: "error v1, failure status",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusFailure},
},
},
ExpectErr: `response.result.status was 'Failure', not 'Success'`,
},
{
Name: "error v1, custom status",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
Response: &v1.ConversionResponse{
UID: "uid",
Result: metav1.Status{Status: metav1.StatusFailure, Message: "some failure message"},
},
},
ExpectErr: `some failure message`,
},
{
Name: "invalid v1, no response",
Response: &v1.ConversionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "apiextensions.k8s.io/v1", Kind: "ConversionReview"},
},
ExpectErr: "no response provided",
},
}
for _, tc := range testcases {
t.Run(tc.Name, func(t *testing.T) {
objects, err := getConvertedObjectsFromResponse("uid", tc.Response)
if err == nil && len(tc.ExpectErr) > 0 {
t.Errorf("expected error, got none")
} else if err != nil && len(tc.ExpectErr) == 0 {
t.Errorf("unexpected error %v", err)
} else if err != nil && !strings.Contains(err.Error(), tc.ExpectErr) {
t.Errorf("expected error containing %q, got %v", tc.ExpectErr, err)
}
if !reflect.DeepEqual(objects, tc.ExpectObjects) {
t.Errorf("unexpected diff: %s", cmp.Diff(tc.ExpectObjects, objects))
}
})
}
}
相关信息
相关文章
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦