// NewStorage returns a RESTStorage object that will work against service accounts objects. func NewStorage(h tools.EtcdHelper) *REST { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.ServiceAccount{} }, NewListFunc: func() runtime.Object { return &api.ServiceAccountList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, Prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, Prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.ServiceAccount).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return serviceaccount.Matcher(label, field) }, EndpointName: "serviceaccounts", Helper: h, } store.CreateStrategy = serviceaccount.Strategy store.UpdateStrategy = serviceaccount.Strategy store.ReturnDeletedObject = true return &REST{store} }
// NewREST returns a RESTStorage object that will work against pod templates. func NewREST(s storage.Interface) *REST { prefix := "/podtemplates" store := etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.PodTemplate{} }, NewListFunc: func() runtime.Object { return &api.PodTemplateList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.PodTemplate).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return podtemplate.MatchPodTemplate(label, field) }, EndpointName: "podtemplates", CreateStrategy: podtemplate.Strategy, UpdateStrategy: podtemplate.Strategy, ReturnDeletedObject: true, Storage: s, } return &REST{store} }
// NewStorage returns a registry which will store Secret in the given etcdStorage func NewStorage(s storage.Interface) *REST { prefix := "/secrets" store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Secret{} }, NewListFunc: func() runtime.Object { return &api.SecretList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Secret).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return secret.Matcher(label, field) }, EndpointName: "secrets", Storage: s, } store.CreateStrategy = secret.Strategy store.UpdateStrategy = secret.Strategy return &REST{store} }
// NewStorage returns a RESTStorage object that will work against Build objects. func NewStorage(h tools.EtcdHelper) *REST { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Build{} }, NewListFunc: func() runtime.Object { return &api.BuildList{} }, EndpointName: "build", KeyRootFunc: func(ctx kapi.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, BuildPath) }, KeyFunc: func(ctx kapi.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, BuildPath, id) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Build).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return build.Matcher(label, field) }, CreateStrategy: build.Strategy, UpdateStrategy: build.Strategy, DeleteStrategy: build.Strategy, Decorator: build.Decorator, ReturnDeletedObject: false, Helper: h, } return &REST{store} }
// NewStorage returns a RESTStorage object that will work against endpoints. func NewStorage(h tools.EtcdHelper) *REST { prefix := "/services/endpoints" return &REST{ &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Endpoints{} }, NewListFunc: func() runtime.Object { return &api.EndpointsList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Endpoints).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return endpoint.MatchEndpoints(label, field) }, EndpointName: "endpoints", CreateStrategy: endpoint.Strategy, UpdateStrategy: endpoint.Strategy, Helper: h, }, } }
// NewStorage returns a RESTStorage object that will work against ResourceQuota objects. func NewStorage(s tools.StorageInterface) (*REST, *StatusREST) { prefix := "/resourcequotas" store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.ResourceQuota{} }, NewListFunc: func() runtime.Object { return &api.ResourceQuotaList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.ResourceQuota).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return resourcequota.MatchResourceQuota(label, field) }, EndpointName: "resourcequotas", Storage: s, } store.CreateStrategy = resourcequota.Strategy store.UpdateStrategy = resourcequota.Strategy store.ReturnDeletedObject = true statusStore := *store statusStore.UpdateStrategy = resourcequota.StatusStrategy return &REST{store}, &StatusREST{store: &statusStore} }
// NewStorage returns a RESTStorage object that will work against nodes. func NewStorage(s storage.Interface) *REST { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &authorizationapi.Policy{} }, NewListFunc: func() runtime.Object { return &authorizationapi.PolicyList{} }, EndpointName: "policy", KeyRootFunc: func(ctx kapi.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, PolicyPath) }, KeyFunc: func(ctx kapi.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, PolicyPath, id) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*authorizationapi.Policy).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return policy.Matcher(label, field) }, CreateStrategy: policy.Strategy, UpdateStrategy: policy.Strategy, Storage: s, } return &REST{store} }
// NewStorage returns a RESTStorage object that will work against DeploymentConfig objects. func NewStorage(s storage.Interface) *REST { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.DeploymentConfig{} }, NewListFunc: func() runtime.Object { return &api.DeploymentConfigList{} }, EndpointName: "deploymentConfig", KeyRootFunc: func(ctx kapi.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, DeploymentConfigPath) }, KeyFunc: func(ctx kapi.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, DeploymentConfigPath, id) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.DeploymentConfig).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return deployconfig.Matcher(label, field) }, CreateStrategy: deployconfig.Strategy, UpdateStrategy: deployconfig.Strategy, DeleteStrategy: deployconfig.Strategy, ReturnDeletedObject: false, Storage: s, } return &REST{store} }
// NewREST returns a RESTStorage object that will work against pods. func NewREST(h tools.EtcdHelper) (*REST, *BindingREST, *StatusREST) { prefix := "/registry/pods" store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Pod{} }, NewListFunc: func() runtime.Object { return &api.PodList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Pod).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return pod.MatchPod(label, field) }, EndpointName: "pods", Helper: h, } statusStore := *store bindings := &podLifecycle{} store.CreateStrategy = pod.Strategy store.UpdateStrategy = pod.Strategy store.AfterUpdate = bindings.AfterUpdate store.ReturnDeletedObject = true store.AfterDelete = bindings.AfterDelete statusStore.UpdateStrategy = pod.StatusStrategy return &REST{store: store}, &BindingREST{store: store}, &StatusREST{store: &statusStore} }
// NewREST returns a new REST. func NewREST(h tools.EtcdHelper, defaultRegistry imagestream.DefaultRegistry, subjectAccessReviewRegistry subjectaccessreview.Registry) (*REST, *StatusREST) { prefix := "/imagestreams" store := etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.ImageStream{} }, NewListFunc: func() runtime.Object { return &api.ImageStreamList{} }, KeyRootFunc: func(ctx kapi.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx kapi.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.ImageStream).Name, nil }, EndpointName: "imageStream", ReturnDeletedObject: false, Helper: h, } strategy := imagestream.NewStrategy(defaultRegistry, subjectAccessReviewRegistry) rest := &REST{subjectAccessReviewRegistry: subjectAccessReviewRegistry} strategy.ImageStreamGetter = rest statusStore := store statusStore.UpdateStrategy = imagestream.NewStatusStrategy(strategy) store.CreateStrategy = strategy store.UpdateStrategy = strategy store.Decorator = strategy.Decorate rest.store = &store return rest, &StatusREST{store: &statusStore} }
// NewREST returns a RESTStorage object that will work against PersistentVolumeClaim objects. func NewStorage(h tools.EtcdHelper) (*REST, *StatusREST) { prefix := "/persistentvolumeclaims" store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.PersistentVolumeClaim{} }, NewListFunc: func() runtime.Object { return &api.PersistentVolumeClaimList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.PersistentVolumeClaim).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return persistentvolumeclaim.MatchPersistentVolumeClaim(label, field) }, EndpointName: "persistentvolumeclaims", Helper: h, } store.CreateStrategy = persistentvolumeclaim.Strategy store.UpdateStrategy = persistentvolumeclaim.Strategy store.ReturnDeletedObject = true statusStore := *store statusStore.UpdateStrategy = persistentvolumeclaim.StatusStrategy return &REST{store}, &StatusREST{store: &statusStore} }
// NewEtcdRegistry returns a registry which will store LimitRange in the given helper func NewEtcdRegistry(h tools.EtcdHelper) generic.Registry { return registry{ Etcd: &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.LimitRange{} }, NewListFunc: func() runtime.Object { return &api.LimitRangeList{} }, EndpointName: "limitranges", KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/limitranges") }, KeyFunc: func(ctx api.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, "/registry/limitranges", id) }, Helper: h, }, } }
// NewEtcdRegistry returns a registry which will store Events in the given // EtcdHelper. ttl is the time that Events will be retained by the system. func NewEtcdRegistry(h tools.EtcdHelper, ttl uint64) generic.Registry { return registry{ Etcd: &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Event{} }, NewListFunc: func() runtime.Object { return &api.EventList{} }, EndpointName: "events", KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/events") }, KeyFunc: func(ctx api.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, "/registry/events", id) }, Helper: h, }, ttl: ttl, } }
// NewEtcdRegistry returns a registry which will store LimitRange in the given storage func NewEtcdRegistry(s tools.StorageInterface) generic.Registry { prefix := "/limitranges" return registry{ Etcd: &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.LimitRange{} }, NewListFunc: func() runtime.Object { return &api.LimitRangeList{} }, EndpointName: "limitranges", KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) }, Storage: s, }, } }
// NewStorage returns a RESTStorage object that will work against pods. func NewStorage(s storage.Interface, k client.ConnectionInfoGetter) PodStorage { prefix := "/pods" store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Pod{} }, NewListFunc: func() runtime.Object { return &api.PodList{} }, KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Pod).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return pod.MatchPod(label, field) }, EndpointName: "pods", Storage: s, } statusStore := *store bindings := &podLifecycle{} store.CreateStrategy = pod.Strategy store.UpdateStrategy = pod.Strategy store.AfterUpdate = bindings.AfterUpdate store.DeleteStrategy = pod.Strategy store.ReturnDeletedObject = true store.AfterDelete = bindings.AfterDelete statusStore.UpdateStrategy = pod.StatusStrategy return PodStorage{ Pod: &REST{*store}, Binding: &BindingREST{store: store}, Status: &StatusREST{store: &statusStore}, Log: &LogREST{store: store, kubeletConn: k}, Proxy: &ProxyREST{store: store}, Exec: &ExecREST{store: store, kubeletConn: k}, Attach: &AttachREST{store: store, kubeletConn: k}, PortForward: &PortForwardREST{store: store, kubeletConn: k}, } }
func TestEtcdWatchControllersMatch(t *testing.T) { ctx := api.NewDefaultContext() fakeClient := tools.NewFakeEtcdClient(t) registry := NewTestEtcdRegistryWithPods(fakeClient) path := etcdgeneric.NamespaceKeyRootFunc(ctx, "/pods") path = etcdtest.AddPrefix(path) fakeClient.ExpectNotFoundGet(path) watching, err := registry.WatchControllers(ctx, labels.SelectorFromSet(labels.Set{"name": "foo"}), fields.Everything(), "1", ) if err != nil { t.Fatalf("unexpected error: %v", err) } fakeClient.WaitForWatchCompletion() controller := &api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: map[string]string{ "name": "foo", }, }, } controllerBytes, _ := latest.Codec.Encode(controller) fakeClient.WatchResponse <- &etcd.Response{ Action: "create", Node: &etcd.Node{ Value: string(controllerBytes), }, } select { case _, ok := <-watching.ResultChan(): if !ok { t.Errorf("watching channel should be open") } case <-time.After(time.Millisecond * 100): t.Error("unexpected timeout from result channel") } watching.Stop() }
func TestEtcdWatchControllersMatch(t *testing.T) { ctx := api.WithNamespace(api.NewDefaultContext(), validController.Namespace) storage, fakeClient := newStorage(t) fakeClient.ExpectNotFoundGet(etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/pods")) watching, err := storage.Watch(ctx, labels.SelectorFromSet(validController.Spec.Selector), fields.Everything(), "1", ) if err != nil { t.Fatalf("unexpected error: %v", err) } fakeClient.WaitForWatchCompletion() // The watcher above is waiting for these Labels, on receiving them it should // apply the ControllerStatus decorator, which lists pods, causing a query against // the /registry/pods endpoint of the etcd client. controller := &api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: validController.Spec.Selector, Namespace: "default", }, } controllerBytes, _ := latest.Codec.Encode(controller) fakeClient.WatchResponse <- &etcd.Response{ Action: "create", Node: &etcd.Node{ Value: string(controllerBytes), }, } select { case _, ok := <-watching.ResultChan(): if !ok { t.Errorf("watching channel should be open") } case <-time.After(time.Millisecond * 100): t.Error("unexpected timeout from result channel") } watching.Stop() }
// NewEtcdRegistry returns a registry which will store Events in the given // EtcdStorage. ttl is the time that Events will be retained by the system. func NewEtcdRegistry(s storage.Interface, ttl uint64) generic.Registry { prefix := "/events" return registry{ Etcd: &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Event{} }, NewListFunc: func() runtime.Object { return &api.EventList{} }, EndpointName: "events", KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx api.Context, id string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, id) }, TTLFunc: func(runtime.Object, uint64, bool) (uint64, error) { return ttl, nil }, Storage: s, }, } }
func TestEtcdWatchControllersNotMatch(t *testing.T) { ctx := api.NewDefaultContext() storage, fakeClient := newStorage(t) fakeClient.ExpectNotFoundGet(etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/pods")) watching, err := storage.Watch(ctx, labels.SelectorFromSet(labels.Set{"name": "foo"}), fields.Everything(), "1", ) if err != nil { t.Fatalf("unexpected error: %v", err) } fakeClient.WaitForWatchCompletion() controller := &api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: "bar", Labels: map[string]string{ "name": "bar", }, }, } controllerBytes, _ := latest.Codec.Encode(controller) fakeClient.WatchResponse <- &etcd.Response{ Action: "create", Node: &etcd.Node{ Value: string(controllerBytes), }, } select { case <-watching.ResultChan(): t.Error("unexpected result from result channel") case <-time.After(time.Millisecond * 100): // expected case } }
// Benchmark pod listing by waiting on `Tasks` listers to list `Pods` pods via `Workers`. func BenchmarkPodListEtcd(b *testing.B) { b.StopTimer() m := framework.NewMasterComponents(&framework.Config{nil, true, false, 250.0, 500}) defer m.Stop(true, true) numPods, numTasks, iter := getPods(b.N), getTasks(b.N), getIterations(b.N) podsPerNode := numPods / numTasks if podsPerNode < 1 { podsPerNode = 1 } startPodsOnNodes(numPods, numTasks, m.RestClient) // Stop the rc manager so it doesn't steal resources m.Stop(false, true) glog.Infof("Starting benchmark: b.N %d, pods %d, workers %d, podsPerNode %d", b.N, numPods, numTasks, podsPerNode) ctx := api.WithNamespace(api.NewContext(), framework.TestNS) key := etcdgeneric.NamespaceKeyRootFunc(ctx, fmt.Sprintf("%s/pods", etcdtest.PathPrefix())) b.StartTimer() for i := 0; i < iter; i++ { framework.RunParallel(func(id int) error { now := time.Now() defer func() { glog.V(3).Infof("Worker %d: listing pods took %v", id, time.Since(now)) }() if response, err := m.EtcdHelper.Client.Get(key, true, true); err != nil { return err } else if len(response.Node.Nodes) < podsPerNode { glog.Fatalf("List retrieved %d pods, which is less than %d", len(response.Node.Nodes), podsPerNode) } return nil }, numTasks, Workers) } b.StopTimer() }
// NewREST returns a RESTStorage object that will work against replication controllers. func NewREST(h tools.EtcdHelper) *REST { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.ReplicationController{} }, // NewListFunc returns an object capable of storing results of an etcd list. NewListFunc: func() runtime.Object { return &api.ReplicationControllerList{} }, // Produces a path that etcd understands, to the root of the resource // by combining the namespace in the context with the given prefix KeyRootFunc: func(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, controllerPrefix) }, // Produces a path that etcd understands, to the resource by combining // the namespace in the context with the given prefix KeyFunc: func(ctx api.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, controllerPrefix, name) }, // Retrieve the name field of a replication controller ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.ReplicationController).Name, nil }, // Used to match objects based on labels/fields for list and watch PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return controller.MatchController(label, field) }, EndpointName: "replicationControllers", // Used to validate controller creation CreateStrategy: controller.Strategy, // Used to validate controller updates UpdateStrategy: controller.Strategy, Helper: h, } return &REST{store} }
// NewREST returns a RESTStorage object that will work against templates. func NewREST(s storage.Interface) *REST { store := &etcdgeneric.Etcd{ NewFunc: func() runtime.Object { return &api.Template{} }, NewListFunc: func() runtime.Object { return &api.TemplateList{} }, KeyRootFunc: func(ctx kapi.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, prefix) }, KeyFunc: func(ctx kapi.Context, name string) (string, error) { return etcdgeneric.NamespaceKeyFunc(ctx, prefix, name) }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Template).Name, nil }, EndpointName: "templates", CreateStrategy: registry.Strategy, UpdateStrategy: registry.Strategy, ReturnDeletedObject: true, Storage: s, } return &REST{store} }
func TestEtcdWatchControllersFields(t *testing.T) { ctx := api.WithNamespace(api.NewDefaultContext(), validController.Namespace) storage, fakeClient := newStorage(t) fakeClient.ExpectNotFoundGet(etcdgeneric.NamespaceKeyRootFunc(ctx, "/registry/pods")) testFieldMap := map[int][]fields.Set{ PASS: { {"status.replicas": "0"}, {"metadata.name": "foo"}, {"status.replicas": "0", "metadata.name": "foo"}, }, FAIL: { {"status.replicas": "10"}, {"metadata.name": "bar"}, {"name": "foo"}, {"status.replicas": "10", "metadata.name": "foo"}, {"status.replicas": "0", "metadata.name": "bar"}, }, } testEtcdActions := []string{ tools.EtcdCreate, tools.EtcdSet, tools.EtcdCAS, tools.EtcdDelete} controller := &api.ReplicationController{ ObjectMeta: api.ObjectMeta{ Name: "foo", Labels: validController.Spec.Selector, Namespace: "default", }, Status: api.ReplicationControllerStatus{ Replicas: 0, }, } controllerBytes, _ := latest.Codec.Encode(controller) for expectedResult, fieldSet := range testFieldMap { for _, field := range fieldSet { for _, action := range testEtcdActions { watching, err := storage.Watch(ctx, labels.Everything(), field.AsSelector(), "1", ) var prevNode *etcd.Node = nil node := &etcd.Node{ Value: string(controllerBytes), } if action == tools.EtcdDelete { prevNode = node } fakeClient.WaitForWatchCompletion() fakeClient.WatchResponse <- &etcd.Response{ Action: action, Node: node, PrevNode: prevNode, } if err != nil { t.Fatalf("unexpected error: %v", err) } select { case r, ok := <-watching.ResultChan(): if expectedResult == FAIL { t.Errorf("Unexpected result from channel %#v", r) } if !ok { t.Errorf("watching channel should be open") } case <-time.After(time.Millisecond * 100): if expectedResult == PASS { t.Error("unexpected timeout from result channel") } } watching.Stop() } } } }
// makeControllerListKey constructs etcd paths to the root of the resource, // not a specific controller resource func makeControllerListKey(ctx api.Context) string { return etcdgeneric.NamespaceKeyRootFunc(ctx, controllerPrefix) }