Merge pull request #7030 from liggitt/skip_fetch_delete

Skip fetching from server when deleting a named resource
This commit is contained in:
Clayton Coleman 2015-04-20 19:38:10 -04:00
commit 1bc4912f51
4 changed files with 149 additions and 42 deletions

View File

@ -87,7 +87,7 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
FilenameParam(filenames...).
SelectorParam(cmdutil.GetFlagString(cmd, "selector")).
SelectAllParam(cmdutil.GetFlagBool(cmd, "all")).
ResourceTypeOrNameArgs(false, args...).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
Flatten().
Do()
err = r.Err()

View File

@ -27,6 +27,66 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
func TestDeleteObjectByTuple(t *testing.T) {
_, _, rc := testData()
f, tf, codec := NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &client.FakeRESTClient{
Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
default:
// Ensures no GET is performed when deleting by name
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf)
cmd.Flags().Set("namespace", "test")
cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"})
if buf.String() != "replicationControllers/redis-master-controller\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteNamedObject(t *testing.T) {
_, _, rc := testData()
f, tf, codec := NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &client.FakeRESTClient{
Codec: codec,
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master-controller" && m == "DELETE":
return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil
default:
// Ensures no GET is performed when deleting by name
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf)
cmd.Flags().Set("namespace", "test")
cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"})
if buf.String() != "replicationControllers/redis-master-controller\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestDeleteObject(t *testing.T) {
_, _, rc := testData()

View File

@ -58,6 +58,8 @@ type Builder struct {
flatten bool
latest bool
requireObject bool
singleResourceType bool
continueOnError bool
}
@ -70,7 +72,8 @@ type resourceTuple struct {
// NewBuilder creates a builder that operates on generic objects.
func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper) *Builder {
return &Builder{
mapper: &Mapper{typer, mapper, clientMapper},
mapper: &Mapper{typer, mapper, clientMapper},
requireObject: true,
}
}
@ -327,6 +330,12 @@ func (b *Builder) Latest() *Builder {
return b
}
// RequireObject ensures that resulting infos have an object set. If false, resulting info may not have an object set.
func (b *Builder) RequireObject(require bool) *Builder {
b.requireObject = require
return b
}
// ContinueOnError will attempt to load and visit as many objects as possible, even if some visits
// return errors or some objects cannot be loaded. The default behavior is to terminate after
// the first error is returned from a VisitorFunc.
@ -537,9 +546,6 @@ func (b *Builder) visitorResult() *Result {
visitors := []Visitor{}
for _, name := range b.names {
info := NewInfo(client, mapping, selectorNamespace, name)
if err := info.Get(); err != nil {
return &Result{singular: isSingular, err: err}
}
visitors = append(visitors, info)
}
return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors}
@ -593,7 +599,7 @@ func (b *Builder) Do() *Result {
helpers = append(helpers, RequireNamespace(b.namespace))
}
helpers = append(helpers, FilterNamespace)
if b.latest {
if b.requireObject {
helpers = append(helpers, RetrieveLazy)
}
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)

View File

@ -58,7 +58,7 @@ func fakeClient() ClientMapper {
})
}
func fakeClientWith(t *testing.T, data map[string]string) ClientMapper {
func fakeClientWith(testName string, t *testing.T, data map[string]string) ClientMapper {
return ClientMapperFunc(func(*meta.RESTMapping) (RESTClient, error) {
return &client.FakeRESTClient{
Codec: latest.Codec,
@ -70,7 +70,7 @@ func fakeClientWith(t *testing.T, data map[string]string) ClientMapper {
}
body, ok := data[p]
if !ok {
t.Fatalf("unexpected request: %s (%s)\n%#v", p, req.URL, req)
t.Fatalf("%s: unexpected request: %s (%s)\n%#v", testName, p, req.URL, req)
}
return &http.Response{
StatusCode: http.StatusOK,
@ -305,7 +305,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) {
func TestResourceByName(t *testing.T) {
pods, _ := testData()
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
})).
NamespaceParam("test")
@ -336,9 +336,42 @@ func TestResourceByName(t *testing.T) {
}
}
func TestResourceByNameWithoutRequireObject(t *testing.T) {
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{})).
NamespaceParam("test")
test := &testVisitor{}
singular := false
if b.Do().Err() == nil {
t.Errorf("unexpected non-error")
}
b.ResourceTypeOrNameArgs(true, "pods", "foo").RequireObject(false)
err := b.Do().IntoSingular(&singular).Visit(test.Handle)
if err != nil || !singular || len(test.Infos) != 1 {
t.Fatalf("unexpected response: %v %t %#v", err, singular, test.Infos)
}
if test.Infos[0].Name != "foo" {
t.Errorf("unexpected name: %#v", test.Infos[0].Name)
}
if test.Infos[0].Object != nil {
t.Errorf("unexpected object: %#v", test.Infos[0].Object)
}
mapping, err := b.Do().ResourceMapping()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if mapping.Kind != "Pod" || mapping.Resource != "pods" {
t.Errorf("unexpected resource mapping: %#v", mapping)
}
}
func TestResourceByNameAndEmptySelector(t *testing.T) {
pods, _ := testData()
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
})).
NamespaceParam("test").
@ -366,7 +399,7 @@ func TestResourceByNameAndEmptySelector(t *testing.T) {
func TestSelector(t *testing.T) {
pods, svc := testData()
labelKey := api.LabelSelectorQueryParam(testapi.Version())
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
"/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
})).
@ -467,35 +500,43 @@ func TestResourceTuple(t *testing.T) {
},
}
for k, testCase := range testCases {
pods, _ := testData()
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
"/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
"/nodes/foo": runtime.EncodeOrDie(latest.Codec, &api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
})).
NamespaceParam("test").DefaultNamespace().
ResourceTypeOrNameArgs(true, testCase.args...)
for _, requireObject := range []bool{true, false} {
expectedRequests := map[string]string{}
if requireObject {
pods, _ := testData()
expectedRequests = map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
"/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
"/nodes/foo": runtime.EncodeOrDie(latest.Codec, &api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
"/minions/foo": runtime.EncodeOrDie(latest.Codec, &api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
}
}
r := b.Do()
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(k, t, expectedRequests)).
NamespaceParam("test").DefaultNamespace().
ResourceTypeOrNameArgs(true, testCase.args...).RequireObject(requireObject)
if !testCase.errFn(r.Err()) {
t.Errorf("%s: unexpected error: %v", k, r.Err())
}
if r.Err() != nil {
continue
}
switch {
case (r.singular && len(testCase.args) != 1),
(!r.singular && len(testCase.args) == 1):
t.Errorf("%s: result had unexpected singular value", k)
}
info, err := r.Infos()
if err != nil {
// test error
continue
}
if len(info) != len(testCase.args) {
t.Errorf("%s: unexpected number of infos returned: %#v", k, info)
r := b.Do()
if !testCase.errFn(r.Err()) {
t.Errorf("%s: unexpected error: %v", k, r.Err())
}
if r.Err() != nil {
continue
}
switch {
case (r.singular && len(testCase.args) != 1),
(!r.singular && len(testCase.args) == 1):
t.Errorf("%s: result had unexpected singular value", k)
}
info, err := r.Infos()
if err != nil {
// test error
continue
}
if len(info) != len(testCase.args) {
t.Errorf("%s: unexpected number of infos returned: %#v", k, info)
}
}
}
}
@ -579,7 +620,7 @@ func TestSingularObject(t *testing.T) {
func TestListObject(t *testing.T) {
pods, _ := testData()
labelKey := api.LabelSelectorQueryParam(testapi.Version())
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
})).
SelectorParam("a=b").
@ -612,7 +653,7 @@ func TestListObject(t *testing.T) {
func TestListObjectWithDifferentVersions(t *testing.T) {
pods, svc := testData()
labelKey := api.LabelSelectorQueryParam(testapi.Version())
obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{
"/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
"/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
})).
@ -638,7 +679,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) {
func TestWatch(t *testing.T) {
_, svc := testData()
w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{
"/watch/namespaces/test/services/redis-master?resourceVersion=12": watchBody(watch.Event{
Type: watch.Added,
Object: &svc.Items[0],
@ -693,7 +734,7 @@ func TestLatest(t *testing.T) {
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "15"},
}
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{
"/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, newPod),
"/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, newPod2),
"/namespaces/test/services/baz": runtime.EncodeOrDie(latest.Codec, newSvc),