Skip to content

Commit

Permalink
Merge pull request #81314 from jpbetz/crd-test-image
Browse files Browse the repository at this point in the history
Upgrade ConversionReview e2e test image to also support v1
  • Loading branch information
k8s-ci-robot committed Aug 18, 2019
2 parents f12a40d + 3f519b0 commit 091a5dc
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 92 deletions.
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

0 comments on commit 091a5dc

Please sign in to comment.