// TestValidNamespace validates that namespace rules are enforced on a resource prior to create or update func TestValidNamespace(t *testing.T) { ctx := api.NewDefaultContext() namespace, _ := api.NamespaceFrom(ctx) resource := api.ReplicationController{} if !api.ValidNamespace(ctx, &resource.ObjectMeta) { t.Errorf("expected success") } if namespace != resource.Namespace { t.Errorf("expected resource to have the default namespace assigned during validation") } resource = api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: "other"}} if api.ValidNamespace(ctx, &resource.ObjectMeta) { t.Errorf("Expected error that resource and context errors do not match because resource has different namespace") } ctx = api.NewContext() if api.ValidNamespace(ctx, &resource.ObjectMeta) { t.Errorf("Expected error that resource and context errors do not match since context has no namespace") } ctx = api.NewContext() ns := api.NamespaceValue(ctx) if ns != "" { t.Errorf("Expected the empty string") } }
func TestEtcdDelete(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}, Spec: api.PodSpec{NodeName: "machine"}, } nodeWithPodA := tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Value: runtime.EncodeOrDie(testapi.Default.Codec(), podA), ModifiedIndex: 1, CreatedIndex: 1, }, }, E: nil, } emptyNode := tools.EtcdResponseWithError{ R: &etcd.Response{}, E: tools.EtcdErrorNotFound, } testContext := api.WithNamespace(api.NewContext(), "test") key := "foo" table := map[string]struct { existing tools.EtcdResponseWithError expect tools.EtcdResponseWithError errOK func(error) bool }{ "normal": { existing: nodeWithPodA, expect: emptyNode, errOK: func(err error) bool { return err == nil }, }, "notExisting": { existing: emptyNode, expect: emptyNode, errOK: func(err error) bool { return errors.IsNotFound(err) }, }, } for name, item := range table { fakeClient, registry := NewTestGenericEtcdRegistry(t) path := etcdtest.AddPrefix("pods/foo") fakeClient.Data[path] = item.existing obj, err := registry.Delete(testContext, key, nil) if !item.errOK(err) { t.Errorf("%v: unexpected error: %v (%#v)", name, err, obj) } if item.expect.E != nil { item.expect.E.(*etcd.EtcdError).Index = fakeClient.ChangeIndex } if e, a := item.expect, fakeClient.Data[path]; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a)) } } }
func TestScaleUpdate(t *testing.T) { storage, fakeClient := newStorage(t) ctx := api.WithNamespace(api.NewContext(), namespace) key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Extensions.Codec(), &validDeployment), 0); err != nil { t.Fatalf("unexpected error: %v", err) } replicas := 12 update := extensions.Scale{ ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace}, Spec: extensions.ScaleSpec{ Replicas: replicas, }, } if _, _, err := storage.Scale.Update(ctx, &update); err != nil { t.Fatalf("unexpected error: %v", err) } response, err := fakeClient.Get(key, false, false) if err != nil { t.Fatalf("unexpected error: %v", err) } var deployment extensions.Deployment testapi.Extensions.Codec().DecodeInto([]byte(response.Node.Value), &deployment) if deployment.Spec.Replicas != replicas { t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas) } }
// Bind just does a POST binding RPC. func (b *binder) Bind(binding *api.Binding) error { glog.V(2).Infof("Attempting to bind %v to %v", binding.Name, binding.Target.Name) ctx := api.WithNamespace(api.NewContext(), binding.Namespace) return b.Post().Namespace(api.NamespaceValue(ctx)).Resource("bindings").Body(binding).Do().Error() // TODO: use Pods interface for binding once clusters are upgraded // return b.Pods(binding.Namespace).Bind(binding) }
func TestUpdate(t *testing.T) { storage, fakeClient := newStorage(t) ctx := api.WithNamespace(api.NewContext(), "test") key := etcdtest.AddPrefix("/controllers/test/foo") if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Default.Codec(), &validController), 0); err != nil { t.Fatalf("unexpected error: %v", err) } replicas := 12 update := extensions.Scale{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, Spec: extensions.ScaleSpec{ Replicas: replicas, }, } if _, _, err := storage.Update(ctx, &update); err != nil { t.Fatalf("unexpected error: %v", err) } response, err := fakeClient.Get(key, false, false) if err != nil { t.Fatalf("unexpected error: %v", err) } var controller api.ReplicationController testapi.Extensions.Codec().DecodeInto([]byte(response.Node.Value), &controller) if controller.Spec.Replicas != replicas { t.Errorf("wrong replicas count expected: %d got: %d", replicas, controller.Spec.Replicas) } }
// testGetDifferentNamespace ensures same-name objects in different namespaces do not clash func (t *Tester) testGetDifferentNamespace(obj runtime.Object) { if t.clusterScope { t.Fatalf("the test does not work in in cluster-scope") } objMeta := t.getObjectMetaOrFail(obj) objMeta.Name = "foo5" ctx1 := api.WithNamespace(api.NewContext(), "bar3") objMeta.Namespace = api.NamespaceValue(ctx1) _, err := t.storage.(rest.Creater).Create(ctx1, obj) if err != nil { t.Errorf("unexpected error: %v", err) } ctx2 := api.WithNamespace(api.NewContext(), "bar4") objMeta.Namespace = api.NamespaceValue(ctx2) _, err = t.storage.(rest.Creater).Create(ctx2, obj) if err != nil { t.Errorf("unexpected error: %v", err) } got1, err := t.storage.(rest.Getter).Get(ctx1, objMeta.Name) if err != nil { t.Errorf("unexpected error: %v", err) } got1Meta := t.getObjectMetaOrFail(got1) if got1Meta.Name != objMeta.Name { t.Errorf("unexpected name of object: %#v, expected: %s", got1, objMeta.Name) } if got1Meta.Namespace != api.NamespaceValue(ctx1) { t.Errorf("unexpected namespace of object: %#v, expected: %s", got1, api.NamespaceValue(ctx1)) } got2, err := t.storage.(rest.Getter).Get(ctx2, objMeta.Name) if err != nil { t.Errorf("unexpected error: %v", err) } got2Meta := t.getObjectMetaOrFail(got2) if got2Meta.Name != objMeta.Name { t.Errorf("unexpected name of object: %#v, expected: %s", got2, objMeta.Name) } if got2Meta.Namespace != api.NamespaceValue(ctx2) { t.Errorf("unexpected namespace of object: %#v, expected: %s", got2, api.NamespaceValue(ctx2)) } }
// createController is a helper function that returns a controller with the updated resource version. func createController(storage *REST, rc api.ReplicationController, t *testing.T) (api.ReplicationController, error) { ctx := api.WithNamespace(api.NewContext(), rc.Namespace) obj, err := storage.Create(ctx, &rc) if err != nil { t.Errorf("Failed to create controller, %v", err) } newRc := obj.(*api.ReplicationController) return *newRc, nil }
func TestGet_BadName(t *testing.T) { r := NewTestREST(testResponse{code: 200, data: "ok"}) _, err := r.Get(api.NewContext(), "invalidname") if err == nil { t.Fatalf("Expected error, but did not get one") } if !strings.Contains(err.Error(), "Component not found: invalidname") { t.Fatalf("Got unexpected error: %v", err) } }
func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { storage, _, _, fakeClient := newStorage(t) fakeClient.TestIndex = true pod := validNewPod() pod.Namespace = "" _, err := storage.Create(api.NewContext(), pod) // Accept "namespace" or "Namespace". if err == nil || !strings.Contains(err.Error(), "amespace") { t.Fatalf("expected error that namespace was missing from context, got: %v", err) } }
func TestGet_NoError(t *testing.T) { r := NewTestREST(testResponse{code: 200, data: "ok"}) got, err := r.Get(api.NewContext(), "test1") if err != nil { t.Fatalf("Unexpected error: %v", err) } expect := createTestStatus("test1", api.ConditionTrue, "ok", "nil") if e, a := expect, got; !reflect.DeepEqual(e, a) { t.Errorf("Got unexpected object. Diff: %s", util.ObjectDiff(e, a)) } }
func (t *Tester) testGetMimatchedNamespace(obj runtime.Object) { ctx1 := api.WithNamespace(api.NewContext(), "bar1") ctx2 := api.WithNamespace(api.NewContext(), "bar2") objMeta := t.getObjectMetaOrFail(obj) objMeta.Name = "foo4" objMeta.Namespace = api.NamespaceValue(ctx1) _, err := t.storage.(rest.Creater).Create(ctx1, obj) if err != nil { t.Errorf("unexpected error: %v", err) } _, err = t.storage.(rest.Getter).Get(ctx2, "foo4") if t.clusterScope { if err != nil { t.Errorf("unexpected error: %v", err) } } else { if !errors.IsNotFound(err) { t.Errorf("unexpected error returned: %#v", err) } } }
func TestList_NoError(t *testing.T) { r := NewTestREST(testResponse{code: 200, data: "ok"}) got, err := r.List(api.NewContext(), labels.Everything(), fields.Everything()) if err != nil { t.Fatalf("Unexpected error: %v", err) } expect := &api.ComponentStatusList{ Items: []api.ComponentStatus{*(createTestStatus("test1", api.ConditionTrue, "ok", "nil"))}, } if e, a := expect, got; !reflect.DeepEqual(e, a) { t.Errorf("Got unexpected object. Diff: %s", util.ObjectDiff(e, a)) } }
func (t *Tester) testCreateIgnoresContextNamespace(valid runtime.Object) { // Ignore non-empty namespace in context ctx := api.WithNamespace(api.NewContext(), "not-default2") // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted created, err := t.storage.(rest.Creater).Create(ctx, copyOrDie(valid)) if err != nil { t.Fatalf("Unexpected error: %v", err) } createdObjectMeta := t.getObjectMetaOrFail(created) if createdObjectMeta.Namespace != api.NamespaceNone { t.Errorf("Expected empty namespace on created object, got '%v'", createdObjectMeta.Namespace) } }
func TestList_UnknownError(t *testing.T) { r := NewTestREST(testResponse{code: 500, data: "", err: fmt.Errorf("fizzbuzz error")}) got, err := r.List(api.NewContext(), labels.Everything(), fields.Everything()) if err != nil { t.Fatalf("Unexpected error: %v", err) } expect := &api.ComponentStatusList{ Items: []api.ComponentStatus{ *(createTestStatus("test1", api.ConditionUnknown, "", "Get http://testserver1:8000/healthz: fizzbuzz error"))}, } if e, a := expect, got; !reflect.DeepEqual(e, a) { t.Errorf("Got unexpected object. Diff: %s", util.ObjectDiff(e, a)) } }
// TestNamespaceContext validates that a namespace can be get/set on a context object func TestNamespaceContext(t *testing.T) { ctx := api.NewDefaultContext() result, ok := api.NamespaceFrom(ctx) if !ok { t.Errorf("Error getting namespace") } if api.NamespaceDefault != result { t.Errorf("Expected: %v, Actual: %v", api.NamespaceDefault, result) } ctx = api.NewContext() result, ok = api.NamespaceFrom(ctx) if ok { t.Errorf("Should not be ok because there is no namespace on the context") } }
// CreateNamespaceIfNeeded will create the namespace that contains the master services if it doesn't already exist func (c *Controller) CreateNamespaceIfNeeded(ns string) error { ctx := api.NewContext() if _, err := c.NamespaceRegistry.GetNamespace(ctx, api.NamespaceDefault); err == nil { // the namespace already exists return nil } newNs := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: ns, Namespace: "", }, } err := c.NamespaceRegistry.CreateNamespace(ctx, newNs) if err != nil && errors.IsAlreadyExists(err) { err = nil } return err }
func TestScaleGet(t *testing.T) { storage, fakeClient := newStorage(t) ctx := api.WithNamespace(api.NewContext(), namespace) key := etcdtest.AddPrefix("/deployments/" + namespace + "/" + name) if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Extensions.Codec(), &validDeployment), 0); err != nil { t.Fatalf("unexpected error: %v", err) } expect := &validScale obj, err := storage.Scale.Get(ctx, name) scale := obj.(*extensions.Scale) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := expect, scale; !api.Semantic.DeepEqual(e, a) { t.Errorf("unexpected scale: %s", util.ObjectDiff(e, a)) } }
func TestGet(t *testing.T) { storage, fakeClient := newStorage(t) ctx := api.WithNamespace(api.NewContext(), "test") key := etcdtest.AddPrefix("/controllers/test/foo") if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Default.Codec(), &validController), 0); err != nil { t.Fatalf("unexpected error: %v", err) } expect := &validScale obj, err := storage.Get(ctx, "foo") scale := obj.(*extensions.Scale) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := expect, scale; !api.Semantic.DeepEqual(e, a) { t.Errorf("unexpected scale: %s", util.ObjectDiff(e, a)) } }
func TestDeleteNamespaceWithCompleteFinalizers(t *testing.T) { storage, fakeClient := newStorage(t) key := etcdtest.AddPrefix("namespaces/foo") ctx := api.NewContext() now := unversioned.Now() namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: "foo", DeletionTimestamp: &now, }, Spec: api.NamespaceSpec{ Finalizers: []api.FinalizerName{}, }, Status: api.NamespaceStatus{Phase: api.NamespaceActive}, } if _, err := fakeClient.Set(key, runtime.EncodeOrDie(testapi.Default.Codec(), namespace), 0); err != nil { t.Fatalf("unexpected error: %v", err) } if _, err := storage.Delete(ctx, "foo", nil); err != nil { t.Errorf("unexpected error: %v", err) } }
// 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.EtcdStorage.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() }
// implements binding.Registry, launches the pod-associated-task in mesos func (b *binder) Bind(binding *api.Binding) error { ctx := api.WithNamespace(api.NewContext(), binding.Namespace) // default upstream scheduler passes pod.Name as binding.Name podKey, err := podtask.MakePodKey(ctx, binding.Name) if err != nil { return err } b.api.Lock() defer b.api.Unlock() switch task, state := b.api.tasks().ForPod(podKey); state { case podtask.StatePending: return b.bind(ctx, binding, task) default: // in this case it's likely that the pod has been deleted between Schedule // and Bind calls log.Infof("No pending task for pod %s", podKey) return noSuchPodErr //TODO(jdef) this error is somewhat misleading since the task could be running?! } }
func (t *Tester) TestCreateIgnoresMismatchedNamespace(valid runtime.Object) { objectMeta, err := api.ObjectMetaFor(valid) if err != nil { t.Fatalf("object does not have ObjectMeta: %v\n%#v", err, valid) } // Ignore non-empty namespace in object meta objectMeta.Namespace = "not-default" ctx := api.WithNamespace(api.NewContext(), "not-default2") // Ideally, we'd get an error back here, but at least verify the namespace wasn't persisted created, err := t.storage.(rest.Creater).Create(ctx, copyOrDie(valid)) if err != nil { t.Fatalf("Unexpected error: %v", err) } createdObjectMeta, err := api.ObjectMetaFor(created) if err != nil { t.Fatalf("object does not have ObjectMeta: %v\n%#v", err, created) } if createdObjectMeta.Namespace != api.NamespaceNone { t.Errorf("Expected empty namespace on created object, got '%v'", createdObjectMeta.Namespace) } }
func TestUpdateStatus(t *testing.T) { storage, statusStorage, fakeClient := newStorage(t) fakeClient.TestIndex = true ctx := api.NewContext() key, _ := storage.KeyFunc(ctx, "foo") key = etcdtest.AddPrefix(key) pvStart := validNewPersistentVolume("foo") fakeClient.Set(key, runtime.EncodeOrDie(testapi.Default.Codec(), pvStart), 0) pvIn := &api.PersistentVolume{ ObjectMeta: api.ObjectMeta{ Name: "foo", ResourceVersion: "1", }, Status: api.PersistentVolumeStatus{ Phase: api.VolumeBound, }, } expected := *pvStart expected.ResourceVersion = "2" expected.Labels = pvIn.Labels expected.Status = pvIn.Status _, _, err := statusStorage.Update(ctx, pvIn) if err != nil { t.Fatalf("Unexpected error: %v", err) } pvOut, err := storage.Get(ctx, "foo") if err != nil { t.Errorf("unexpected error: %v", err) } if !api.Semantic.DeepEqual(&expected, pvOut) { t.Errorf("unexpected object: %s", util.ObjectDiff(&expected, pvOut)) } }
func TestCreateSetsFields(t *testing.T) { storage, _ := newStorage(t) namespace := validNewNamespace() ctx := api.NewContext() _, err := storage.Create(ctx, namespace) if err != nil { t.Fatalf("unexpected error: %v", err) } object, err := storage.Get(ctx, "foo") if err != nil { t.Errorf("unexpected error: %v", err) } actual := object.(*api.Namespace) if actual.Name != namespace.Name { t.Errorf("unexpected namespace: %#v", actual) } if len(actual.UID) == 0 { t.Errorf("expected namespace UID to be set: %#v", actual) } if actual.Status.Phase != api.NamespaceActive { t.Errorf("expected namespace phase to be set to active, but %v", actual.Status.Phase) } }
func TestEtcdList(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "foo"}, Spec: api.PodSpec{NodeName: "machine"}, } podB := &api.Pod{ ObjectMeta: api.ObjectMeta{Namespace: "test", Name: "bar"}, Spec: api.PodSpec{NodeName: "machine"}, } singleElemListResp := &etcd.Response{ Node: &etcd.Node{ Value: runtime.EncodeOrDie(testapi.Default.Codec(), podA), }, } normalListResp := &etcd.Response{ Node: &etcd.Node{ Nodes: []*etcd.Node{ {Value: runtime.EncodeOrDie(testapi.Default.Codec(), podA)}, {Value: runtime.EncodeOrDie(testapi.Default.Codec(), podB)}, }, }, } testContext := api.WithNamespace(api.NewContext(), "test") noNamespaceContext := api.NewContext() table := map[string]struct { in tools.EtcdResponseWithError m generic.Matcher out runtime.Object context api.Context succeed bool }{ "empty": { in: tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Nodes: []*etcd.Node{}, }, }, E: nil, }, m: everythingMatcher{}, out: &api.PodList{Items: []api.Pod{}}, succeed: true, }, "notFound": { in: tools.EtcdResponseWithError{ R: &etcd.Response{}, E: tools.EtcdErrorNotFound, }, m: everythingMatcher{}, out: &api.PodList{Items: []api.Pod{}}, succeed: true, }, "normal": { in: tools.EtcdResponseWithError{ R: normalListResp, E: nil, }, m: everythingMatcher{}, out: &api.PodList{Items: []api.Pod{*podA, *podB}}, succeed: true, }, "normalFiltered": { in: tools.EtcdResponseWithError{ R: singleElemListResp, E: nil, }, m: setMatcher{sets.NewString("foo")}, out: &api.PodList{Items: []api.Pod{*podA}}, succeed: true, }, "normalFilteredNoNamespace": { in: tools.EtcdResponseWithError{ R: normalListResp, E: nil, }, m: setMatcher{sets.NewString("foo")}, out: &api.PodList{Items: []api.Pod{*podA}}, context: noNamespaceContext, succeed: true, }, "normalFilteredMatchMultiple": { in: tools.EtcdResponseWithError{ R: normalListResp, E: nil, }, m: setMatcher{sets.NewString("foo", "makeMatchSingleReturnFalse")}, out: &api.PodList{Items: []api.Pod{*podA}}, succeed: true, }, } for name, item := range table { ctx := testContext if item.context != nil { ctx = item.context } fakeClient, registry := NewTestGenericEtcdRegistry(t) if name, ok := item.m.MatchesSingle(); ok { if key, err := registry.KeyFunc(ctx, name); err == nil { key = etcdtest.AddPrefix(key) fakeClient.Data[key] = item.in } else { key := registry.KeyRootFunc(ctx) key = etcdtest.AddPrefix(key) fakeClient.Data[key] = item.in } } else { key := registry.KeyRootFunc(ctx) key = etcdtest.AddPrefix(key) fakeClient.Data[key] = item.in } list, err := registry.ListPredicate(ctx, item.m) if e, a := item.succeed, err == nil; e != a { t.Errorf("%v: expected %v, got %v: %v", name, e, a, err) continue } if e, a := item.out, list; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v: Expected %#v, got %#v", name, e, a) } } }
func TestEtcdCreate(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, Spec: api.PodSpec{NodeName: "machine"}, } podB := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, Spec: api.PodSpec{NodeName: "machine2"}, } nodeWithPodA := tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Value: runtime.EncodeOrDie(testapi.Default.Codec(), podA), ModifiedIndex: 1, CreatedIndex: 1, }, }, E: nil, } emptyNode := tools.EtcdResponseWithError{ R: &etcd.Response{}, E: tools.EtcdErrorNotFound, } testContext := api.WithNamespace(api.NewContext(), "test") table := map[string]struct { existing tools.EtcdResponseWithError expect tools.EtcdResponseWithError toCreate runtime.Object objOK func(obj runtime.Object) bool errOK func(error) bool }{ "normal": { existing: emptyNode, toCreate: podA, objOK: hasCreated(t, podA), errOK: func(err error) bool { return err == nil }, }, "preExisting": { existing: nodeWithPodA, expect: nodeWithPodA, toCreate: podB, errOK: errors.IsAlreadyExists, }, } for name, item := range table { fakeClient, registry := NewTestGenericEtcdRegistry(t) path := etcdtest.AddPrefix("pods/foo") fakeClient.Data[path] = item.existing obj, err := registry.Create(testContext, item.toCreate) if !item.errOK(err) { t.Errorf("%v: unexpected error: %v", name, err) } actual := fakeClient.Data[path] if item.objOK != nil { if !item.objOK(obj) { t.Errorf("%v: unexpected returned: %v", name, obj) } actualObj, err := api.Scheme.Decode([]byte(actual.R.Node.Value)) if err != nil { t.Errorf("unable to decode stored value for %#v", actual) continue } if !item.objOK(actualObj) { t.Errorf("%v: unexpected response: %v", name, actual) } } else { if e, a := item.expect, actual; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a)) } } } }
func TestEtcdWatch(t *testing.T) { testContext := api.WithNamespace(api.NewContext(), "test") noNamespaceContext := api.NewContext() table := map[string]struct { generic.Matcher context api.Context }{ "single": { Matcher: setMatcher{sets.NewString("foo")}, }, "multi": { Matcher: setMatcher{sets.NewString("foo", "bar")}, }, "singleNoNamespace": { Matcher: setMatcher{sets.NewString("foo")}, context: noNamespaceContext, }, } for name, m := range table { ctx := testContext if m.context != nil { ctx = m.context } podA := &api.Pod{ ObjectMeta: api.ObjectMeta{ Name: "foo", Namespace: "test", ResourceVersion: "1", }, Spec: api.PodSpec{NodeName: "machine"}, } respWithPodA := &etcd.Response{ Node: &etcd.Node{ Key: "/registry/pods/test/foo", Value: runtime.EncodeOrDie(testapi.Default.Codec(), podA), ModifiedIndex: 1, CreatedIndex: 1, }, Action: "create", } fakeClient, registry := NewTestGenericEtcdRegistry(t) wi, err := registry.WatchPredicate(ctx, m, "1") if err != nil { t.Errorf("%v: unexpected error: %v", name, err) continue } fakeClient.WaitForWatchCompletion() go func() { fakeClient.WatchResponse <- respWithPodA }() got, open := <-wi.ResultChan() if !open { t.Errorf("%v: unexpected channel close", name) continue } if e, a := podA, got.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v: difference: %s", name, util.ObjectDiff(e, a)) } } }
func TestEtcdUpdate(t *testing.T) { podA := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test"}, Spec: api.PodSpec{NodeName: "machine"}, } podB := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "1"}, Spec: api.PodSpec{NodeName: "machine2"}, } podAWithResourceVersion := &api.Pod{ ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "3"}, Spec: api.PodSpec{NodeName: "machine"}, } nodeWithPodA := tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Value: runtime.EncodeOrDie(testapi.Default.Codec(), podA), ModifiedIndex: 1, CreatedIndex: 1, }, }, E: nil, } newerNodeWithPodA := tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Value: runtime.EncodeOrDie(testapi.Default.Codec(), podA), ModifiedIndex: 2, CreatedIndex: 1, }, }, E: nil, } nodeWithPodB := tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Value: runtime.EncodeOrDie(testapi.Default.Codec(), podB), ModifiedIndex: 1, CreatedIndex: 1, }, }, E: nil, } nodeWithPodAWithResourceVersion := tools.EtcdResponseWithError{ R: &etcd.Response{ Node: &etcd.Node{ Value: runtime.EncodeOrDie(testapi.Default.Codec(), podAWithResourceVersion), ModifiedIndex: 3, CreatedIndex: 1, }, }, E: nil, } emptyNode := tools.EtcdResponseWithError{ R: &etcd.Response{}, E: tools.EtcdErrorNotFound, } testContext := api.WithNamespace(api.NewContext(), "test") table := map[string]struct { existing tools.EtcdResponseWithError expect tools.EtcdResponseWithError toUpdate runtime.Object allowCreate bool allowUnconditionalUpdate bool objOK func(obj runtime.Object) bool errOK func(error) bool }{ "normal": { existing: nodeWithPodA, expect: nodeWithPodB, toUpdate: podB, errOK: func(err error) bool { return err == nil }, }, "notExisting": { existing: emptyNode, expect: emptyNode, toUpdate: podA, errOK: func(err error) bool { return errors.IsNotFound(err) }, }, "createIfNotFound": { existing: emptyNode, toUpdate: podA, allowCreate: true, objOK: hasCreated(t, podA), errOK: func(err error) bool { return err == nil }, }, "outOfDate": { existing: newerNodeWithPodA, expect: newerNodeWithPodA, toUpdate: podB, errOK: func(err error) bool { return errors.IsConflict(err) }, }, "unconditionalUpdate": { existing: nodeWithPodAWithResourceVersion, allowUnconditionalUpdate: true, toUpdate: podA, objOK: func(obj runtime.Object) bool { return true }, errOK: func(err error) bool { return err == nil }, }, } for name, item := range table { fakeClient, registry := NewTestGenericEtcdRegistry(t) registry.UpdateStrategy.(*testRESTStrategy).allowCreateOnUpdate = item.allowCreate registry.UpdateStrategy.(*testRESTStrategy).allowUnconditionalUpdate = item.allowUnconditionalUpdate path := etcdtest.AddPrefix("pods/foo") fakeClient.Data[path] = item.existing obj, _, err := registry.Update(testContext, item.toUpdate) if !item.errOK(err) { t.Errorf("%v: unexpected error: %v", name, err) } actual := fakeClient.Data[path] if item.objOK != nil { if !item.objOK(obj) { t.Errorf("%v: unexpected returned: %#v", name, obj) } actualObj, err := api.Scheme.Decode([]byte(actual.R.Node.Value)) if err != nil { t.Errorf("unable to decode stored value for %#v", actual) continue } if !item.objOK(actualObj) { t.Errorf("%v: unexpected response: %#v", name, actual) } } else { if e, a := item.expect, actual; !api.Semantic.DeepDerivative(e, a) { t.Errorf("%v:\n%s", name, util.ObjectDiff(e, a)) } } } }
func (r *registryGetter) GetSecret(namespace, name string) (*api.Secret, error) { ctx := api.WithNamespace(api.NewContext(), namespace) return r.secrets.GetSecret(ctx, name) }
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*unversioned.APIResource, error) { admit := a.group.Admit context := a.group.Context serverVersion := a.group.ServerVersion if len(serverVersion) == 0 { serverVersion = a.group.Version } var resource, subresource string switch parts := strings.Split(path, "/"); len(parts) { case 2: resource, subresource = parts[0], parts[1] case 1: resource = parts[0] default: // TODO: support deeper paths return nil, fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)") } hasSubresource := len(subresource) > 0 object := storage.New() _, kind, err := a.group.Typer.ObjectVersionAndKind(object) if err != nil { return nil, err } versionedPtr, err := a.group.Creater.New(a.group.Version, kind) if err != nil { return nil, err } versionedObject := indirectArbitraryPointer(versionedPtr) mapping, err := a.group.Mapper.RESTMapping(kind, a.group.Version) if err != nil { return nil, err } // subresources must have parent resources, and follow the namespacing rules of their parent if hasSubresource { parentStorage, ok := a.group.Storage[resource] if !ok { return nil, fmt.Errorf("subresources can only be declared when the parent is also registered: %s needs %s", path, resource) } parentObject := parentStorage.New() _, parentKind, err := a.group.Typer.ObjectVersionAndKind(parentObject) if err != nil { return nil, err } parentMapping, err := a.group.Mapper.RESTMapping(parentKind, a.group.Version) if err != nil { return nil, err } mapping.Scope = parentMapping.Scope } // what verbs are supported by the storage, used to know what verbs we support per path creater, isCreater := storage.(rest.Creater) namedCreater, isNamedCreater := storage.(rest.NamedCreater) lister, isLister := storage.(rest.Lister) getter, isGetter := storage.(rest.Getter) getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions) deleter, isDeleter := storage.(rest.Deleter) gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter) updater, isUpdater := storage.(rest.Updater) patcher, isPatcher := storage.(rest.Patcher) watcher, isWatcher := storage.(rest.Watcher) _, isRedirector := storage.(rest.Redirector) connecter, isConnecter := storage.(rest.Connecter) storageMeta, isMetadata := storage.(rest.StorageMetadata) if !isMetadata { storageMeta = defaultStorageMetadata{} } if isNamedCreater { isCreater = true } var versionedList interface{} if isLister { list := lister.NewList() _, listKind, err := a.group.Typer.ObjectVersionAndKind(list) versionedListPtr, err := a.group.Creater.New(a.group.Version, listKind) if err != nil { return nil, err } versionedList = indirectArbitraryPointer(versionedListPtr) } versionedListOptions, err := a.group.Creater.New(serverVersion, "ListOptions") if err != nil { return nil, err } var versionedDeleterObject interface{} switch { case isGracefulDeleter: objectPtr, err := a.group.Creater.New(serverVersion, "DeleteOptions") if err != nil { return nil, err } versionedDeleterObject = indirectArbitraryPointer(objectPtr) isDeleter = true case isDeleter: gracefulDeleter = rest.GracefulDeleteAdapter{Deleter: deleter} } versionedStatusPtr, err := a.group.Creater.New(serverVersion, "Status") if err != nil { return nil, err } versionedStatus := indirectArbitraryPointer(versionedStatusPtr) var ( getOptions runtime.Object versionedGetOptions runtime.Object getOptionsKind string getSubpath bool getSubpathKey string ) if isGetterWithOptions { getOptions, getSubpath, getSubpathKey = getterWithOptions.NewGetOptions() _, getOptionsKind, err = a.group.Typer.ObjectVersionAndKind(getOptions) if err != nil { return nil, err } versionedGetOptions, err = a.group.Creater.New(serverVersion, getOptionsKind) if err != nil { return nil, err } isGetter = true } var ( connectOptions runtime.Object versionedConnectOptions runtime.Object connectOptionsKind string connectSubpath bool connectSubpathKey string ) if isConnecter { connectOptions, connectSubpath, connectSubpathKey = connecter.NewConnectOptions() if connectOptions != nil { _, connectOptionsKind, err = a.group.Typer.ObjectVersionAndKind(connectOptions) if err != nil { return nil, err } versionedConnectOptions, err = a.group.Creater.New(serverVersion, connectOptionsKind) } } var ctxFn ContextFunc ctxFn = func(req *restful.Request) api.Context { if context == nil { return api.NewContext() } if ctx, ok := context.Get(req.Request); ok { return ctx } return api.NewContext() } allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list. scope := mapping.Scope nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string") pathParam := ws.PathParameter("path", "path to the resource").DataType("string") params := []*restful.Parameter{} actions := []action{} var apiResource unversioned.APIResource // Get the list of actions for the given scope. switch scope.Name() { case meta.RESTScopeNameRoot: // Handle non-namespace scoped resources like nodes. resourcePath := resource resourceParams := params itemPath := resourcePath + "/{name}" nameParams := append(params, nameParam) proxyParams := append(nameParams, pathParam) if hasSubresource { itemPath = itemPath + "/" + subresource resourcePath = itemPath resourceParams = nameParams } apiResource.Name = path apiResource.Namespaced = false namer := rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath)} // Handler for standard REST verbs (GET, PUT, POST and DELETE). // Add actions at the resource path: /api/apiVersion/resource actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister) actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer}, isCreater) actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer}, allowWatchList) // Add actions at the item path: /api/apiVersion/resource/{name} actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) if getSubpath { actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer}, isGetter) } actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher) actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter) actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter) actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer}, isConnecter && connectSubpath) break case meta.RESTScopeNameNamespace: // Handler for standard REST verbs (GET, PUT, POST and DELETE). namespaceParam := ws.PathParameter(scope.ArgumentName(), scope.ParamDescription()).DataType("string") namespacedPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/" + resource namespaceParams := []*restful.Parameter{namespaceParam} resourcePath := namespacedPath resourceParams := namespaceParams itemPath := namespacedPath + "/{name}" nameParams := append(namespaceParams, nameParam) proxyParams := append(nameParams, pathParam) if hasSubresource { itemPath = itemPath + "/" + subresource resourcePath = itemPath resourceParams = nameParams } apiResource.Name = path apiResource.Namespaced = true namer := scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), false} actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister) actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer}, isCreater) // DEPRECATED actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer}, allowWatchList) actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter) if getSubpath { actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer}, isGetter) } actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater) actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher) actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter) actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector) actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector) actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter) actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer}, isConnecter && connectSubpath) // list or post across namespace. // For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods. // TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete) if !hasSubresource { namer = scopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath), true} actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister) actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer}, allowWatchList) } break default: return nil, fmt.Errorf("unsupported restscope: %s", scope.Name()) } // Create Routes for the actions. // TODO: Add status documentation using Returns() // Errors (see api/errors/errors.go as well as go-restful router): // http.StatusNotFound, http.StatusMethodNotAllowed, // http.StatusUnsupportedMediaType, http.StatusNotAcceptable, // http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, // http.StatusRequestTimeout, http.StatusConflict, http.StatusPreconditionFailed, // 422 (StatusUnprocessableEntity), http.StatusInternalServerError, // http.StatusServiceUnavailable // and api error codes // Note that if we specify a versioned Status object here, we may need to // create one for the tests, also // Success: // http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent // // test/integration/auth_test.go is currently the most comprehensive status code test reqScope := RequestScope{ ContextFunc: ctxFn, Creater: a.group.Creater, Convertor: a.group.Convertor, Codec: mapping.Codec, APIVersion: a.group.Version, ServerAPIVersion: serverVersion, Resource: resource, Subresource: subresource, Kind: kind, } for _, action := range actions { reqScope.Namer = action.Namer m := monitorFilter(action.Verb, resource) namespaced := "" if strings.Contains(action.Path, scope.ArgumentName()) { namespaced = "Namespaced" } switch action.Verb { case "GET": // Get a resource. var handler restful.RouteFunction if isGetterWithOptions { handler = GetResourceWithOptions(getterWithOptions, reqScope, getOptionsKind, getSubpath, getSubpathKey) } else { handler = GetResource(getter, reqScope) } doc := "read the specified " + kind if hasSubresource { doc = "read " + subresource + " of the specified " + kind } route := ws.GET(action.Path).To(handler). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("read"+namespaced+kind+strings.Title(subresource)). Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...). Returns(http.StatusOK, "OK", versionedObject). Writes(versionedObject) if isGetterWithOptions { if err := addObjectParams(ws, route, versionedGetOptions); err != nil { return nil, err } } addParams(route, action.Params) ws.Route(route) case "LIST": // List all resources of a kind. doc := "list objects of kind " + kind if hasSubresource { doc = "list " + subresource + " of objects of kind " + kind } route := ws.GET(action.Path).To(ListResource(lister, watcher, reqScope, false, a.minRequestTimeout)). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("list"+namespaced+kind+strings.Title(subresource)). Produces("application/json"). Returns(http.StatusOK, "OK", versionedList). Writes(versionedList) if err := addObjectParams(ws, route, versionedListOptions); err != nil { return nil, err } switch { case isLister && isWatcher: doc := "list or watch objects of kind " + kind if hasSubresource { doc = "list or watch " + subresource + " of objects of kind " + kind } route.Doc(doc) case isWatcher: doc := "watch objects of kind " + kind if hasSubresource { doc = "watch " + subresource + "of objects of kind " + kind } route.Doc(doc) } addParams(route, action.Params) ws.Route(route) case "PUT": // Update a resource. doc := "replace the specified " + kind if hasSubresource { doc = "replace " + subresource + " of the specified " + kind } route := ws.PUT(action.Path).To(UpdateResource(updater, reqScope, a.group.Typer, admit)). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("replace"+namespaced+kind+strings.Title(subresource)). Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...). Returns(http.StatusOK, "OK", versionedObject). Reads(versionedObject). Writes(versionedObject) addParams(route, action.Params) ws.Route(route) case "PATCH": // Partially update a resource doc := "partially update the specified " + kind if hasSubresource { doc = "partially update " + subresource + " of the specified " + kind } route := ws.PATCH(action.Path).To(PatchResource(patcher, reqScope, a.group.Typer, admit, mapping.ObjectConvertor)). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Consumes(string(api.JSONPatchType), string(api.MergePatchType), string(api.StrategicMergePatchType)). Operation("patch"+namespaced+kind+strings.Title(subresource)). Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...). Returns(http.StatusOK, "OK", versionedObject). Reads(unversioned.Patch{}). Writes(versionedObject) addParams(route, action.Params) ws.Route(route) case "POST": // Create a resource. var handler restful.RouteFunction if isNamedCreater { handler = CreateNamedResource(namedCreater, reqScope, a.group.Typer, admit) } else { handler = CreateResource(creater, reqScope, a.group.Typer, admit) } doc := "create a " + kind if hasSubresource { doc = "create " + subresource + " of a " + kind } route := ws.POST(action.Path).To(handler). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("create"+namespaced+kind+strings.Title(subresource)). Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...). Returns(http.StatusOK, "OK", versionedObject). Reads(versionedObject). Writes(versionedObject) addParams(route, action.Params) ws.Route(route) case "DELETE": // Delete a resource. doc := "delete a " + kind if hasSubresource { doc = "delete " + subresource + " of a " + kind } route := ws.DELETE(action.Path).To(DeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit)). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("delete"+namespaced+kind+strings.Title(subresource)). Produces(append(storageMeta.ProducesMIMETypes(action.Verb), "application/json")...). Writes(versionedStatus). Returns(http.StatusOK, "OK", versionedStatus) if isGracefulDeleter { route.Reads(versionedDeleterObject) } addParams(route, action.Params) ws.Route(route) // TODO: deprecated case "WATCH": // Watch a resource. doc := "watch changes to an object of kind " + kind if hasSubresource { doc = "watch changes to " + subresource + " of an object of kind " + kind } route := ws.GET(action.Path).To(ListResource(lister, watcher, reqScope, true, a.minRequestTimeout)). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("watch"+namespaced+kind+strings.Title(subresource)). Produces("application/json"). Returns(http.StatusOK, "OK", watchjson.WatchEvent{}). Writes(watchjson.WatchEvent{}) if err := addObjectParams(ws, route, versionedListOptions); err != nil { return nil, err } addParams(route, action.Params) ws.Route(route) // TODO: deprecated case "WATCHLIST": // Watch all resources of a kind. doc := "watch individual changes to a list of " + kind if hasSubresource { doc = "watch individual changes to a list of " + subresource + " of " + kind } route := ws.GET(action.Path).To(ListResource(lister, watcher, reqScope, true, a.minRequestTimeout)). Filter(m). Doc(doc). Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")). Operation("watch"+namespaced+kind+strings.Title(subresource)+"List"). Produces("application/json"). Returns(http.StatusOK, "OK", watchjson.WatchEvent{}). Writes(watchjson.WatchEvent{}) if err := addObjectParams(ws, route, versionedListOptions); err != nil { return nil, err } addParams(route, action.Params) ws.Route(route) case "PROXY": // Proxy requests to a resource. // Accept all methods as per http://issue.k8s.io/3996 addProxyRoute(ws, "GET", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) addProxyRoute(ws, "PUT", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) addProxyRoute(ws, "POST", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) addProxyRoute(ws, "DELETE", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) addProxyRoute(ws, "HEAD", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) addProxyRoute(ws, "OPTIONS", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params) case "CONNECT": for _, method := range connecter.ConnectMethods() { doc := "connect " + method + " requests to " + kind if hasSubresource { doc = "connect " + method + " requests to " + subresource + " of " + kind } route := ws.Method(method).Path(action.Path). To(ConnectResource(connecter, reqScope, admit, connectOptionsKind, path, connectSubpath, connectSubpathKey)). Filter(m). Doc(doc). Operation("connect" + strings.Title(strings.ToLower(method)) + namespaced + kind + strings.Title(subresource)). Produces("*/*"). Consumes("*/*"). Writes("string") if versionedConnectOptions != nil { if err := addObjectParams(ws, route, versionedConnectOptions); err != nil { return nil, err } } addParams(route, action.Params) ws.Route(route) } default: return nil, fmt.Errorf("unrecognized action verb: %s", action.Verb) } // Note: update GetAttribs() when adding a custom handler. } return &apiResource, nil }