Skip to content
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

Small cleanups to a number of client behaviors #5013

Merged
merged 8 commits into from
Mar 6, 2015
44 changes: 0 additions & 44 deletions pkg/api/latest/latest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions pkg/apiserver/api_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/httplog/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
25 changes: 16 additions & 9 deletions pkg/kubectl/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion pkg/kubectl/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/resource/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
22 changes: 16 additions & 6 deletions pkg/kubectl/resource_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions pkg/registry/event/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 43 additions & 10 deletions pkg/registry/generic/etcd/etcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ limitations under the License.
package etcd

import (
"fmt"

"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
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"
Expand Down Expand Up @@ -68,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
Expand Down Expand Up @@ -117,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 {
Expand Down Expand Up @@ -228,6 +247,7 @@ func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool
if err != nil {
return nil, false, err
}
// TODO: expose TTL
creating := false
out := e.NewFunc()
err = e.Helper.AtomicUpdate(key, out, true, func(existing runtime.Object) (runtime.Object, error) {
Expand All @@ -237,21 +257,30 @@ func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool
}
if version == 0 {
if !e.UpdateStrategy.AllowCreateOnUpdate() {
return nil, kubeerr.NewAlreadyExists(e.EndpointName, name)
return nil, kubeerr.NewNotFound(e.EndpointName, name)
}
creating = true
if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil {
return nil, err
}
return obj, nil
}

creating = false
newVersion, err := e.Helper.ResourceVersioner.ResourceVersion(obj)
if err != nil {
return nil, err
}
if newVersion != version {
// TODO: return the most recent version to a client?
return nil, kubeerr.NewConflict(e.EndpointName, name, fmt.Errorf("the resource was updated to %d", version))
}
if err := rest.BeforeUpdate(e.UpdateStrategy, ctx, obj, existing); err != nil {
return nil, err
}
// TODO: expose TTL
return obj, nil
})

if err != nil {
if creating {
err = etcderr.InterpretCreateError(err, e.EndpointName, name)
Expand Down Expand Up @@ -327,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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: name doesn't match

// TODO: Detect if m references a single object instead of a list.
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.
// 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) 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
Expand Down
Loading