diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go index f0582b5a18d..253eb7cf82a 100644 --- a/pkg/api/latest/latest_test.go +++ b/pkg/api/latest/latest_test.go @@ -18,57 +18,13 @@ package latest import ( "encoding/json" - "math/rand" "testing" internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api" - apitesting "github.com/GoogleCloudPlatform/kubernetes/pkg/api/testing" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) -func TestInternalRoundTrip(t *testing.T) { - latest := "v1beta2" - - seed := rand.Int63() - apiObjectFuzzer := apitesting.FuzzerFor(t, "", rand.NewSource(seed)) - for k := range internal.Scheme.KnownTypes("") { - obj, err := internal.Scheme.New("", k) - if err != nil { - t.Errorf("%s: unexpected error: %v", k, err) - continue - } - apiObjectFuzzer.Fuzz(obj) - - newer, err := internal.Scheme.New(latest, k) - if err != nil { - t.Errorf("%s: unexpected error: %v", k, err) - continue - } - - if err := internal.Scheme.Convert(obj, newer); err != nil { - t.Errorf("unable to convert %#v to %#v: %v", obj, newer, err) - continue - } - - actual, err := internal.Scheme.New("", k) - if err != nil { - t.Errorf("%s: unexpected error: %v", k, err) - continue - } - - if err := internal.Scheme.Convert(newer, actual); err != nil { - t.Errorf("unable to convert %#v to %#v: %v", newer, actual, err) - continue - } - - if !internal.Semantic.DeepEqual(obj, actual) { - t.Errorf("%s: diff %s", k, util.ObjectDiff(obj, actual)) - } - } -} - func TestResourceVersioner(t *testing.T) { pod := internal.Pod{ObjectMeta: internal.ObjectMeta{ResourceVersion: "10"}} version, err := ResourceVersioner.ResourceVersion(&pod) diff --git a/pkg/apiserver/api_installer.go b/pkg/apiserver/api_installer.go index 9f17f2396de..72d25c2b8e2 100644 --- a/pkg/apiserver/api_installer.go +++ b/pkg/apiserver/api_installer.go @@ -26,6 +26,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/emicklei/go-restful" @@ -106,6 +107,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage return err } versionedPtr, err := api.Scheme.New(a.version, kind) + if conversion.IsNotRegisteredError(err) { + return nil + } if err != nil { return err } diff --git a/pkg/httplog/log.go b/pkg/httplog/log.go index ec26de718e9..21f01519184 100644 --- a/pkg/httplog/log.go +++ b/pkg/httplog/log.go @@ -74,7 +74,7 @@ func (passthroughLogger) Addf(format string, data ...interface{}) { // DefaultStacktracePred is the default implementation of StacktracePred. func DefaultStacktracePred(status int) bool { - return (status < http.StatusOK || status >= http.StatusBadRequest) && status != http.StatusSwitchingProtocols + return (status < http.StatusOK || status >= http.StatusInternalServerError) && status != http.StatusSwitchingProtocols } // NewLogged turns a normal response writer into a logged response writer. diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 8e06ff0f6f7..860f3d91e71 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -138,18 +138,25 @@ func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) { // the outermost object will be converted to the output-version version := util.OutputVersion(cmd, defaultVersion) - if len(version) == 0 { - // TODO: add a new ResourceBuilder mode for Object() that attempts to ensure the objects - // are in the appropriate version if one exists (and if not, use the best effort). - // TODO: ensure api-version is set with the default preferred api version by the client - // builder on initialization - version = latest.Version - } - printer = kubectl.NewVersionedPrinter(printer, api.Scheme, version) - obj, err := b.Flatten().Do().Object() + r := b.Flatten().Do() + obj, err := r.Object() checkErr(err) + // try conversion to all the possible versions + // TODO: simplify by adding a ResourceBuilder mode + versions := []string{version, latest.Version} + infos, _ := r.Infos() + for _, info := range infos { + versions = append(versions, info.Mapping.APIVersion) + } + + // TODO: add a new ResourceBuilder mode for Object() that attempts to ensure the objects + // are in the appropriate version if one exists (and if not, use the best effort). + // TODO: ensure api-version is set with the default preferred api version by the client + // builder on initialization + printer := kubectl.NewVersionedPrinter(printer, api.Scheme, versions...) + err = printer.PrintObj(obj, out) checkErr(err) return diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index b2b5e74bbee..166f4876485 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -63,7 +63,20 @@ type OutputVersionMapper struct { // RESTMapping implements meta.RESTMapper by prepending the output version to the preferred version list. func (m OutputVersionMapper) RESTMapping(kind string, versions ...string) (*meta.RESTMapping, error) { - preferred := append([]string{m.OutputVersion}, versions...) + preferred := []string{m.OutputVersion} + for _, version := range versions { + if len(version) > 0 { + preferred = append(preferred, version) + } + } + // if the caller wants to use the default version list, try with the preferred version, and on + // error, use the default behavior. + if len(preferred) == 1 { + if m, err := m.RESTMapper.RESTMapping(kind, preferred...); err == nil { + return m, nil + } + preferred = nil + } return m.RESTMapper.RESTMapping(kind, preferred...) } diff --git a/pkg/kubectl/resource/result.go b/pkg/kubectl/resource/result.go index 9ceeda5a840..13e5b89638b 100644 --- a/pkg/kubectl/resource/result.go +++ b/pkg/kubectl/resource/result.go @@ -133,7 +133,7 @@ func (r *Result) Object() (runtime.Object, error) { return objects[0], nil } // if the item is a list already, don't create another list - if _, err := runtime.GetItemsPtr(objects[0]); err == nil { + if runtime.IsListType(objects[0]) { return objects[0], nil } } diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index ee2a385acd2..340c444ced0 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -32,6 +32,7 @@ import ( "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/docker/docker/pkg/units" "github.com/ghodss/yaml" @@ -98,11 +99,11 @@ func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error { type VersionedPrinter struct { printer ResourcePrinter convertor runtime.ObjectConvertor - version string + version []string } // NewVersionedPrinter wraps a printer to convert objects to a known API version prior to printing. -func NewVersionedPrinter(printer ResourcePrinter, convertor runtime.ObjectConvertor, version string) ResourcePrinter { +func NewVersionedPrinter(printer ResourcePrinter, convertor runtime.ObjectConvertor, version ...string) ResourcePrinter { return &VersionedPrinter{ printer: printer, convertor: convertor, @@ -115,11 +116,20 @@ func (p *VersionedPrinter) PrintObj(obj runtime.Object, w io.Writer) error { if len(p.version) == 0 { return fmt.Errorf("no version specified, object cannot be converted") } - converted, err := p.convertor.ConvertToVersion(obj, p.version) - if err != nil { - return err + for _, version := range p.version { + if len(version) == 0 { + continue + } + converted, err := p.convertor.ConvertToVersion(obj, version) + if conversion.IsNotRegisteredError(err) { + continue + } + if err != nil { + return err + } + return p.printer.PrintObj(converted, w) } - return p.printer.PrintObj(converted, w) + return fmt.Errorf("the object cannot be converted to any of the versions: %v", p.version) } // JSONPrinter is an implementation of ResourcePrinter which outputs an object as JSON. diff --git a/pkg/registry/event/rest.go b/pkg/registry/event/rest.go index 07d8a61f8cf..f4ec1981e00 100644 --- a/pkg/registry/event/rest.go +++ b/pkg/registry/event/rest.go @@ -131,13 +131,13 @@ func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, e } func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { - return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) + return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) } // Watch returns Events events via a watch.Interface. // It implements apiserver.ResourceWatcher. func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { - return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) + return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) } // New returns a new api.Event diff --git a/pkg/registry/generic/etcd/etcd.go b/pkg/registry/generic/etcd/etcd.go index e9b8b3e4e38..2ea765fa49a 100644 --- a/pkg/registry/generic/etcd/etcd.go +++ b/pkg/registry/generic/etcd/etcd.go @@ -23,6 +23,7 @@ import ( kubeerr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" @@ -70,6 +71,9 @@ type Etcd struct { // is an operation against an existing object. TTLFunc func(obj runtime.Object, update bool) (uint64, error) + // Returns a matcher corresponding to the provided labels and fields. + PredicateFunc func(label, field labels.Selector) generic.Matcher + // Called on all objects returned from the underlying store, after // the exit hooks are invoked. Decorators are intended for integrations // that are above etcd and should only be used for specific cases where @@ -119,10 +123,23 @@ func NamespaceKeyFunc(ctx api.Context, prefix string, name string) (string, erro return key, nil } -// List returns a list of all the items matching m. -// TODO: rename this to ListPredicate, take the default predicate function on the constructor, and -// introduce a List method that uses the default predicate function -func (e *Etcd) List(ctx api.Context, m generic.Matcher) (runtime.Object, error) { +// New implements RESTStorage +func (e *Etcd) New() runtime.Object { + return e.NewFunc() +} + +// NewList implements RESTLister +func (e *Etcd) NewList() runtime.Object { + return e.NewListFunc() +} + +// List returns a list of items matching labels and field +func (e *Etcd) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { + return e.ListPredicate(ctx, e.PredicateFunc(label, field)) +} + +// ListPredicate returns a list of all the items matching m. +func (e *Etcd) ListPredicate(ctx api.Context, m generic.Matcher) (runtime.Object, error) { list := e.NewListFunc() err := e.Helper.ExtractToList(e.KeyRootFunc(ctx), list) if err != nil { @@ -339,11 +356,15 @@ func (e *Etcd) Delete(ctx api.Context, name string) (runtime.Object, error) { return &api.Status{Status: api.StatusSuccess}, nil } -// Watch starts a watch for the items that m matches. +// WatchPredicate starts a watch for the items that m matches. // TODO: Detect if m references a single object instead of a list. -// TODO: rename this to WatchPredicate, take the default predicate function on the constructor, and -// introduce a Watch method that uses the default predicate function -func (e *Etcd) Watch(ctx api.Context, m generic.Matcher, resourceVersion string) (watch.Interface, error) { +func (e *Etcd) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { + return e.WatchPredicate(ctx, e.PredicateFunc(label, field), resourceVersion) +} + +// WatchPredicate starts a watch for the items that m matches. +// TODO: Detect if m references a single object instead of a list. +func (e *Etcd) WatchPredicate(ctx api.Context, m generic.Matcher, resourceVersion string) (watch.Interface, error) { version, err := tools.ParseWatchResourceVersion(resourceVersion, e.EndpointName) if err != nil { return nil, err diff --git a/pkg/registry/generic/etcd/etcd_test.go b/pkg/registry/generic/etcd/etcd_test.go index 59d9ee1782c..9c052014938 100644 --- a/pkg/registry/generic/etcd/etcd_test.go +++ b/pkg/registry/generic/etcd/etcd_test.go @@ -172,7 +172,7 @@ func TestEtcdList(t *testing.T) { for name, item := range table { fakeClient, registry := NewTestGenericEtcdRegistry(t) fakeClient.Data[registry.KeyRootFunc(api.NewContext())] = item.in - list, err := registry.List(api.NewContext(), item.m) + list, err := registry.ListPredicate(api.NewContext(), item.m) if e, a := item.succeed, err == nil; e != a { t.Errorf("%v: expected %v, got %v", name, e, a) continue @@ -660,7 +660,7 @@ func TestEtcdWatch(t *testing.T) { } fakeClient, registry := NewTestGenericEtcdRegistry(t) - wi, err := registry.Watch(api.NewContext(), EverythingMatcher{}, "1") + wi, err := registry.WatchPredicate(api.NewContext(), EverythingMatcher{}, "1") if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/registry/generic/registry.go b/pkg/registry/generic/registry.go index f801b754d75..c194208a25d 100644 --- a/pkg/registry/generic/registry.go +++ b/pkg/registry/generic/registry.go @@ -75,12 +75,12 @@ type DecoratorFunc func(obj runtime.Object) error // layer. // DEPRECATED: replace with direct implementation of RESTStorage type Registry interface { - List(api.Context, Matcher) (runtime.Object, error) + ListPredicate(api.Context, Matcher) (runtime.Object, error) CreateWithName(ctx api.Context, id string, obj runtime.Object) error UpdateWithName(ctx api.Context, id string, obj runtime.Object) error Get(ctx api.Context, id string) (runtime.Object, error) Delete(ctx api.Context, id string) (runtime.Object, error) - Watch(ctx api.Context, m Matcher, resourceVersion string) (watch.Interface, error) + WatchPredicate(ctx api.Context, m Matcher, resourceVersion string) (watch.Interface, error) } // FilterList filters any list object that conforms to the api conventions, diff --git a/pkg/registry/limitrange/rest.go b/pkg/registry/limitrange/rest.go index 26637e19e2d..7d9bf8c5f58 100644 --- a/pkg/registry/limitrange/rest.go +++ b/pkg/registry/limitrange/rest.go @@ -135,11 +135,11 @@ func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, e } func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { - return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) + return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) } func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { - return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) + return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) } // New returns a new api.LimitRange diff --git a/pkg/registry/namespace/rest.go b/pkg/registry/namespace/rest.go index 055dfe1f79d..db71a3a5cc9 100644 --- a/pkg/registry/namespace/rest.go +++ b/pkg/registry/namespace/rest.go @@ -109,11 +109,11 @@ func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, e } func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { - return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) + return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) } func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { - return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) + return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) } // New returns a new api.Namespace diff --git a/pkg/registry/pod/etcd/etcd.go b/pkg/registry/pod/etcd/etcd.go index 9bedb080854..2ca4e738446 100644 --- a/pkg/registry/pod/etcd/etcd.go +++ b/pkg/registry/pod/etcd/etcd.go @@ -24,6 +24,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" "github.com/GoogleCloudPlatform/kubernetes/pkg/constraint" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" + "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic" etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/pod" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" @@ -53,6 +54,9 @@ func NewREST(h tools.EtcdHelper, factory pod.BoundPodFactory) (*REST, *BindingRE ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Pod).Name, nil }, + PredicateFunc: func(label, field labels.Selector) generic.Matcher { + return pod.MatchPod(label, field) + }, EndpointName: "pods", Helper: h, @@ -92,12 +96,12 @@ func (r *REST) NewList() runtime.Object { // List obtains a list of pods with labels that match selector. func (r *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { - return r.store.List(ctx, pod.MatchPod(label, field)) + return r.store.List(ctx, label, field) } // Watch begins watching for new, changed, or deleted pods. func (r *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { - return r.store.Watch(ctx, pod.MatchPod(label, field), resourceVersion) + return r.store.Watch(ctx, label, field, resourceVersion) } // Get gets a specific pod specified by its ID. diff --git a/pkg/registry/registrytest/generic.go b/pkg/registry/registrytest/generic.go index 820528f4aee..3cecc0a10a9 100644 --- a/pkg/registry/registrytest/generic.go +++ b/pkg/registry/registrytest/generic.go @@ -42,7 +42,7 @@ func NewGeneric(list runtime.Object) *GenericRegistry { } } -func (r *GenericRegistry) List(ctx api.Context, m generic.Matcher) (runtime.Object, error) { +func (r *GenericRegistry) ListPredicate(ctx api.Context, m generic.Matcher) (runtime.Object, error) { r.Lock() defer r.Unlock() if r.Err != nil { @@ -51,7 +51,7 @@ func (r *GenericRegistry) List(ctx api.Context, m generic.Matcher) (runtime.Obje return generic.FilterList(r.ObjectList, m, nil) } -func (r *GenericRegistry) Watch(ctx api.Context, m generic.Matcher, resourceVersion string) (watch.Interface, error) { +func (r *GenericRegistry) WatchPredicate(ctx api.Context, m generic.Matcher, resourceVersion string) (watch.Interface, error) { // TODO: wire filter down into the mux; it needs access to current and previous state :( return r.Broadcaster.Watch(), nil } diff --git a/pkg/registry/resourcequota/rest.go b/pkg/registry/resourcequota/rest.go index 62597c47d49..98616f2a351 100644 --- a/pkg/registry/resourcequota/rest.go +++ b/pkg/registry/resourcequota/rest.go @@ -138,11 +138,11 @@ func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, e } func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { - return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) + return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) } func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { - return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) + return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) } // New returns a new api.ResourceQuota diff --git a/pkg/registry/secret/rest.go b/pkg/registry/secret/rest.go index 8bdec0de328..265d23c5544 100644 --- a/pkg/registry/secret/rest.go +++ b/pkg/registry/secret/rest.go @@ -145,11 +145,11 @@ func (rs *REST) getAttrs(obj runtime.Object) (objLabels, objFields labels.Set, e } func (rs *REST) List(ctx api.Context, label, field labels.Selector) (runtime.Object, error) { - return rs.registry.List(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) + return rs.registry.ListPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}) } func (rs *REST) Watch(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { - return rs.registry.Watch(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) + return rs.registry.WatchPredicate(ctx, &generic.SelectionPredicate{label, field, rs.getAttrs}, resourceVersion) } // New returns a new api.Secret