Merge pull request #55835 from smarterclayton/table_printer_meta

Automatic merge from submit-queue (batch tested with PRs 55642, 55897, 55835, 55496, 55313). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Table printers and server generation should always copy ListMeta

Tables should be a mapping from lists, so if the incoming object has these add them to the table. Paging over server side tables was broken without this. Add tests on the generic creater and on the resttest compatibility.

@deads2k

Kubernetes-commit: 941c6aa1db828c9f687780bea21f1d94319a1abe
This commit is contained in:
Kubernetes Publisher 2017-11-18 10:46:35 -08:00
commit f4eef190f3
4 changed files with 1824 additions and 1713 deletions

514
Godeps/Godeps.json generated

File diff suppressed because it is too large Load Diff

View File

@ -441,6 +441,10 @@ func (storage *SimpleRESTStorage) ConvertToTable(ctx request.Context, obj runtim
func (storage *SimpleRESTStorage) List(ctx request.Context, options *metainternalversion.ListOptions) (runtime.Object, error) { func (storage *SimpleRESTStorage) List(ctx request.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
storage.checkContext(ctx) storage.checkContext(ctx)
result := &genericapitesting.SimpleList{ result := &genericapitesting.SimpleList{
ListMeta: metav1.ListMeta{
ResourceVersion: "10",
SelfLink: "/test/link",
},
Items: storage.list, Items: storage.list,
} }
storage.requestedLabelSelector = labels.Everything() storage.requestedLabelSelector = labels.Everything()
@ -1832,24 +1836,10 @@ func TestGetPretty(t *testing.T) {
func TestGetTable(t *testing.T) { func TestGetTable(t *testing.T) {
now := metav1.Now() now := metav1.Now()
storage := map[string]rest.Storage{}
obj := genericapitesting.Simple{ obj := genericapitesting.Simple{
ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", CreationTimestamp: now, UID: types.UID("abcdef0123")}, ObjectMeta: metav1.ObjectMeta{Name: "foo1", Namespace: "ns1", ResourceVersion: "10", SelfLink: "/blah", CreationTimestamp: now, UID: types.UID("abcdef0123")},
Other: "foo", Other: "foo",
} }
simpleStorage := SimpleRESTStorage{
item: obj,
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
name: "id",
namespace: "default",
}
storage["simple"] = &simpleStorage
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
m, err := meta.Accessor(&obj) m, err := meta.Accessor(&obj)
if err != nil { if err != nil {
@ -1872,15 +1862,34 @@ func TestGetTable(t *testing.T) {
pretty bool pretty bool
expected *metav1alpha1.Table expected *metav1alpha1.Table
statusCode int statusCode int
item bool
}{ }{
{ {
accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io", accept: runtime.ContentTypeJSON + ";as=Table;v=v1;g=meta.k8s.io",
statusCode: http.StatusNotAcceptable, statusCode: http.StatusNotAcceptable,
}, },
{ {
item: true,
accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io", accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io",
expected: &metav1alpha1.Table{ expected: &metav1alpha1.Table{
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"}, TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
},
Rows: []metav1alpha1.TableRow{
{Cells: []interface{}{"foo1", now.Time.UTC().Format(time.RFC3339)}, Object: runtime.RawExtension{Raw: encodedBody}},
},
},
},
{
item: true,
accept: runtime.ContentTypeJSON + ";as=Table;v=v1alpha1;g=meta.k8s.io",
params: url.Values{"includeObject": []string{"Metadata"}},
expected: &metav1alpha1.Table{
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/blah"},
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{ ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
@ -1895,6 +1904,7 @@ func TestGetTable(t *testing.T) {
params: url.Values{"includeObject": []string{"Metadata"}}, params: url.Values{"includeObject": []string{"Metadata"}},
expected: &metav1alpha1.Table{ expected: &metav1alpha1.Table{
TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"}, TypeMeta: metav1.TypeMeta{Kind: "Table", APIVersion: "meta.k8s.io/v1alpha1"},
ListMeta: metav1.ListMeta{ResourceVersion: "10", SelfLink: "/test/link"},
ColumnDefinitions: []metav1alpha1.TableColumnDefinition{ ColumnDefinitions: []metav1alpha1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]}, {Name: "Name", Type: "string", Format: "name", Description: metaDoc["name"]},
{Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]}, {Name: "Created At", Type: "date", Description: metaDoc["creationTimestamp"]},
@ -1906,7 +1916,31 @@ func TestGetTable(t *testing.T) {
}, },
} }
for i, test := range tests { for i, test := range tests {
u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id") t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{
item: obj,
list: []genericapitesting.Simple{obj},
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple",
namespace: "default",
}
if test.item {
selfLinker.expectedSet += "/id"
selfLinker.name = "id"
}
storage["simple"] = &simpleStorage
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
var id string
if test.item {
id = "/id"
}
u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple" + id)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1924,14 +1958,13 @@ func TestGetTable(t *testing.T) {
} }
obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme) obj, _, err := extractBodyObject(resp, unstructured.UnstructuredJSONScheme)
if err != nil { if err != nil {
t.Errorf("%d: unexpected body read error: %v", err) t.Fatalf("%d: unexpected body read error: %v", err)
continue
} }
gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"} gvk := schema.GroupVersionKind{Version: "v1", Kind: "Status"}
if obj.GetObjectKind().GroupVersionKind() != gvk { if obj.GetObjectKind().GroupVersionKind() != gvk {
t.Errorf("%d: unexpected error body: %#v", obj) t.Fatalf("%d: unexpected error body: %#v", obj)
} }
continue return
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
t.Errorf("%d: unexpected response: %#v", i, resp) t.Errorf("%d: unexpected response: %#v", i, resp)
@ -1945,6 +1978,7 @@ func TestGetTable(t *testing.T) {
t.Log(body) t.Log(body)
t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut)) t.Errorf("%d: did not match: %s", i, diff.ObjectReflectDiff(test.expected, &itemOut))
} }
})
} }
} }

View File

@ -1291,10 +1291,21 @@ func (t *Tester) testListTableConversion(obj runtime.Object, assignFn AssignFunc
t.Errorf("expected: %#v, got: %#v", objs, items) t.Errorf("expected: %#v, got: %#v", objs, items)
} }
m, err := meta.ListAccessor(listObj)
if err != nil {
t.Fatalf("list should support ListMeta %T: %v", listObj, err)
}
m.SetContinue("continuetoken")
m.SetResourceVersion("11")
m.SetSelfLink("/list/link")
table, err := t.storage.(rest.TableConvertor).ConvertToTable(ctx, listObj, nil) table, err := t.storage.(rest.TableConvertor).ConvertToTable(ctx, listObj, nil)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
if table.ResourceVersion != "11" || table.SelfLink != "/list/link" || table.Continue != "continuetoken" {
t.Errorf("printer lost list meta: %#v", table.ListMeta)
}
if len(table.Rows) != len(items) { if len(table.Rows) != len(items) {
t.Errorf("unexpected number of rows: %v", len(table.Rows)) t.Errorf("unexpected number of rows: %v", len(table.Rows))
} }

View File

@ -63,6 +63,16 @@ func (c defaultTableConvertor) ConvertToTable(ctx genericapirequest.Context, obj
return nil, err return nil, err
} }
} }
if m, err := meta.ListAccessor(object); err == nil {
table.ResourceVersion = m.GetResourceVersion()
table.SelfLink = m.GetSelfLink()
table.Continue = m.GetContinue()
} else {
if m, err := meta.CommonAccessor(object); err == nil {
table.ResourceVersion = m.GetResourceVersion()
table.SelfLink = m.GetSelfLink()
}
}
table.ColumnDefinitions = []metav1alpha1.TableColumnDefinition{ table.ColumnDefinitions = []metav1alpha1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]},
{Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]}, {Name: "Created At", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"]},