Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade ConversionReview e2e test image to also support v1 #81314

Merged
merged 1 commit into from Aug 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion test/images/agnhost/crd-conversion-webhook/converter/BUILD
Expand Up @@ -9,11 +9,13 @@ go_library(
importpath = "k8s.io/kubernetes/test/images/agnhost/crd-conversion-webhook/converter",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/github.com/munnerz/goautoneg:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
Expand All @@ -24,7 +26,7 @@ go_test(
srcs = ["converter_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
Expand Down
122 changes: 71 additions & 51 deletions test/images/agnhost/crd-conversion-webhook/converter/converter_test.go
Expand Up @@ -17,21 +17,47 @@ limitations under the License.
package converter

import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
)

func TestConverter(t *testing.T) {
sampleObj := `kind: ConversionReview
apiVersion: apiextensions.k8s.io/v1beta1
func TestConverterYAML(t *testing.T) {
cases := []struct {
apiVersion string
contentType string
expected400Err string
}{
{
apiVersion: "apiextensions.k8s.io/v1beta1",
contentType: "application/json",
expected400Err: "json parse error",
},
{
apiVersion: "apiextensions.k8s.io/v1beta1",
contentType: "application/yaml",
},
{
apiVersion: "apiextensions.k8s.io/v1",
contentType: "application/json",
expected400Err: "json parse error",
},
{
apiVersion: "apiextensions.k8s.io/v1",
contentType: "application/yaml",
},
}
sampleObjTemplate := `kind: ConversionReview
apiVersion: %s
request:
uid: 0000-0000-0000-0000
desiredAPIVersion: stable.example.com/v2
Expand All @@ -45,53 +71,47 @@ request:
image: my-awesome-cron-image
hostPort: "localhost:7070"
`
// First try json, it should fail as the data is taml
response := httptest.NewRecorder()
request, err := http.NewRequest("POST", "/convert", strings.NewReader(sampleObj))
if err != nil {
t.Fatal(err)
}
request.Header.Add("Content-Type", "application/json")
ServeExampleConvert(response, request)
convertReview := v1beta1.ConversionReview{}
scheme := runtime.NewScheme()
jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
if _, _, err := jsonSerializer.Decode(response.Body.Bytes(), nil, &convertReview); err != nil {
t.Fatal(err)
}
if convertReview.Response.Result.Status != v1.StatusFailure {
t.Fatalf("expected the operation to fail when yaml is provided with json header")
} else if !strings.Contains(convertReview.Response.Result.Message, "json parse error") {
t.Fatalf("expected to fail on json parser, but it failed with: %v", convertReview.Response.Result.Message)
}
for _, tc := range cases {
t.Run(tc.apiVersion+" "+tc.contentType, func(t *testing.T) {
sampleObj := fmt.Sprintf(sampleObjTemplate, tc.apiVersion)
// First try json, it should fail as the data is taml
response := httptest.NewRecorder()
request, err := http.NewRequest("POST", "/convert", strings.NewReader(sampleObj))
if err != nil {
t.Fatal(err)
}
request.Header.Add("Content-Type", tc.contentType)
ServeExampleConvert(response, request)
convertReview := apiextensionsv1.ConversionReview{}
scheme := runtime.NewScheme()
if len(tc.expected400Err) > 0 {
body := response.Body.Bytes()
if !bytes.Contains(body, []byte(tc.expected400Err)) {
t.Fatalf("expected to fail on '%s', but it failed with: %s", tc.expected400Err, string(body))
}
return
}

// Now try yaml, and it should successfully convert
response = httptest.NewRecorder()
request, err = http.NewRequest("POST", "/convert", strings.NewReader(sampleObj))
if err != nil {
t.Fatal(err)
}
request.Header.Add("Content-Type", "application/yaml")
ServeExampleConvert(response, request)
convertReview = v1beta1.ConversionReview{}
yamlSerializer := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme, scheme)
if _, _, err := yamlSerializer.Decode(response.Body.Bytes(), nil, &convertReview); err != nil {
t.Fatalf("cannot decode data: \n %v\n Error: %v", response.Body, err)
}
if convertReview.Response.Result.Status != v1.StatusSuccess {
t.Fatalf("cr conversion failed: %v", convertReview.Response)
}
convertedObj := unstructured.Unstructured{}
if _, _, err := yamlSerializer.Decode(convertReview.Response.ConvertedObjects[0].Raw, nil, &convertedObj); err != nil {
t.Fatal(err)
}
if e, a := "stable.example.com/v2", convertedObj.GetAPIVersion(); e != a {
t.Errorf("expected= %v, actual= %v", e, a)
}
if e, a := "localhost", convertedObj.Object["host"]; e != a {
t.Errorf("expected= %v, actual= %v", e, a)
}
if e, a := "7070", convertedObj.Object["port"]; e != a {
t.Errorf("expected= %v, actual= %v", e, a)
yamlSerializer := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme, scheme)
if _, _, err := yamlSerializer.Decode(response.Body.Bytes(), nil, &convertReview); err != nil {
t.Fatalf("cannot decode data: \n %v\n Error: %v", response.Body, err)
}
if convertReview.Response.Result.Status != v1.StatusSuccess {
t.Fatalf("cr conversion failed: %v", convertReview.Response)
}
convertedObj := unstructured.Unstructured{}
if _, _, err := yamlSerializer.Decode(convertReview.Response.ConvertedObjects[0].Raw, nil, &convertedObj); err != nil {
t.Fatal(err)
}
if e, a := "stable.example.com/v2", convertedObj.GetAPIVersion(); e != a {
t.Errorf("expected= %v, actual= %v", e, a)
}
if e, a := "localhost", convertedObj.Object["host"]; e != a {
t.Errorf("expected= %v, actual= %v", e, a)
}
if e, a := "7070", convertedObj.Object["port"]; e != a {
t.Errorf("expected= %v, actual= %v", e, a)
}
})
}
}
122 changes: 96 additions & 26 deletions test/images/agnhost/crd-conversion-webhook/converter/framework.go
Expand Up @@ -26,29 +26,19 @@ import (

"k8s.io/klog"

"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/runtime/serializer/json"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)

// convertFunc is the user defined function for any conversion. The code in this file is a
// template that can be use for any CR conversion given this function.
type convertFunc func(Object *unstructured.Unstructured, version string) (*unstructured.Unstructured, metav1.Status)

// conversionResponseFailureWithMessagef is a helper function to create an AdmissionResponse
// with a formatted embedded error message.
func conversionResponseFailureWithMessagef(msg string, params ...interface{}) *v1beta1.ConversionResponse {
return &v1beta1.ConversionResponse{
Result: metav1.Status{
Message: fmt.Sprintf(msg, params...),
Status: metav1.StatusFailure,
},
}

}

func statusErrorWithMessage(msg string, params ...interface{}) metav1.Status {
return metav1.Status{
Message: fmt.Sprintf(msg, params...),
Expand All @@ -62,15 +52,20 @@ func statusSucceed() metav1.Status {
}
}

// doConversion converts the requested object given the conversion function and returns a conversion response.
// failures will be reported as Reason in the conversion response.
func doConversion(convertRequest *v1beta1.ConversionRequest, convert convertFunc) *v1beta1.ConversionResponse {
// doConversionV1beta1 converts the requested objects in the v1beta1 ConversionRequest using the given conversion function and
// returns a conversion response. Failures are reported with the Reason in the conversion response.
func doConversionV1beta1(convertRequest *v1beta1.ConversionRequest, convert convertFunc) *v1beta1.ConversionResponse {
var convertedObjects []runtime.RawExtension
for _, obj := range convertRequest.Objects {
cr := unstructured.Unstructured{}
if err := cr.UnmarshalJSON(obj.Raw); err != nil {
klog.Error(err)
return conversionResponseFailureWithMessagef("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err)
return &v1beta1.ConversionResponse{
Result: metav1.Status{
Message: fmt.Sprintf("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err),
Status: metav1.StatusFailure,
},
}
}
convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
if status.Status != metav1.StatusSuccess {
Expand All @@ -88,6 +83,37 @@ func doConversion(convertRequest *v1beta1.ConversionRequest, convert convertFunc
}
}

// doConversionV1 converts the requested objects in the v1 ConversionRequest using the given conversion function and
// returns a conversion response. Failures are reported with the Reason in the conversion response.
func doConversionV1(convertRequest *v1.ConversionRequest, convert convertFunc) *v1.ConversionResponse {
var convertedObjects []runtime.RawExtension
for _, obj := range convertRequest.Objects {
cr := unstructured.Unstructured{}
if err := cr.UnmarshalJSON(obj.Raw); err != nil {
klog.Error(err)
return &v1.ConversionResponse{
Result: metav1.Status{
Message: fmt.Sprintf("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err),
Status: metav1.StatusFailure,
},
}
}
convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
if status.Status != metav1.StatusSuccess {
klog.Error(status.String())
return &v1.ConversionResponse{
Result: status,
}
}
convertedCR.SetAPIVersion(convertRequest.DesiredAPIVersion)
convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedCR})
}
return &v1.ConversionResponse{
ConvertedObjects: convertedObjects,
Result: statusSucceed(),
}
}

func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
var body []byte
if r.Body != nil {
Expand All @@ -106,18 +132,52 @@ func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
}

klog.V(2).Infof("handling request: %v", body)
convertReview := v1beta1.ConversionReview{}
if _, _, err := serializer.Decode(body, nil, &convertReview); err != nil {
obj, gvk, err := serializer.Decode(body, nil, nil)
if err != nil {
msg := fmt.Sprintf("failed to deserialize body (%v) with error %v", string(body), err)
klog.Error(err)
convertReview.Response = conversionResponseFailureWithMessagef("failed to deserialize body (%v) with error %v", string(body), err)
} else {
convertReview.Response = doConversion(convertReview.Request, convert)
convertReview.Response.UID = convertReview.Request.UID
http.Error(w, msg, http.StatusBadRequest)
return
}
klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))

// reset the request, it is not needed in a response.
convertReview.Request = &v1beta1.ConversionRequest{}
var responseObj runtime.Object
switch *gvk {
case v1beta1.SchemeGroupVersion.WithKind("ConversionReview"):
convertReview, ok := obj.(*v1beta1.ConversionReview)
if !ok {
msg := fmt.Sprintf("Expected v1beta1.ConversionReview but got: %T", obj)
klog.Errorf(msg)
http.Error(w, msg, http.StatusBadRequest)
return
}
convertReview.Response = doConversionV1beta1(convertReview.Request, convert)
convertReview.Response.UID = convertReview.Request.UID
klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))

// reset the request, it is not needed in a response.
convertReview.Request = &v1beta1.ConversionRequest{}
responseObj = convertReview
case v1.SchemeGroupVersion.WithKind("ConversionReview"):
convertReview, ok := obj.(*v1.ConversionReview)
if !ok {
msg := fmt.Sprintf("Expected v1.ConversionReview but got: %T", obj)
klog.Errorf(msg)
http.Error(w, msg, http.StatusBadRequest)
return
}
convertReview.Response = doConversionV1(convertReview.Request, convert)
convertReview.Response.UID = convertReview.Request.UID
klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))

// reset the request, it is not needed in a response.
convertReview.Request = &v1.ConversionRequest{}
responseObj = convertReview
default:
msg := fmt.Sprintf("Unsupported group version kind: %v", gvk)
klog.Error(err)
http.Error(w, msg, http.StatusBadRequest)
return
}

accept := r.Header.Get("Accept")
outSerializer := getOutputSerializer(accept)
Expand All @@ -127,7 +187,7 @@ func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
http.Error(w, msg, http.StatusBadRequest)
return
}
err := outSerializer.Encode(&convertReview, w)
err = outSerializer.Encode(responseObj, w)
if err != nil {
klog.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
Expand All @@ -145,6 +205,16 @@ type mediaType struct {
}

var scheme = runtime.NewScheme()

func init() {
addToScheme(scheme)
}

func addToScheme(scheme *runtime.Scheme) {
utilruntime.Must(v1.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
}

var serializers = map[mediaType]runtime.Serializer{
{"application", "json"}: json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false),
{"application", "yaml"}: json.NewYAMLSerializer(json.DefaultMetaFactory, scheme, scheme),
Expand Down
1 change: 1 addition & 0 deletions test/images/agnhost/webhook/BUILD
Expand Up @@ -23,6 +23,7 @@ go_library(
"//staging/src/k8s.io/api/admissionregistration/v1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
Expand Down