diff --git a/pkg/labels/labelquery_test.go b/pkg/labels/labelquery_test.go deleted file mode 100644 index f0f3f83fd4f..00000000000 --- a/pkg/labels/labelquery_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2014 Google Inc. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package labels - -import ( - "testing" -) - -func TestQueryParse(t *testing.T) { - testGoodStrings := []string{ - "x=a,y=b,z=c", - "", - "x!=a,y=b", - } - testBadStrings := []string{ - "x=a||y=b", - "x==a==b", - } - for _, test := range testGoodStrings { - lq, err := ParseQuery(test) - if err != nil { - t.Errorf("%v: error %v (%#v)\n", test, err, err) - } - if test != lq.String() { - t.Errorf("%v restring gave: %v\n", test, lq.String()) - } - } - for _, test := range testBadStrings { - _, err := ParseQuery(test) - if err == nil { - t.Errorf("%v: did not get expected error\n", test) - } - } -} - -func shouldMatch(t *testing.T, query string, ls LabelSet) { - lq, err := ParseQuery(query) - if err != nil { - t.Errorf("Unable to parse %v as a query\n", query) - return - } - if !lq.Matches(ls) { - t.Errorf("Wanted %s to match %s, but it did not.\n", query, ls) - } -} - -func shouldNotMatch(t *testing.T, query string, ls LabelSet) { - lq, err := ParseQuery(query) - if err != nil { - t.Errorf("Unable to parse %v as a query\n", query) - return - } - if lq.Matches(ls) { - t.Errorf("Wanted '%s' to not match %s, but it did.", query, ls) - } -} - -func TestSimpleLabel(t *testing.T) { - shouldMatch(t, "", LabelSet{"x": "y"}) - shouldMatch(t, "x=y", LabelSet{"x": "y"}) - shouldMatch(t, "x=y,z=w", LabelSet{"x": "y", "z": "w"}) - shouldMatch(t, "x!=y,z!=w", LabelSet{"x": "z", "z": "a"}) - shouldNotMatch(t, "x=y", LabelSet{"x": "z"}) - shouldNotMatch(t, "x=y,z=w", LabelSet{"x": "w", "z": "w"}) - shouldNotMatch(t, "x!=y,z!=w", LabelSet{"x": "z", "z": "w"}) -} diff --git a/pkg/labels/query_test.go b/pkg/labels/query_test.go new file mode 100644 index 00000000000..d42a4ede620 --- /dev/null +++ b/pkg/labels/query_test.go @@ -0,0 +1,123 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package labels + +import ( + "testing" +) + +func TestQueryParse(t *testing.T) { + testGoodStrings := []string{ + "x=a,y=b,z=c", + "", + "x!=a,y=b", + } + testBadStrings := []string{ + "x=a||y=b", + "x==a==b", + } + for _, test := range testGoodStrings { + lq, err := ParseQuery(test) + if err != nil { + t.Errorf("%v: error %v (%#v)\n", test, err, err) + } + if test != lq.String() { + t.Errorf("%v restring gave: %v\n", test, lq.String()) + } + } + for _, test := range testBadStrings { + _, err := ParseQuery(test) + if err == nil { + t.Errorf("%v: did not get expected error\n", test) + } + } +} + +func expectMatch(t *testing.T, query string, ls LabelSet) { + lq, err := ParseQuery(query) + if err != nil { + t.Errorf("Unable to parse %v as a query\n", query) + return + } + if !lq.Matches(ls) { + t.Errorf("Wanted %s to match '%s', but it did not.\n", query, ls) + } +} + +func expectNoMatch(t *testing.T, query string, ls LabelSet) { + lq, err := ParseQuery(query) + if err != nil { + t.Errorf("Unable to parse %v as a query\n", query) + return + } + if lq.Matches(ls) { + t.Errorf("Wanted '%s' to not match '%s', but it did.", query, ls) + } +} + +func TestEverything(t *testing.T) { + if !Everything().Matches(LabelSet{"x": "y"}) { + t.Errorf("Nil query didn't match") + } +} + +func TestLabelQueryMatches(t *testing.T) { + expectMatch(t, "", LabelSet{"x": "y"}) + expectMatch(t, "x=y", LabelSet{"x": "y"}) + expectMatch(t, "x=y,z=w", LabelSet{"x": "y", "z": "w"}) + expectMatch(t, "x!=y,z!=w", LabelSet{"x": "z", "z": "a"}) + expectNoMatch(t, "x=y", LabelSet{"x": "z"}) + expectNoMatch(t, "x=y,z=w", LabelSet{"x": "w", "z": "w"}) + expectNoMatch(t, "x!=y,z!=w", LabelSet{"x": "z", "z": "w"}) + + labelset := LabelSet{ + "foo": "bar", + "baz": "blah", + } + expectMatch(t, "foo=bar", labelset) + expectMatch(t, "baz=blah", labelset) + expectMatch(t, "foo=bar,baz=blah", labelset) + expectNoMatch(t, "foo=blah", labelset) + expectNoMatch(t, "baz=bar", labelset) + expectNoMatch(t, "foo=bar,foobar=bar,baz=blah", labelset) +} + +func expectMatchDirect(t *testing.T, query, ls LabelSet) { + if !QueryFromSet(query).Matches(ls) { + t.Errorf("Wanted %s to match '%s', but it did not.\n", query, ls) + } +} + +func expectNoMatchDirect(t *testing.T, query, ls LabelSet) { + if QueryFromSet(query).Matches(ls) { + t.Errorf("Wanted '%s' to not match '%s', but it did.", query, ls) + } +} + +func TestLabelSetMatches(t *testing.T) { + labelset := LabelSet{ + "foo": "bar", + "baz": "blah", + } + expectMatchDirect(t, LabelSet{}, labelset) + expectMatchDirect(t, LabelSet{"foo": "bar"}, labelset) + expectMatchDirect(t, LabelSet{"baz": "blah"}, labelset) + expectMatchDirect(t, LabelSet{"foo": "bar", "baz": "blah"}, labelset) + expectNoMatchDirect(t, LabelSet{"foo": "=blah"}, labelset) + expectNoMatchDirect(t, LabelSet{"baz": "=bar"}, labelset) + expectNoMatchDirect(t, LabelSet{"foo": "=bar", "foobar": "bar", "baz": "blah"}, labelset) +} diff --git a/pkg/registry/controller_registry_test.go b/pkg/registry/controller_registry_test.go index b3d9d7ce5ec..14eac7e4986 100644 --- a/pkg/registry/controller_registry_test.go +++ b/pkg/registry/controller_registry_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) type MockControllerRegistry struct { @@ -71,7 +72,7 @@ func TestListEmptyControllerList(t *testing.T) { storage := ControllerRegistryStorage{ registry: &mockRegistry, } - controllers, err := storage.List(nil) + controllers, err := storage.List(labels.Everything()) expectNoError(t, err) if len(controllers.(api.ReplicationControllerList).Items) != 0 { t.Errorf("Unexpected non-zero ctrl list: %#v", controllers) @@ -96,7 +97,7 @@ func TestListControllerList(t *testing.T) { storage := ControllerRegistryStorage{ registry: &mockRegistry, } - controllersObj, err := storage.List(nil) + controllersObj, err := storage.List(labels.Everything()) controllers := controllersObj.(api.ReplicationControllerList) expectNoError(t, err) if len(controllers.Items) != 2 { diff --git a/pkg/registry/endpoints.go b/pkg/registry/endpoints.go index 46ce7f304be..cee7b424607 100644 --- a/pkg/registry/endpoints.go +++ b/pkg/registry/endpoints.go @@ -20,6 +20,7 @@ import ( "log" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) func MakeEndpointController(serviceRegistry ServiceRegistry, podRegistry PodRegistry) *EndpointController { @@ -41,7 +42,7 @@ func (e *EndpointController) SyncServiceEndpoints() error { } var resultErr error for _, service := range services.Items { - pods, err := e.podRegistry.ListPods(&service.Labels) + pods, err := e.podRegistry.ListPods(labels.QueryFromSet(labels.LabelSet(service.Labels))) if err != nil { log.Printf("Error syncing service: %#v, skipping.", service) resultErr = err diff --git a/pkg/registry/etcd_registry.go b/pkg/registry/etcd_registry.go index b1c212c55d9..8bd882ced29 100644 --- a/pkg/registry/etcd_registry.go +++ b/pkg/registry/etcd_registry.go @@ -23,6 +23,7 @@ import ( "github.com/coreos/go-etcd/etcd" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) // TODO: Need to add a reconciler loop that makes sure that things in pods are reflected into @@ -66,7 +67,7 @@ func makePodKey(machine, podID string) string { return "/registry/hosts/" + machine + "/pods/" + podID } -func (registry *EtcdRegistry) ListPods(query *map[string]string) ([]api.Pod, error) { +func (registry *EtcdRegistry) ListPods(query labels.Query) ([]api.Pod, error) { pods := []api.Pod{} for _, machine := range registry.machines { machinePods, err := registry.listPodsForMachine(machine) @@ -74,7 +75,7 @@ func (registry *EtcdRegistry) ListPods(query *map[string]string) ([]api.Pod, err return pods, err } for _, pod := range machinePods { - if LabelsMatch(pod, query) { + if query.Matches(labels.LabelSet(pod.Labels)) { pods = append(pods, pod) } } diff --git a/pkg/registry/etcd_registry_test.go b/pkg/registry/etcd_registry_test.go index d117ea018ef..a9872b888fd 100644 --- a/pkg/registry/etcd_registry_test.go +++ b/pkg/registry/etcd_registry_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/coreos/go-etcd/etcd" ) @@ -307,7 +308,7 @@ func TestEtcdEmptyListPods(t *testing.T) { E: nil, } registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"}) - pods, err := registry.ListPods(nil) + pods, err := registry.ListPods(labels.Everything()) expectNoError(t, err) if len(pods) != 0 { t.Errorf("Unexpected pod list: %#v", pods) @@ -322,7 +323,7 @@ func TestEtcdListPodsNotFound(t *testing.T) { E: &etcd.EtcdError{ErrorCode: 100}, } registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"}) - pods, err := registry.ListPods(nil) + pods, err := registry.ListPods(labels.Everything()) expectNoError(t, err) if len(pods) != 0 { t.Errorf("Unexpected pod list: %#v", pods) @@ -348,7 +349,7 @@ func TestEtcdListPods(t *testing.T) { E: nil, } registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"}) - pods, err := registry.ListPods(nil) + pods, err := registry.ListPods(labels.Everything()) expectNoError(t, err) if len(pods) != 2 || pods[0].ID != "foo" || pods[1].ID != "bar" { t.Errorf("Unexpected pod list: %#v", pods) diff --git a/pkg/registry/interfaces.go b/pkg/registry/interfaces.go index db27abde8da..bf0e2728e97 100644 --- a/pkg/registry/interfaces.go +++ b/pkg/registry/interfaces.go @@ -17,13 +17,13 @@ package registry import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -// PodRegistry is an interface implemented by things that know how to store Pod objects +// PodRegistry is an interface implemented by things that know how to store Pod objects. type PodRegistry interface { // ListPods obtains a list of pods that match query. - // Query may be nil in which case all pods are returned. - ListPods(query *map[string]string) ([]api.Pod, error) + ListPods(query labels.Query) ([]api.Pod, error) // Get a specific pod GetPod(podID string) (*api.Pod, error) // Create a pod based on a specification, schedule it onto a specific machine. @@ -34,7 +34,7 @@ type PodRegistry interface { DeletePod(podID string) error } -// ControllerRegistry is an interface for things that know how to store Controllers +// ControllerRegistry is an interface for things that know how to store Controllers. type ControllerRegistry interface { ListControllers() ([]api.ReplicationController, error) GetController(controllerId string) (*api.ReplicationController, error) @@ -42,3 +42,13 @@ type ControllerRegistry interface { UpdateController(controller api.ReplicationController) error DeleteController(controllerId string) error } + +// ServiceRegistry is an interface for things that know how to store services. +type ServiceRegistry interface { + ListServices() (api.ServiceList, error) + CreateService(svc api.Service) error + GetService(name string) (*api.Service, error) + DeleteService(name string) error + UpdateService(svc api.Service) error + UpdateEndpoints(e api.Endpoints) error +} diff --git a/pkg/registry/memory_registry.go b/pkg/registry/memory_registry.go index 894a3854251..0ff4263344e 100644 --- a/pkg/registry/memory_registry.go +++ b/pkg/registry/memory_registry.go @@ -17,6 +17,7 @@ package registry import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) // An implementation of PodRegistry and ControllerRegistry that is backed by memory @@ -35,10 +36,10 @@ func MakeMemoryRegistry() *MemoryRegistry { } } -func (registry *MemoryRegistry) ListPods(labelQuery *map[string]string) ([]api.Pod, error) { +func (registry *MemoryRegistry) ListPods(query labels.Query) ([]api.Pod, error) { result := []api.Pod{} for _, value := range registry.podData { - if LabelsMatch(value, labelQuery) { + if query.Matches(labels.LabelSet(value.Labels)) { result = append(result, value) } } diff --git a/pkg/registry/memory_registry_test.go b/pkg/registry/memory_registry_test.go index 3de7902c60f..b9adb26d9c8 100644 --- a/pkg/registry/memory_registry_test.go +++ b/pkg/registry/memory_registry_test.go @@ -19,11 +19,12 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) func TestListPodsEmpty(t *testing.T) { registry := MakeMemoryRegistry() - pods, err := registry.ListPods(nil) + pods, err := registry.ListPods(labels.Everything()) expectNoError(t, err) if len(pods) != 0 { t.Errorf("Unexpected pod list: %#v", pods) @@ -33,7 +34,7 @@ func TestListPodsEmpty(t *testing.T) { func TestMemoryListPods(t *testing.T) { registry := MakeMemoryRegistry() registry.CreatePod("machine", api.Pod{JSONBase: api.JSONBase{ID: "foo"}}) - pods, err := registry.ListPods(nil) + pods, err := registry.ListPods(labels.Everything()) expectNoError(t, err) if len(pods) != 1 || pods[0].ID != "foo" { t.Errorf("Unexpected pod list: %#v", pods) diff --git a/pkg/registry/pod_registry.go b/pkg/registry/pod_registry.go index 95b150f99aa..c57a7c7f725 100644 --- a/pkg/registry/pod_registry.go +++ b/pkg/registry/pod_registry.go @@ -18,11 +18,11 @@ package registry import ( "encoding/json" "fmt" - "net/url" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/client" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) // PodRegistryStorage implements the RESTStorage interface in terms of a PodRegistry @@ -40,41 +40,11 @@ func MakePodRegistryStorage(registry PodRegistry, containerInfo client.Container } } -// LabelMatch tests to see if a Pod's labels map contains 'key' mapping to 'value' -func LabelMatch(pod api.Pod, queryKey, queryValue string) bool { - for key, value := range pod.Labels { - if queryKey == key && queryValue == value { - return true - } - } - return false -} - -// LabelMatch tests to see if a Pod's labels map contains all key/value pairs in 'labelQuery' -func LabelsMatch(pod api.Pod, labelQuery *map[string]string) bool { - if labelQuery == nil { - return true - } - for key, value := range *labelQuery { - if !LabelMatch(pod, key, value) { - return false - } - } - return true -} - -func (storage *PodRegistryStorage) List(url *url.URL) (interface{}, error) { +func (storage *PodRegistryStorage) List(query labels.Query) (interface{}, error) { var result api.PodList - var query *map[string]string - if url != nil { - queryMap := client.DecodeLabelQuery(url.Query().Get("labels")) - query = &queryMap - } pods, err := storage.registry.ListPods(query) if err == nil { - result = api.PodList{ - Items: pods, - } + result.Items = pods } result.Kind = "cluster#podList" return result, err diff --git a/pkg/registry/pod_registry_test.go b/pkg/registry/pod_registry_test.go index 217192e4fc9..3e6bda6208f 100644 --- a/pkg/registry/pod_registry_test.go +++ b/pkg/registry/pod_registry_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) type MockPodRegistry struct { @@ -35,7 +36,7 @@ func expectNoError(t *testing.T, err error) { } } -func (registry *MockPodRegistry) ListPods(*map[string]string) ([]api.Pod, error) { +func (registry *MockPodRegistry) ListPods(labels.Query) ([]api.Pod, error) { return registry.pods, registry.err } @@ -135,74 +136,6 @@ func TestExtractJson(t *testing.T) { } } -func expectLabelMatch(t *testing.T, pod api.Pod, key, value string) { - if !LabelMatch(pod, key, value) { - t.Errorf("Unexpected match failure: %#v %s %s", pod, key, value) - } -} - -func expectNoLabelMatch(t *testing.T, pod api.Pod, key, value string) { - if LabelMatch(pod, key, value) { - t.Errorf("Unexpected match success: %#v %s %s", pod, key, value) - } -} - -func expectLabelsMatch(t *testing.T, pod api.Pod, query *map[string]string) { - if !LabelsMatch(pod, query) { - t.Errorf("Unexpected match failure: %#v %#v", pod, *query) - } -} - -func expectNoLabelsMatch(t *testing.T, pod api.Pod, query *map[string]string) { - if LabelsMatch(pod, query) { - t.Errorf("Unexpected match success: %#v %#v", pod, *query) - } -} - -func TestLabelMatch(t *testing.T) { - pod := api.Pod{ - Labels: map[string]string{ - "foo": "bar", - "baz": "blah", - }, - } - expectLabelMatch(t, pod, "foo", "bar") - expectLabelMatch(t, pod, "baz", "blah") - expectNoLabelMatch(t, pod, "foo", "blah") - expectNoLabelMatch(t, pod, "baz", "bar") -} - -func TestLabelsMatch(t *testing.T) { - pod := api.Pod{ - Labels: map[string]string{ - "foo": "bar", - "baz": "blah", - }, - } - expectLabelsMatch(t, pod, &map[string]string{}) - expectLabelsMatch(t, pod, &map[string]string{ - "foo": "bar", - }) - expectLabelsMatch(t, pod, &map[string]string{ - "baz": "blah", - }) - expectLabelsMatch(t, pod, &map[string]string{ - "foo": "bar", - "baz": "blah", - }) - expectNoLabelsMatch(t, pod, &map[string]string{ - "foo": "blah", - }) - expectNoLabelsMatch(t, pod, &map[string]string{ - "baz": "bar", - }) - expectNoLabelsMatch(t, pod, &map[string]string{ - "foo": "bar", - "foobar": "bar", - "baz": "blah", - }) -} - func TestMakePodStatus(t *testing.T) { status := makePodStatus(map[string]interface{}{}) if status != "Pending" { diff --git a/pkg/registry/service_registry.go b/pkg/registry/service_registry.go index 218cc0eb4cc..102ffe174cc 100644 --- a/pkg/registry/service_registry.go +++ b/pkg/registry/service_registry.go @@ -17,23 +17,14 @@ package registry import ( "encoding/json" - "net/url" "strconv" "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" ) -type ServiceRegistry interface { - ListServices() (api.ServiceList, error) - CreateService(svc api.Service) error - GetService(name string) (*api.Service, error) - DeleteService(name string) error - UpdateService(svc api.Service) error - UpdateEndpoints(e api.Endpoints) error -} - type ServiceRegistryStorage struct { registry ServiceRegistry } @@ -59,12 +50,19 @@ func GetServiceEnvironmentVariables(registry ServiceRegistry, machine string) ([ return result, nil } -func (sr *ServiceRegistryStorage) List(*url.URL) (interface{}, error) { +func (sr *ServiceRegistryStorage) List(query labels.Query) (interface{}, error) { list, err := sr.registry.ListServices() if err != nil { return nil, err } list.Kind = "cluster#serviceList" + var filtered []api.Service + for _, service := range list.Items { + if query.Matches(labels.LabelSet(service.Labels)) { + filtered = append(filtered, service) + } + } + list.Items = filtered return list, err }