-
Notifications
You must be signed in to change notification settings - Fork 38.6k
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
Allow cross-group subresource registrations in the APIInstaller. #21909
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,9 +59,12 @@ func convert(obj runtime.Object) (runtime.Object, error) { | |
|
||
// This creates fake API versions, similar to api/latest.go. | ||
var testAPIGroup = "test.group" | ||
var testAPIGroup2 = "test.group2" | ||
var testInternalGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: runtime.APIVersionInternal} | ||
var testGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version"} | ||
var newGroupVersion = unversioned.GroupVersion{Group: testAPIGroup, Version: "version2"} | ||
var testGroup2Version = unversioned.GroupVersion{Group: testAPIGroup2, Version: "version"} | ||
var testInternalGroup2Version = unversioned.GroupVersion{Group: testAPIGroup2, Version: runtime.APIVersionInternal} | ||
var prefix = "apis" | ||
|
||
var grouplessGroupVersion = unversioned.GroupVersion{Group: "", Version: "v1"} | ||
|
@@ -99,6 +102,11 @@ func interfacesFor(version unversioned.GroupVersion) (*meta.VersionInterfaces, e | |
ObjectConvertor: api.Scheme, | ||
MetadataAccessor: accessor, | ||
}, nil | ||
case testGroup2Version: | ||
return &meta.VersionInterfaces{ | ||
ObjectConvertor: api.Scheme, | ||
MetadataAccessor: accessor, | ||
}, nil | ||
default: | ||
return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, groupVersions) | ||
} | ||
|
@@ -139,11 +147,18 @@ func addTestTypes() { | |
} | ||
api.Scheme.AddKnownTypes(testGroupVersion, | ||
&apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &ListOptions{}, | ||
&api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) | ||
&api.DeleteOptions{}, &apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}, | ||
&SimpleXGSubresource{}) | ||
api.Scheme.AddKnownTypes(testGroupVersion, &api.Pod{}) | ||
api.Scheme.AddKnownTypes(testInternalGroupVersion, | ||
&apiservertesting.Simple{}, &apiservertesting.SimpleList{}, &api.ListOptions{}, | ||
&apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}) | ||
&apiservertesting.SimpleGetOptions{}, &apiservertesting.SimpleRoot{}, | ||
&SimpleXGSubresource{}) | ||
// Register SimpleXGSubresource in both testGroupVersion and testGroup2Version, and also their | ||
// their corresponding internal versions, to verify that the desired group version object is | ||
// served in the tests. | ||
api.Scheme.AddKnownTypes(testGroup2Version, &SimpleXGSubresource{}) | ||
api.Scheme.AddKnownTypes(testInternalGroup2Version, &SimpleXGSubresource{}) | ||
} | ||
|
||
func addNewTestTypes() { | ||
|
@@ -3157,6 +3172,124 @@ func TestUpdateChecksAPIVersion(t *testing.T) { | |
} | ||
} | ||
|
||
// SimpleXGSubresource is a cross group subresource, i.e. the subresource does not belong to the | ||
// same group as its parent resource. | ||
type SimpleXGSubresource struct { | ||
unversioned.TypeMeta `json:",inline"` | ||
api.ObjectMeta `json:"metadata"` | ||
SubresourceInfo string `json:"subresourceInfo,omitempty"` | ||
Labels map[string]string `json:"labels,omitempty"` | ||
} | ||
|
||
func (obj *SimpleXGSubresource) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta } | ||
|
||
type SimpleXGSubresourceRESTStorage struct { | ||
item SimpleXGSubresource | ||
} | ||
|
||
func (storage *SimpleXGSubresourceRESTStorage) New() runtime.Object { | ||
return &SimpleXGSubresource{} | ||
} | ||
|
||
func (storage *SimpleXGSubresourceRESTStorage) Get(ctx api.Context, id string) (runtime.Object, error) { | ||
copied, err := api.Scheme.Copy(&storage.item) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return copied, nil | ||
} | ||
|
||
func TestXGSubresource(t *testing.T) { | ||
container := restful.NewContainer() | ||
container.Router(restful.CurlyRouter{}) | ||
mux := container.ServeMux | ||
|
||
itemID := "theID" | ||
subresourceStorage := &SimpleXGSubresourceRESTStorage{ | ||
item: SimpleXGSubresource{ | ||
SubresourceInfo: "foo", | ||
}, | ||
} | ||
storage := map[string]rest.Storage{ | ||
"simple": &SimpleRESTStorage{}, | ||
"simple/subsimple": subresourceStorage, | ||
} | ||
|
||
group := APIGroupVersion{ | ||
Storage: storage, | ||
|
||
RequestInfoResolver: newTestRequestInfoResolver(), | ||
|
||
Creater: api.Scheme, | ||
Convertor: api.Scheme, | ||
Typer: api.Scheme, | ||
Linker: selfLinker, | ||
Mapper: namespaceMapper, | ||
|
||
ParameterCodec: api.ParameterCodec, | ||
|
||
Admit: admissionControl, | ||
Context: requestContextMapper, | ||
|
||
Root: "/" + prefix, | ||
GroupVersion: testGroupVersion, | ||
OptionsExternalVersion: &testGroupVersion, | ||
Serializer: api.Codecs, | ||
|
||
SubresourceGroupVersionKind: map[string]unversioned.GroupVersionKind{ | ||
"simple/subsimple": testGroup2Version.WithKind("SimpleXGSubresource"), | ||
}, | ||
} | ||
|
||
if err := (&group).InstallREST(container); err != nil { | ||
panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err)) | ||
} | ||
|
||
ws := new(restful.WebService) | ||
InstallSupport(mux, ws) | ||
container.Add(ws) | ||
|
||
handler := defaultAPIServer{mux, container} | ||
server := httptest.NewServer(handler) | ||
// TODO: Uncomment when fix #19254 | ||
// defer server.Close() | ||
|
||
resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple") | ||
if err != nil { | ||
t.Fatalf("unexpected error: %v", err) | ||
} | ||
if resp.StatusCode != http.StatusOK { | ||
t.Fatalf("unexpected response: %#v", resp) | ||
} | ||
var itemOut SimpleXGSubresource | ||
body, err := extractBody(resp, &itemOut) | ||
if err != nil { | ||
t.Errorf("unexpected error: %v", err) | ||
} | ||
|
||
// Test if the returned object has the expected group, version and kind | ||
// We are directly unmarshaling JSON here because TypeMeta cannot be decoded through the | ||
// installed decoders. TypeMeta cannot be decoded because it is added to the ignored | ||
// conversion type list in API scheme and hence cannot be converted from input type object | ||
// to output type object. So it's values don't appear in the decoded output object. | ||
decoder := json.NewDecoder(strings.NewReader(body)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem right… we can't decode an API object using a real codec? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @liggitt We can decode a serialized API object into a destination, not just I think this is a reasonable workaround. Any other alternative suggestions? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @liggett it's that I asked for the version/kind to be checked, and our codec consumes that information and leaves those fields blank. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UnstructuredJSONScheme would do this, I think...
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's json decoding either way, I just thought there was an existing codepath we could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @liggitt Do you feel strongly about swapping the current unmarshaling code with this snippet? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @liggitt Alright then, thanks! |
||
var itemFromBody SimpleXGSubresource | ||
err = decoder.Decode(&itemFromBody) | ||
if err != nil { | ||
t.Errorf("unexpected JSON decoding error: %v", err) | ||
} | ||
if want := fmt.Sprintf("%s/%s", testGroup2Version.Group, testGroup2Version.Version); itemFromBody.APIVersion != want { | ||
t.Errorf("unexpected APIVersion got: %+v want: %+v", itemFromBody.APIVersion, want) | ||
} | ||
if itemFromBody.Kind != "SimpleXGSubresource" { | ||
t.Errorf("unexpected Kind got: %+v want: SimpleXGSubresource", itemFromBody.Kind) | ||
} | ||
|
||
if itemOut.Name != subresourceStorage.item.Name { | ||
t.Errorf("Unexpected data: %#v, expected %#v (%s)", itemOut, subresourceStorage.item, string(body)) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test that the returned item is the correct group/version/kind. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
} | ||
|
||
func readBodyOrDie(r io.Reader) []byte { | ||
body, err := ioutil.ReadAll(r) | ||
if err != nil { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know why this is special-cased? I still don't, but this pull is changing behavior around this bit of hardcoding (unlike the previous comment) so I think it should be re-visited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's deal with ThirdPartyResource in another PR. The currently implemented behavior isn't what we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think that should be in another PR.