func TestUpdateWithRetries(t *testing.T) { codec := testapi.Codec() rc := &api.ReplicationController{ ObjectMeta: api.ObjectMeta{Name: "rc", Labels: map[string]string{ "foo": "bar", }, }, Spec: api.ReplicationControllerSpec{ Selector: map[string]string{ "foo": "bar", }, Template: &api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Labels: map[string]string{ "foo": "bar", }, }, Spec: api.PodSpec{ RestartPolicy: api.RestartPolicyAlways, DNSPolicy: api.DNSClusterFirst, }, }, }, } // Test end to end updating of the rc with retries. Essentially make sure the update handler // sees the right updates, failures in update/get are handled properly, and that the updated // rc with new resource version is returned to the caller. Without any of these rollingupdate // will fail cryptically. newRc := *rc newRc.ResourceVersion = "2" newRc.Spec.Selector["baz"] = "foobar" updates := []*http.Response{ {StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})}, {StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})}, {StatusCode: 200, Body: objBody(codec, &newRc)}, } gets := []*http.Response{ {StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})}, {StatusCode: 200, Body: objBody(codec, rc)}, } fakeClient := &client.FakeRESTClient{ Codec: codec, Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == testapi.ResourcePath("replicationcontrollers", "default", "rc") && m == "PUT": update := updates[0] updates = updates[1:] // We should always get an update with a valid rc even when the get fails. The rc should always // contain the update. if c, ok := readOrDie(t, req, codec).(*api.ReplicationController); !ok || !reflect.DeepEqual(rc, c) { t.Errorf("Unexpected update body, got %+v expected %+v", c, rc) } else if sel, ok := c.Spec.Selector["baz"]; !ok || sel != "foobar" { t.Errorf("Expected selector label update, got %+v", c.Spec.Selector) } else { delete(c.Spec.Selector, "baz") } return update, nil case p == testapi.ResourcePath("replicationcontrollers", "default", "rc") && m == "GET": get := gets[0] gets = gets[1:] return get, nil default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil } }), } clientConfig := &client.Config{Version: testapi.Version()} client := client.NewOrDie(clientConfig) client.Client = fakeClient.Client if rc, err := updateWithRetries( client.ReplicationControllers("default"), rc, func(c *api.ReplicationController) { c.Spec.Selector["baz"] = "foobar" }); err != nil { t.Errorf("unexpected error: %v", err) } else if sel, ok := rc.Spec.Selector["baz"]; !ok || sel != "foobar" || rc.ResourceVersion != "2" { t.Errorf("Expected updated rc, got %+v", rc) } if len(updates) != 0 || len(gets) != 0 { t.Errorf("Remaining updates %+v gets %+v", updates, gets) } }
// NewFactory creates a factory with the default Kubernetes resources defined // if optionalClientConfig is nil, then flags will be bound to a new clientcmd.ClientConfig. // if optionalClientConfig is not nil, then this factory will make use of it. func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { mapper := kubectl.ShortcutExpander{latest.RESTMapper} flags := pflag.NewFlagSet("", pflag.ContinueOnError) clientConfig := optionalClientConfig if optionalClientConfig == nil { clientConfig = DefaultClientConfig(flags) } clients := &clientCache{ clients: make(map[string]*client.Client), loader: clientConfig, } return &Factory{ clients: clients, flags: flags, Object: func() (meta.RESTMapper, runtime.ObjectTyper) { cfg, err := clientConfig.ClientConfig() CheckErr(err) cmdApiVersion := cfg.Version return kubectl.OutputVersionMapper{mapper, cmdApiVersion}, api.Scheme }, Client: func() (*client.Client, error) { return clients.ClientForVersion("") }, ClientConfig: func() (*client.Config, error) { return clients.ClientConfigForVersion("") }, RESTClient: func(mapping *meta.RESTMapping) (resource.RESTClient, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } return client.RESTClient, nil }, Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } describer, ok := kubectl.DescriberFor(mapping.Kind, client) if !ok { return nil, fmt.Errorf("no description has been implemented for %q", mapping.Kind) } return describer, nil }, Printer: func(mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) { return kubectl.NewHumanReadablePrinter(noHeaders), nil }, PodSelectorForResource: func(mapping *meta.RESTMapping, namespace, name string) (string, error) { // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) client, err := clients.ClientForVersion("") if err != nil { return "", err } switch mapping.Kind { case "ReplicationController": rc, err := client.ReplicationControllers(namespace).Get(name) if err != nil { return "", err } return kubectl.MakeLabels(rc.Spec.Selector), nil case "Pod": rc, err := client.Pods(namespace).Get(name) if err != nil { return "", err } if len(rc.Labels) == 0 { return "", fmt.Errorf("the pod has no labels and cannot be exposed") } return kubectl.MakeLabels(rc.Labels), nil case "Service": rc, err := client.ReplicationControllers(namespace).Get(name) if err != nil { return "", err } if rc.Spec.Selector == nil { return "", fmt.Errorf("the service has no pod selector set") } return kubectl.MakeLabels(rc.Spec.Selector), nil default: return "", fmt.Errorf("it is not possible to get a pod selector from %s", mapping.Kind) } }, PortsForResource: func(mapping *meta.RESTMapping, namespace, name string) ([]string, error) { // TODO: replace with a swagger schema based approach (identify pod selector via schema introspection) client, err := clients.ClientForVersion("") if err != nil { return nil, err } switch mapping.Kind { case "ReplicationController": rc, err := client.ReplicationControllers(namespace).Get(name) if err != nil { return nil, err } return getPorts(rc.Spec.Template.Spec), nil case "Pod": pod, err := client.Pods(namespace).Get(name) if err != nil { return nil, err } return getPorts(pod.Spec), nil default: return nil, fmt.Errorf("it is not possible to get ports from %s", mapping.Kind) } }, Resizer: func(mapping *meta.RESTMapping) (kubectl.Resizer, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } return kubectl.ResizerFor(mapping.Kind, kubectl.NewResizerClient(client)) }, Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil { return nil, err } return kubectl.ReaperFor(mapping.Kind, client) }, Validator: func() (validation.Schema, error) { if flags.Lookup("validate").Value.String() == "true" { client, err := clients.ClientForVersion("") if err != nil { return nil, err } return &clientSwaggerSchema{client, api.Scheme}, nil } return validation.NullSchema{}, nil }, DefaultNamespace: func() (string, error) { return clientConfig.Namespace() }, } }