mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 09:49:50 +00:00
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. Allows paging over server side tables. Add tests on the generic creater and on the resttest compatibility.
This commit is contained in:
parent
52492e683f
commit
d2a62fd422
@ -525,6 +525,16 @@ func (h *HumanReadablePrinter) PrintTable(obj runtime.Object, options PrintOptio
|
|||||||
ColumnDefinitions: columns,
|
ColumnDefinitions: columns,
|
||||||
Rows: results[0].Interface().([]metav1alpha1.TableRow),
|
Rows: results[0].Interface().([]metav1alpha1.TableRow),
|
||||||
}
|
}
|
||||||
|
if m, err := meta.ListAccessor(obj); err == nil {
|
||||||
|
table.ResourceVersion = m.GetResourceVersion()
|
||||||
|
table.SelfLink = m.GetSelfLink()
|
||||||
|
table.Continue = m.GetContinue()
|
||||||
|
} else {
|
||||||
|
if m, err := meta.CommonAccessor(obj); err == nil {
|
||||||
|
table.ResourceVersion = m.GetResourceVersion()
|
||||||
|
table.SelfLink = m.GetSelfLink()
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := DecorateTable(table, options); err != nil {
|
if err := DecorateTable(table, options); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ func ListAccessor(obj interface{}) (List, error) {
|
|||||||
}
|
}
|
||||||
return nil, errNotList
|
return nil, errNotList
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("%T does not implement the List interface", obj))
|
return nil, errNotList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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"]},
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
metav1alpha1 "k8s.io/apimachinery/pkg/apis/meta/v1alpha1"
|
||||||
|
"k8s.io/client-go/util/workqueue"
|
||||||
"k8s.io/kubernetes/pkg/printers"
|
"k8s.io/kubernetes/pkg/printers"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
imageutils "k8s.io/kubernetes/test/utils/image"
|
imageutils "k8s.io/kubernetes/test/utils/image"
|
||||||
@ -63,6 +64,56 @@ var _ = SIGDescribe("Servers with support for Table transformation", func() {
|
|||||||
framework.Logf("Table:\n%s", out)
|
framework.Logf("Table:\n%s", out)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should return chunks of table results for list calls", func() {
|
||||||
|
ns := f.Namespace.Name
|
||||||
|
c := f.ClientSet
|
||||||
|
client := c.CoreV1().PodTemplates(ns)
|
||||||
|
|
||||||
|
By("creating a large number of resources")
|
||||||
|
workqueue.Parallelize(5, 20, func(i int) {
|
||||||
|
for tries := 3; tries >= 0; tries-- {
|
||||||
|
_, err := client.Create(&v1.PodTemplate{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: fmt.Sprintf("template-%04d", i),
|
||||||
|
},
|
||||||
|
Template: v1.PodTemplateSpec{
|
||||||
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{Name: "test", Image: "test2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
framework.Logf("Got an error creating template %d: %v", i, err)
|
||||||
|
}
|
||||||
|
Fail("Unable to create template %d, exiting", i)
|
||||||
|
})
|
||||||
|
|
||||||
|
pagedTable := &metav1alpha1.Table{}
|
||||||
|
err := c.CoreV1().RESTClient().Get().Namespace(ns).Resource("podtemplates").
|
||||||
|
VersionedParams(&metav1.ListOptions{Limit: 2}, metav1.ParameterCodec).
|
||||||
|
SetHeader("Accept", "application/json;as=Table;v=v1alpha1;g=meta.k8s.io").
|
||||||
|
Do().Into(pagedTable)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(pagedTable.Rows)).To(Equal(2))
|
||||||
|
Expect(pagedTable.ResourceVersion).ToNot(Equal(""))
|
||||||
|
Expect(pagedTable.SelfLink).ToNot(Equal(""))
|
||||||
|
Expect(pagedTable.Continue).ToNot(Equal(""))
|
||||||
|
Expect(pagedTable.Rows[0].Cells[0]).To(Equal("template-0000"))
|
||||||
|
Expect(pagedTable.Rows[1].Cells[0]).To(Equal("template-0001"))
|
||||||
|
|
||||||
|
err = c.CoreV1().RESTClient().Get().Namespace(ns).Resource("podtemplates").
|
||||||
|
VersionedParams(&metav1.ListOptions{Continue: pagedTable.Continue}, metav1.ParameterCodec).
|
||||||
|
SetHeader("Accept", "application/json;as=Table;v=v1alpha1;g=meta.k8s.io").
|
||||||
|
Do().Into(pagedTable)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
Expect(len(pagedTable.Rows)).To(BeNumerically(">", 0))
|
||||||
|
Expect(pagedTable.Rows[0].Cells[0]).To(Equal("template-0002"))
|
||||||
|
})
|
||||||
|
|
||||||
It("should return generic metadata details across all namespaces for nodes", func() {
|
It("should return generic metadata details across all namespaces for nodes", func() {
|
||||||
c := f.ClientSet
|
c := f.ClientSet
|
||||||
|
|
||||||
@ -75,6 +126,8 @@ var _ = SIGDescribe("Servers with support for Table transformation", func() {
|
|||||||
Expect(len(table.Rows)).To(BeNumerically(">=", 1))
|
Expect(len(table.Rows)).To(BeNumerically(">=", 1))
|
||||||
Expect(len(table.Rows[0].Cells)).To(Equal(len(table.ColumnDefinitions)))
|
Expect(len(table.Rows[0].Cells)).To(Equal(len(table.ColumnDefinitions)))
|
||||||
Expect(table.ColumnDefinitions[0].Name).To(Equal("Name"))
|
Expect(table.ColumnDefinitions[0].Name).To(Equal("Name"))
|
||||||
|
Expect(table.ResourceVersion).ToNot(Equal(""))
|
||||||
|
Expect(table.SelfLink).ToNot(Equal(""))
|
||||||
|
|
||||||
out := printTable(table)
|
out := printTable(table)
|
||||||
Expect(out).To(MatchRegexp("^NAME\\s"))
|
Expect(out).To(MatchRegexp("^NAME\\s"))
|
||||||
|
Loading…
Reference in New Issue
Block a user