From c4649d539bc9bc8a315c6d55605cc491c781c4aa Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Wed, 18 Jun 2014 16:01:49 -0700 Subject: [PATCH 1/7] Separate labels and selector in services for clarity --- pkg/api/types.go | 14 ++++++++++---- pkg/cloudcfg/cloudcfg.go | 3 +++ pkg/cloudcfg/parse_test.go | 3 +++ pkg/cloudcfg/resource_printer.go | 17 +++++------------ pkg/labels/labels.go | 5 +++++ pkg/registry/endpoints.go | 3 ++- pkg/registry/endpoints_test.go | 4 ++-- pkg/registry/etcd_registry_test.go | 13 +++++++++---- 8 files changed, 39 insertions(+), 23 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 0c7ea04a75..9fd31f7036 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -138,11 +138,17 @@ type ServiceList struct { } // Defines a service abstraction by a name (for example, mysql) consisting of local port -// (for example 3306) that the proxy listens on, and the labels that define the service. +// (for example 3306) that the proxy listens on, and the selector that determines which pods +// will answer requests sent through the proxy. type Service struct { - JSONBase `json:",inline" yaml:",inline"` - Port int `json:"port,omitempty" yaml:"port,omitempty"` - Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + JSONBase `json:",inline" yaml:",inline"` + Port int `json:"port,omitempty" yaml:"port,omitempty"` + + // This service's labels. + Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` + + // This service will route traffic to pods having labels matching this selector. + Selector map[string]string `json:"selector,omitempty" yaml:"selector,omitempty"` CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"` } diff --git a/pkg/cloudcfg/cloudcfg.go b/pkg/cloudcfg/cloudcfg.go index c1ec9f35cf..feb53f284b 100644 --- a/pkg/cloudcfg/cloudcfg.go +++ b/pkg/cloudcfg/cloudcfg.go @@ -255,6 +255,9 @@ func createService(name string, port int, client client.ClientInterface) (api.Se Labels: map[string]string{ "name": name, }, + Selector: map[string]string{ + "name": name, + }, } svc, err := client.CreateService(svc) return svc, err diff --git a/pkg/cloudcfg/parse_test.go b/pkg/cloudcfg/parse_test.go index 1884466de6..f8ac85b89d 100644 --- a/pkg/cloudcfg/parse_test.go +++ b/pkg/cloudcfg/parse_test.go @@ -63,6 +63,9 @@ func TestParseService(t *testing.T) { Labels: map[string]string{ "area": "staging", }, + Selector: map[string]string{ + "area": "staging", + }, }) } diff --git a/pkg/cloudcfg/resource_printer.go b/pkg/cloudcfg/resource_printer.go index 74ea104bfe..d72bfd384e 100644 --- a/pkg/cloudcfg/resource_printer.go +++ b/pkg/cloudcfg/resource_printer.go @@ -24,6 +24,7 @@ import ( "text/tabwriter" "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "gopkg.in/v1/yaml" ) @@ -62,7 +63,7 @@ type HumanReadablePrinter struct{} var podColumns = []string{"Name", "Image(s)", "Host", "Labels"} var replicationControllerColumns = []string{"Name", "Image(s)", "Label Query", "Replicas"} -var serviceColumns = []string{"Name", "Label Query", "Port"} +var serviceColumns = []string{"Name", "Labels", "Selector", "Port"} func (h *HumanReadablePrinter) unknown(data string, w io.Writer) error { _, err := fmt.Fprintf(w, "Unknown object: %s", data) @@ -89,17 +90,9 @@ func (h *HumanReadablePrinter) makeImageList(manifest api.ContainerManifest) str return strings.Join(images, ",") } -func (h *HumanReadablePrinter) makeLabelsList(labels map[string]string) string { - var vals []string - for key, value := range labels { - vals = append(vals, fmt.Sprintf("%s=%s", key, value)) - } - return strings.Join(vals, ",") -} - func (h *HumanReadablePrinter) printPod(pod api.Pod, w io.Writer) error { _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", - pod.ID, h.makeImageList(pod.DesiredState.Manifest), pod.CurrentState.Host+"/"+pod.CurrentState.HostIP, h.makeLabelsList(pod.Labels)) + pod.ID, h.makeImageList(pod.DesiredState.Manifest), pod.CurrentState.Host+"/"+pod.CurrentState.HostIP, labels.Set(pod.Labels)) return err } @@ -114,7 +107,7 @@ func (h *HumanReadablePrinter) printPodList(podList api.PodList, w io.Writer) er func (h *HumanReadablePrinter) printReplicationController(ctrl api.ReplicationController, w io.Writer) error { _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", - ctrl.ID, h.makeImageList(ctrl.DesiredState.PodTemplate.DesiredState.Manifest), h.makeLabelsList(ctrl.DesiredState.ReplicasInSet), ctrl.DesiredState.Replicas) + ctrl.ID, h.makeImageList(ctrl.DesiredState.PodTemplate.DesiredState.Manifest), labels.Set(ctrl.DesiredState.ReplicasInSet), ctrl.DesiredState.Replicas) return err } @@ -128,7 +121,7 @@ func (h *HumanReadablePrinter) printReplicationControllerList(list api.Replicati } func (h *HumanReadablePrinter) printService(svc api.Service, w io.Writer) error { - _, err := fmt.Fprintf(w, "%s\t%s\t%d\n", svc.ID, h.makeLabelsList(svc.Labels), svc.Port) + _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", svc.ID, labels.Set(svc.Labels), labels.Set(svc.Selector), svc.Port) return err } diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go index 156e18561c..35a800126c 100644 --- a/pkg/labels/labels.go +++ b/pkg/labels/labels.go @@ -45,3 +45,8 @@ func (ls Set) String() string { func (ls Set) Get(label string) string { return ls[label] } + +// Convenience function: convert these labels to a query. +func (ls Set) AsQuery() Query { + return QueryFromSet(ls) +} diff --git a/pkg/registry/endpoints.go b/pkg/registry/endpoints.go index 5601db2f13..37257c9117 100644 --- a/pkg/registry/endpoints.go +++ b/pkg/registry/endpoints.go @@ -13,6 +13,7 @@ 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 registry import ( @@ -42,7 +43,7 @@ func (e *EndpointController) SyncServiceEndpoints() error { } var resultErr error for _, service := range services.Items { - pods, err := e.podRegistry.ListPods(labels.QueryFromSet(labels.Set(service.Labels))) + pods, err := e.podRegistry.ListPods(labels.Set(service.Selector).AsQuery()) if err != nil { log.Printf("Error syncing service: %#v, skipping.", service) resultErr = err diff --git a/pkg/registry/endpoints_test.go b/pkg/registry/endpoints_test.go index af34ff23b7..65bab95968 100644 --- a/pkg/registry/endpoints_test.go +++ b/pkg/registry/endpoints_test.go @@ -49,7 +49,7 @@ func TestSyncEndpointsItems(t *testing.T) { list: api.ServiceList{ Items: []api.Service{ { - Labels: map[string]string{ + Selector: map[string]string{ "foo": "bar", }, }, @@ -92,7 +92,7 @@ func TestSyncEndpointsPodError(t *testing.T) { list: api.ServiceList{ Items: []api.Service{ { - Labels: map[string]string{ + Selector: map[string]string{ "foo": "bar", }, }, diff --git a/pkg/registry/etcd_registry_test.go b/pkg/registry/etcd_registry_test.go index 85676d80f8..407de8d7e3 100644 --- a/pkg/registry/etcd_registry_test.go +++ b/pkg/registry/etcd_registry_test.go @@ -604,16 +604,21 @@ func TestEtcdUpdateService(t *testing.T) { fakeClient := util.MakeFakeEtcdClient(t) fakeClient.Set("/registry/services/specs/foo", util.MakeJSONString(api.Service{JSONBase: api.JSONBase{ID: "foo"}}), 0) registry := MakeTestEtcdRegistry(fakeClient, []string{"machine"}) - err := registry.UpdateService(api.Service{ + testService := api.Service{ JSONBase: api.JSONBase{ID: "foo"}, Labels: map[string]string{ "baz": "bar", }, - }) + Selector: map[string]string{ + "baz": "bar", + }, + } + err := registry.UpdateService(testService) expectNoError(t, err) svc, err := registry.GetService("foo") - if svc.Labels["baz"] != "bar" { - t.Errorf("Unexpected service: %#v", svc) + expectNoError(t, err) + if !reflect.DeepEqual(*svc, testService) { + t.Errorf("Unexpected service: got %#v, wanted %#v", svc, testService) } } From bc02b3c21a376d6fee0d7c8f9cc034b1817ea1a8 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Wed, 18 Jun 2014 16:47:41 -0700 Subject: [PATCH 2/7] Rename [label] query to selector --- cmd/cloudcfg/cloudcfg.go | 6 ++--- pkg/apiserver/apiserver.go | 6 ++--- pkg/apiserver/apiserver_test.go | 2 +- pkg/client/client.go | 32 ++++++++++++------------ pkg/client/client_test.go | 12 ++++----- pkg/cloudcfg/cloudcfg_test.go | 2 +- pkg/cloudcfg/resource_printer.go | 2 +- pkg/kubelet/kubelet_server.go | 2 +- pkg/labels/doc.go | 4 +-- pkg/labels/labels.go | 16 ++++++------ pkg/labels/query.go | 34 +++++++++++++------------- pkg/labels/query_test.go | 38 ++++++++++++++--------------- pkg/registry/controller_registry.go | 4 +-- pkg/registry/endpoints.go | 2 +- pkg/registry/etcd_registry.go | 4 +-- pkg/registry/interfaces.go | 4 +-- pkg/registry/memory_registry.go | 4 +-- pkg/registry/pod_registry.go | 4 +-- pkg/registry/service_registry.go | 4 +-- 19 files changed, 91 insertions(+), 91 deletions(-) diff --git a/cmd/cloudcfg/cloudcfg.go b/cmd/cloudcfg/cloudcfg.go index 99380f84da..2ba1cc3216 100644 --- a/cmd/cloudcfg/cloudcfg.go +++ b/cmd/cloudcfg/cloudcfg.go @@ -39,7 +39,7 @@ var ( versionFlag = flag.Bool("v", false, "Print the version number.") httpServer = flag.String("h", "", "The host to connect to.") config = flag.String("c", "", "Path to the config file.") - labelQuery = flag.String("l", "", "Label query to use for listing") + selector = flag.String("l", "", "Selector (label query) to use for listing") updatePeriod = flag.Duration("u", 60*time.Second, "Update interarrival period") portSpec = flag.String("p", "", "The port spec, comma-separated list of :,...") servicePort = flag.Int("s", -1, "If positive, create and run a corresponding service on this port, only used with 'run'") @@ -143,8 +143,8 @@ func executeAPIRequest(method string, auth *kube_client.AuthInfo) bool { switch method { case "get", "list": url := readUrl(parseStorage()) - if len(*labelQuery) > 0 && method == "list" { - url = url + "?labels=" + *labelQuery + if len(*selector) > 0 && method == "list" { + url = url + "?labels=" + *selector } request, err = http.NewRequest("GET", url, nil) case "delete": diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index ea58302a0b..c7d638e851 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -31,7 +31,7 @@ import ( // RESTStorage is a generic interface for RESTful storage services type RESTStorage interface { - List(labels.Query) (interface{}, error) + List(labels.Selector) (interface{}, error) Get(id string) (interface{}, error) Delete(id string) error Extract(body string) (interface{}, error) @@ -149,12 +149,12 @@ func (server *ApiServer) handleREST(parts []string, requestUrl *url.URL, req *ht case "GET": switch len(parts) { case 1: - query, err := labels.ParseQuery(requestUrl.Query().Get("labels")) + selector, err := labels.ParseSelector(requestUrl.Query().Get("labels")) if err != nil { server.error(err, w) return } - controllers, err := storage.List(query) + controllers, err := storage.List(selector) if err != nil { server.error(err, w) return diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index 41197d5b7e..c10ca1939e 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -51,7 +51,7 @@ type SimpleRESTStorage struct { updated Simple } -func (storage *SimpleRESTStorage) List(labels.Query) (interface{}, error) { +func (storage *SimpleRESTStorage) List(labels.Selector) (interface{}, error) { result := SimpleList{ Items: storage.list, } diff --git a/pkg/client/client.go b/pkg/client/client.go index 90300589d1..3d137b5be3 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -33,7 +33,7 @@ import ( // ClientInterface holds the methods for clients of Kubenetes, an interface to allow mock testing type ClientInterface interface { - ListPods(labelQuery map[string]string) (api.PodList, error) + ListPods(selector map[string]string) (api.PodList, error) GetPod(name string) (api.Pod, error) DeletePod(name string) error CreatePod(api.Pod) (api.Pod, error) @@ -112,40 +112,40 @@ func (client Client) makeURL(path string) string { return client.Host + "/api/v1beta1/" + path } -// EncodeLabelQuery transforms a label query expressed as a key/value map, into a +// EncodeSelector transforms a selector expressed as a key/value map, into a // comma separated, key=value encoding. -func EncodeLabelQuery(labelQuery map[string]string) string { - query := make([]string, 0, len(labelQuery)) - for key, value := range labelQuery { - query = append(query, key+"="+value) +func EncodeSelector(selector map[string]string) string { + parts := make([]string, 0, len(selector)) + for key, value := range selector { + parts = append(parts, key+"="+value) } - return url.QueryEscape(strings.Join(query, ",")) + return url.QueryEscape(strings.Join(parts, ",")) } -// DecodeLabelQuery transforms a label query from a comma separated, key=value format into +// DecodeSelector transforms a selector from a comma separated, key=value format into // a key/value map. -func DecodeLabelQuery(labelQuery string) map[string]string { +func DecodeSelector(selector string) map[string]string { result := map[string]string{} - if len(labelQuery) == 0 { + if len(selector) == 0 { return result } - parts := strings.Split(labelQuery, ",") + parts := strings.Split(selector, ",") for _, part := range parts { pieces := strings.Split(part, "=") if len(pieces) == 2 { result[pieces[0]] = pieces[1] } else { - log.Printf("Invalid label query: %s", labelQuery) + log.Printf("Invalid selector: %s", selector) } } return result } -// ListPods takes a label query, and returns the list of pods that match that query -func (client Client) ListPods(labelQuery map[string]string) (api.PodList, error) { +// ListPods takes a selector, and returns the list of pods that match that selector +func (client Client) ListPods(selector map[string]string) (api.PodList, error) { path := "pods" - if labelQuery != nil && len(labelQuery) > 0 { - path += "?labels=" + EncodeLabelQuery(labelQuery) + if selector != nil && len(selector) > 0 { + path += "?labels=" + EncodeSelector(selector) } var result api.PodList _, err := client.rawRequest("GET", path, nil, &result) diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 9cd7cf404f..4b08f34e23 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -117,13 +117,13 @@ func TestListPodsLabels(t *testing.T) { client := Client{ Host: testServer.URL, } - query := map[string]string{"foo": "bar", "name": "baz"} - receivedPodList, err := client.ListPods(query) + selector := map[string]string{"foo": "bar", "name": "baz"} + receivedPodList, err := client.ListPods(selector) fakeHandler.ValidateRequest(t, makeUrl("/pods"), "GET", nil) - queryString := fakeHandler.RequestReceived.URL.Query().Get("labels") - queryString, _ = url.QueryUnescape(queryString) - parsedQueryString := DecodeLabelQuery(queryString) - expectEqual(t, query, parsedQueryString) + selectorString := fakeHandler.RequestReceived.URL.Query().Get("labels") + selectorString, _ = url.QueryUnescape(selectorString) + parsedSelectorString := DecodeSelector(selectorString) + expectEqual(t, selector, parsedSelectorString) if err != nil { t.Errorf("Unexpected error in listing pods: %#v", err) } diff --git a/pkg/cloudcfg/cloudcfg_test.go b/pkg/cloudcfg/cloudcfg_test.go index de23e61216..a1b3ffc089 100644 --- a/pkg/cloudcfg/cloudcfg_test.go +++ b/pkg/cloudcfg/cloudcfg_test.go @@ -46,7 +46,7 @@ type FakeKubeClient struct { ctrl api.ReplicationController } -func (client *FakeKubeClient) ListPods(labelQuery map[string]string) (api.PodList, error) { +func (client *FakeKubeClient) ListPods(selector map[string]string) (api.PodList, error) { client.actions = append(client.actions, Action{action: "list-pods"}) return client.pods, nil } diff --git a/pkg/cloudcfg/resource_printer.go b/pkg/cloudcfg/resource_printer.go index d72bfd384e..648e42c7f1 100644 --- a/pkg/cloudcfg/resource_printer.go +++ b/pkg/cloudcfg/resource_printer.go @@ -62,7 +62,7 @@ func (y *YAMLPrinter) Print(data string, w io.Writer) error { type HumanReadablePrinter struct{} var podColumns = []string{"Name", "Image(s)", "Host", "Labels"} -var replicationControllerColumns = []string{"Name", "Image(s)", "Label Query", "Replicas"} +var replicationControllerColumns = []string{"Name", "Image(s)", "Selector", "Replicas"} var serviceColumns = []string{"Name", "Labels", "Selector", "Port"} func (h *HumanReadablePrinter) unknown(data string, w io.Writer) error { diff --git a/pkg/kubelet/kubelet_server.go b/pkg/kubelet/kubelet_server.go index 9eaf5b8e44..943e0ac20a 100644 --- a/pkg/kubelet/kubelet_server.go +++ b/pkg/kubelet/kubelet_server.go @@ -68,7 +68,7 @@ func (s *KubeletServer) ServeHTTP(w http.ResponseWriter, req *http.Request) { container := u.Query().Get("container") if len(container) == 0 { w.WriteHeader(http.StatusBadRequest) - fmt.Fprint(w, "Missing container query arg.") + fmt.Fprint(w, "Missing container selector arg.") return } id, found, err := s.Kubelet.GetContainerID(container) diff --git a/pkg/labels/doc.go b/pkg/labels/doc.go index 54318e41bb..afc8c27c64 100644 --- a/pkg/labels/doc.go +++ b/pkg/labels/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package labels implements a simple label system, parsing and matching queries -// with sets of labels. +// Package labels implements a simple label system, parsing and matching +// selectors with sets of labels. package labels diff --git a/pkg/labels/labels.go b/pkg/labels/labels.go index 35a800126c..351289ea63 100644 --- a/pkg/labels/labels.go +++ b/pkg/labels/labels.go @@ -30,15 +30,15 @@ type Labels interface { type Set map[string]string // All labels listed as a human readable string. Conveniently, exactly the format -// that ParseQuery takes. +// that ParseSelector takes. func (ls Set) String() string { - query := make([]string, 0, len(ls)) + selector := make([]string, 0, len(ls)) for key, value := range ls { - query = append(query, key+"="+value) + selector = append(selector, key+"="+value) } // Sort for determinism. - sort.StringSlice(query).Sort() - return strings.Join(query, ",") + sort.StringSlice(selector).Sort() + return strings.Join(selector, ",") } // Implement Labels interface. @@ -46,7 +46,7 @@ func (ls Set) Get(label string) string { return ls[label] } -// Convenience function: convert these labels to a query. -func (ls Set) AsQuery() Query { - return QueryFromSet(ls) +// Convenience function: convert these labels to a selector. +func (ls Set) AsSelector() Selector { + return SelectorFromSet(ls) } diff --git a/pkg/labels/query.go b/pkg/labels/query.go index 5e563924a3..52e712cb3c 100644 --- a/pkg/labels/query.go +++ b/pkg/labels/query.go @@ -21,17 +21,17 @@ import ( "strings" ) -// Represents a label query. -type Query interface { - // Returns true if this query matches the given set of labels. +// Represents a selector. +type Selector interface { + // Returns true if this selector matches the given set of labels. Matches(Labels) bool - // Prints a human readable version of this label query. + // Prints a human readable version of this selector. String() string } -// Everything returns a query that matches all labels. -func Everything() Query { +// Everything returns a selector that matches all labels. +func Everything() Selector { return andTerm{} } @@ -59,7 +59,7 @@ func (t *notHasTerm) String() string { return fmt.Sprintf("%v!=%v", t.label, t.value) } -type andTerm []Query +type andTerm []Selector func (t andTerm) Matches(ls Labels) bool { for _, q := range t { @@ -78,17 +78,17 @@ func (t andTerm) String() string { return strings.Join(terms, ",") } -func try(queryPiece, op string) (lhs, rhs string, ok bool) { - pieces := strings.Split(queryPiece, op) +func try(selectorPiece, op string) (lhs, rhs string, ok bool) { + pieces := strings.Split(selectorPiece, op) if len(pieces) == 2 { return pieces[0], pieces[1], true } return "", "", false } -// Given a Set, return a Query which will match exactly that Set. -func QueryFromSet(ls Set) Query { - var items []Query +// Given a Set, return a Selector which will match exactly that Set. +func SelectorFromSet(ls Set) Selector { + var items []Selector for label, value := range ls { items = append(items, &hasTerm{label: label, value: value}) } @@ -98,10 +98,10 @@ func QueryFromSet(ls Set) Query { return andTerm(items) } -// Takes a string repsenting a label query and returns an object suitable for matching, or an error. -func ParseQuery(query string) (Query, error) { - parts := strings.Split(query, ",") - var items []Query +// Takes a string repsenting a selector and returns an object suitable for matching, or an error. +func ParseSelector(selector string) (Selector, error) { + parts := strings.Split(selector, ",") + var items []Selector for _, part := range parts { if part == "" { continue @@ -113,7 +113,7 @@ func ParseQuery(query string) (Query, error) { } else if lhs, rhs, ok := try(part, "="); ok { items = append(items, &hasTerm{label: lhs, value: rhs}) } else { - return nil, fmt.Errorf("invalid label query: '%s'; can't understand '%s'", query, part) + return nil, fmt.Errorf("invalid selector: '%s'; can't understand '%s'", selector, part) } } if len(items) == 1 { diff --git a/pkg/labels/query_test.go b/pkg/labels/query_test.go index 38dae8b489..2947e042bf 100644 --- a/pkg/labels/query_test.go +++ b/pkg/labels/query_test.go @@ -20,7 +20,7 @@ import ( "testing" ) -func TestQueryParse(t *testing.T) { +func TestSelectorParse(t *testing.T) { testGoodStrings := []string{ "x=a,y=b,z=c", "", @@ -31,7 +31,7 @@ func TestQueryParse(t *testing.T) { "x==a==b", } for _, test := range testGoodStrings { - lq, err := ParseQuery(test) + lq, err := ParseSelector(test) if err != nil { t.Errorf("%v: error %v (%#v)\n", test, err, err) } @@ -40,42 +40,42 @@ func TestQueryParse(t *testing.T) { } } for _, test := range testBadStrings { - _, err := ParseQuery(test) + _, err := ParseSelector(test) if err == nil { t.Errorf("%v: did not get expected error\n", test) } } } -func expectMatch(t *testing.T, query string, ls Set) { - lq, err := ParseQuery(query) +func expectMatch(t *testing.T, selector string, ls Set) { + lq, err := ParseSelector(selector) if err != nil { - t.Errorf("Unable to parse %v as a query\n", query) + t.Errorf("Unable to parse %v as a selector\n", selector) return } if !lq.Matches(ls) { - t.Errorf("Wanted %s to match '%s', but it did not.\n", query, ls) + t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) } } -func expectNoMatch(t *testing.T, query string, ls Set) { - lq, err := ParseQuery(query) +func expectNoMatch(t *testing.T, selector string, ls Set) { + lq, err := ParseSelector(selector) if err != nil { - t.Errorf("Unable to parse %v as a query\n", query) + t.Errorf("Unable to parse %v as a selector\n", selector) return } if lq.Matches(ls) { - t.Errorf("Wanted '%s' to not match '%s', but it did.", query, ls) + t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) } } func TestEverything(t *testing.T) { if !Everything().Matches(Set{"x": "y"}) { - t.Errorf("Nil query didn't match") + t.Errorf("Nil selector didn't match") } } -func TestLabelQueryMatches(t *testing.T) { +func TestSelectorMatches(t *testing.T) { expectMatch(t, "", Set{"x": "y"}) expectMatch(t, "x=y", Set{"x": "y"}) expectMatch(t, "x=y,z=w", Set{"x": "y", "z": "w"}) @@ -96,15 +96,15 @@ func TestLabelQueryMatches(t *testing.T) { expectNoMatch(t, "foo=bar,foobar=bar,baz=blah", labelset) } -func expectMatchDirect(t *testing.T, query, ls Set) { - if !QueryFromSet(query).Matches(ls) { - t.Errorf("Wanted %s to match '%s', but it did not.\n", query, ls) +func expectMatchDirect(t *testing.T, selector, ls Set) { + if !SelectorFromSet(selector).Matches(ls) { + t.Errorf("Wanted %s to match '%s', but it did not.\n", selector, ls) } } -func expectNoMatchDirect(t *testing.T, query, ls Set) { - if QueryFromSet(query).Matches(ls) { - t.Errorf("Wanted '%s' to not match '%s', but it did.", query, ls) +func expectNoMatchDirect(t *testing.T, selector, ls Set) { + if SelectorFromSet(selector).Matches(ls) { + t.Errorf("Wanted '%s' to not match '%s', but it did.", selector, ls) } } diff --git a/pkg/registry/controller_registry.go b/pkg/registry/controller_registry.go index 1824ba965a..1b6aabab84 100644 --- a/pkg/registry/controller_registry.go +++ b/pkg/registry/controller_registry.go @@ -35,12 +35,12 @@ func MakeControllerRegistryStorage(registry ControllerRegistry) apiserver.RESTSt } } -func (storage *ControllerRegistryStorage) List(query labels.Query) (interface{}, error) { +func (storage *ControllerRegistryStorage) List(selector labels.Selector) (interface{}, error) { result := api.ReplicationControllerList{JSONBase: api.JSONBase{Kind: "cluster#replicationControllerList"}} controllers, err := storage.registry.ListControllers() if err == nil { for _, controller := range controllers { - if query.Matches(labels.Set(controller.Labels)) { + if selector.Matches(labels.Set(controller.Labels)) { result.Items = append(result.Items, controller) } } diff --git a/pkg/registry/endpoints.go b/pkg/registry/endpoints.go index 37257c9117..f29417a1e6 100644 --- a/pkg/registry/endpoints.go +++ b/pkg/registry/endpoints.go @@ -43,7 +43,7 @@ func (e *EndpointController) SyncServiceEndpoints() error { } var resultErr error for _, service := range services.Items { - pods, err := e.podRegistry.ListPods(labels.Set(service.Selector).AsQuery()) + pods, err := e.podRegistry.ListPods(labels.Set(service.Selector).AsSelector()) 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 389e4b98ec..a4df170c15 100644 --- a/pkg/registry/etcd_registry.go +++ b/pkg/registry/etcd_registry.go @@ -59,7 +59,7 @@ func (registry *EtcdRegistry) helper() *util.EtcdHelper { return &util.EtcdHelper{registry.etcdClient} } -func (registry *EtcdRegistry) ListPods(query labels.Query) ([]api.Pod, error) { +func (registry *EtcdRegistry) ListPods(selector labels.Selector) ([]api.Pod, error) { pods := []api.Pod{} for _, machine := range registry.machines { var machinePods []api.Pod @@ -68,7 +68,7 @@ func (registry *EtcdRegistry) ListPods(query labels.Query) ([]api.Pod, error) { return pods, err } for _, pod := range machinePods { - if query.Matches(labels.Set(pod.Labels)) { + if selector.Matches(labels.Set(pod.Labels)) { pod.CurrentState.Host = machine pods = append(pods, pod) } diff --git a/pkg/registry/interfaces.go b/pkg/registry/interfaces.go index bf0e2728e9..de077447ff 100644 --- a/pkg/registry/interfaces.go +++ b/pkg/registry/interfaces.go @@ -22,8 +22,8 @@ import ( // 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. - ListPods(query labels.Query) ([]api.Pod, error) + // ListPods obtains a list of pods that match selector. + ListPods(selector labels.Selector) ([]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. diff --git a/pkg/registry/memory_registry.go b/pkg/registry/memory_registry.go index a217552591..994649df93 100644 --- a/pkg/registry/memory_registry.go +++ b/pkg/registry/memory_registry.go @@ -36,10 +36,10 @@ func MakeMemoryRegistry() *MemoryRegistry { } } -func (registry *MemoryRegistry) ListPods(query labels.Query) ([]api.Pod, error) { +func (registry *MemoryRegistry) ListPods(selector labels.Selector) ([]api.Pod, error) { result := []api.Pod{} for _, value := range registry.podData { - if query.Matches(labels.Set(value.Labels)) { + if selector.Matches(labels.Set(value.Labels)) { result = append(result, value) } } diff --git a/pkg/registry/pod_registry.go b/pkg/registry/pod_registry.go index 272238c689..75df1825a1 100644 --- a/pkg/registry/pod_registry.go +++ b/pkg/registry/pod_registry.go @@ -45,9 +45,9 @@ func MakePodRegistryStorage(registry PodRegistry, containerInfo client.Container } } -func (storage *PodRegistryStorage) List(query labels.Query) (interface{}, error) { +func (storage *PodRegistryStorage) List(selector labels.Selector) (interface{}, error) { var result api.PodList - pods, err := storage.registry.ListPods(query) + pods, err := storage.registry.ListPods(selector) if err == nil { result.Items = pods } diff --git a/pkg/registry/service_registry.go b/pkg/registry/service_registry.go index 128e3b571b..c30029bd71 100644 --- a/pkg/registry/service_registry.go +++ b/pkg/registry/service_registry.go @@ -59,7 +59,7 @@ func GetServiceEnvironmentVariables(registry ServiceRegistry, machine string) ([ return result, nil } -func (sr *ServiceRegistryStorage) List(query labels.Query) (interface{}, error) { +func (sr *ServiceRegistryStorage) List(selector labels.Selector) (interface{}, error) { list, err := sr.registry.ListServices() if err != nil { return nil, err @@ -67,7 +67,7 @@ func (sr *ServiceRegistryStorage) List(query labels.Query) (interface{}, error) list.Kind = "cluster#serviceList" var filtered []api.Service for _, service := range list.Items { - if query.Matches(labels.Set(service.Labels)) { + if selector.Matches(labels.Set(service.Labels)) { filtered = append(filtered, service) } } From f9e9a7c6861e9aa119ee0b343f23f187d7b64518 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Wed, 18 Jun 2014 17:03:38 -0700 Subject: [PATCH 3/7] Update guestbook example --- examples/guestbook/guestbook.md | 15 ++++++++++----- examples/guestbook/redis-master-service.json | 2 +- examples/guestbook/redis-slave-service.json | 3 +++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/examples/guestbook/guestbook.md b/examples/guestbook/guestbook.md index 8ab6e84640..77b8c01c85 100644 --- a/examples/guestbook/guestbook.md +++ b/examples/guestbook/guestbook.md @@ -73,18 +73,20 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ### Step Two: Turn up the master service. A Kubernetes 'service' is a named load balancer that proxies traffic to one or more containers. The services in a Kubernetes cluster are discoverable inside other containers via environment variables. Services find the containers to load balance based on pod labels. -The pod that you created in Step One has the label `name=redis-master`, so the corresponding service is defined by that label. Create a file named `redis-master-service.json` that contains: +The pod that you created in Step One has the label `name=redis-master`. The selector field of the service determines which pods will receive the traffic sent to the service. Create a file named `redis-master-service.json` that contains: ```js { "id": "redismaster", "port": 10000, - "labels": { + "selector": { "name": "redis-master" } } ``` +This will cause all pods to see the redis master apparently running on localhost:10000. + Once you have that service description, you can create the service with the `cloudcfg` cli: ```shell @@ -126,7 +128,7 @@ Then you can create the service by running: ```shell $ cluster/cloudcfg.sh -c examples/guestbook/redis-slave-controller.json create /replicationControllers -Name Image(s) Label Query Replicas +Name Image(s) Selector Replicas ---------- ---------- ---------- ---------- redisSlaveController brendanburns/redis-slave name=redisslave 2 ``` @@ -160,11 +162,14 @@ Just like the master, we want to have a service to proxy connections to the read "port": 10001, "labels": { "name": "redis-slave" + }, + "selector": { + "name": "redis-slave" } } ``` -This time the label query for the service is `name=redis-slave`. +This time the selector for the service is `name=redis-slave`, because that identifies the pods running redis slaves. It may also be helpful to set labels on your service itself--as we've done here--to make it easy to locate them with the `cloudcfg -l "label=value" list sevices` command. Now that you have created the service specification, create it in your cluster with the `cloudcfg` CLI: @@ -206,7 +211,7 @@ With this file, you can turn up your frontend with: ```shell $ cluster/cloudcfg.sh -c examples/guestbook/frontend-controller.json create /replicationControllers -Name Image(s) Label Query Replicas +Name Image(s) Selector Replicas ---------- ---------- ---------- ---------- frontendController brendanburns/php-redis name=frontend 3 ``` diff --git a/examples/guestbook/redis-master-service.json b/examples/guestbook/redis-master-service.json index 654c7e9307..5d79ea37fd 100644 --- a/examples/guestbook/redis-master-service.json +++ b/examples/guestbook/redis-master-service.json @@ -1,7 +1,7 @@ { "id": "redismaster", "port": 10000, - "labels": { + "selector": { "name": "redis-master" } } diff --git a/examples/guestbook/redis-slave-service.json b/examples/guestbook/redis-slave-service.json index d9e30f8363..ee87e91252 100644 --- a/examples/guestbook/redis-slave-service.json +++ b/examples/guestbook/redis-slave-service.json @@ -4,4 +4,7 @@ "labels": { "name": "redisslave" } + "selector": { + "name": "redisslave" + } } From ee02cb2bf8a3a1bfacc6335c4ba6763a11d1c775 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Wed, 18 Jun 2014 17:15:53 -0700 Subject: [PATCH 4/7] Update api sources. Still need to regen documentation. --- api/doc/service-schema.json | 4 ++++ api/examples/external-service.json | 3 +++ api/examples/service.json | 3 +++ 3 files changed, 10 insertions(+) diff --git a/api/doc/service-schema.json b/api/doc/service-schema.json index 0f85b40ac9..f5a8aba95f 100644 --- a/api/doc/service-schema.json +++ b/api/doc/service-schema.json @@ -32,5 +32,9 @@ "type": "object", "required": false } + "selector": { + "type": "object", + "required": false + } } } diff --git a/api/examples/external-service.json b/api/examples/external-service.json index e3f1d437ce..e841b2e946 100644 --- a/api/examples/external-service.json +++ b/api/examples/external-service.json @@ -4,5 +4,8 @@ "labels": { "name": "nginx" }, + "selector": { + "name": "nginx" + }, "createExternalLoadBalancer": true } diff --git a/api/examples/service.json b/api/examples/service.json index 68012f4459..f194aea41e 100644 --- a/api/examples/service.json +++ b/api/examples/service.json @@ -4,4 +4,7 @@ "labels": { "name": "nginx" } + "selector": { + "name": "nginx" + } } From cfce4082665688d3a1b39e4518c529f1b7f78772 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Wed, 18 Jun 2014 17:20:34 -0700 Subject: [PATCH 5/7] Rename ReplicasInSet to ReplicaSelector --- api/doc/controller-schema.json | 2 +- api/examples/controller-list.json | 2 +- api/examples/controller.json | 2 +- api/kubernetes.html | 12 ++++++------ examples/guestbook/frontend-controller.json | 2 +- examples/guestbook/guestbook.md | 4 ++-- examples/guestbook/redis-slave-controller.json | 2 +- pkg/api/types.go | 6 +++--- pkg/cloudcfg/cloudcfg.go | 4 ++-- pkg/cloudcfg/resource_printer.go | 2 +- pkg/controller/replication_controller.go | 2 +- pkg/registry/controller_registry_test.go | 2 +- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/api/doc/controller-schema.json b/api/doc/controller-schema.json index 7baa0eb944..bec1d9ac1c 100644 --- a/api/doc/controller-schema.json +++ b/api/doc/controller-schema.json @@ -30,7 +30,7 @@ "required": false, "description": "Number of pods desired in the set" }, - "replicasInSet": { + "replicaSelector": { "type": "object", "required": false, "description": "Required labels used to identify pods in the set" diff --git a/api/examples/controller-list.json b/api/examples/controller-list.json index b2a17950b4..3631770957 100644 --- a/api/examples/controller-list.json +++ b/api/examples/controller-list.json @@ -4,7 +4,7 @@ "id": "testRun", "desiredState": { "replicas": 2, - "replicasInSet": { + "replicaSelector": { "name": "testRun" }, "podTemplate": { diff --git a/api/examples/controller.json b/api/examples/controller.json index c08be4aaae..8b6da66ca8 100644 --- a/api/examples/controller.json +++ b/api/examples/controller.json @@ -2,7 +2,7 @@ "id": "nginxController", "desiredState": { "replicas": 2, - "replicasInSet": {"name": "nginx"}, + "replicaSelector": {"name": "nginx"}, "podTemplate": { "desiredState": { "manifest": { diff --git a/api/kubernetes.html b/api/kubernetes.html index 8678ab0ad0..b6cfcae98f 100644 --- a/api/kubernetes.html +++ b/api/kubernetes.html @@ -970,7 +970,7 @@ implemented, either.

"id": "testRun", "desiredState": { "replicas": 2, - "replicasInSet": { + "replicaSelector": { "name": "testRun" }, "podTemplate": { @@ -1074,7 +1074,7 @@ implemented, either.

"required": false, "description": "Number of pods desired in the set" }
, - "replicasInSet": { + "replicaSelector": { "type": "object", "required": false, "description": "Required labels used to identify pods in the set" @@ -1100,7 +1100,7 @@ implemented, either.

"id": "nginxController", "desiredState": { "replicas": 2, - "replicasInSet": {"name": "nginx"}, + "replicaSelector": {"name": "nginx"}, "podTemplate": { "desiredState": { "manifest": { @@ -1231,7 +1231,7 @@ implemented, either.

"id": "nginxController", "desiredState": { "replicas": 2, - "replicasInSet": {"name": "nginx"}, + "replicaSelector": {"name": "nginx"}, "podTemplate": { "desiredState": { "manifest": { @@ -1333,7 +1333,7 @@ implemented, either.

"required": false, "description": "Number of pods desired in the set" }
, - "replicasInSet": { + "replicaSelector": { "type": "object", "required": false, "description": "Required labels used to identify pods in the set" @@ -1359,7 +1359,7 @@ implemented, either.

"id": "nginxController", "desiredState": { "replicas": 2, - "replicasInSet": {"name": "nginx"}, + "replicaSelector": {"name": "nginx"}, "podTemplate": { "desiredState": { "manifest": { diff --git a/examples/guestbook/frontend-controller.json b/examples/guestbook/frontend-controller.json index f3c94ea496..e7caab32ac 100644 --- a/examples/guestbook/frontend-controller.json +++ b/examples/guestbook/frontend-controller.json @@ -2,7 +2,7 @@ "id": "frontendController", "desiredState": { "replicas": 1, - "replicasInSet": {"name": "frontend"}, + "replicaSelector": {"name": "frontend"}, "podTemplate": { "desiredState": { "manifest": { diff --git a/examples/guestbook/guestbook.md b/examples/guestbook/guestbook.md index 77b8c01c85..53d5051754 100644 --- a/examples/guestbook/guestbook.md +++ b/examples/guestbook/guestbook.md @@ -108,7 +108,7 @@ Create a file named `redis-slave-controller.json` that contains: "id": "redisSlaveController", "desiredState": { "replicas": 2, - "replicasInSet": {"name": "redis-slave"}, + "replicaSelector": {"name": "redis-slave"}, "podTemplate": { "desiredState": { "manifest": { @@ -191,7 +191,7 @@ Create a file named `frontend-controller.json`: "id": "frontendController", "desiredState": { "replicas": 3, - "replicasInSet": {"name": "frontend"}, + "replicaSelector": {"name": "frontend"}, "podTemplate": { "desiredState": { "manifest": { diff --git a/examples/guestbook/redis-slave-controller.json b/examples/guestbook/redis-slave-controller.json index ee6a31e0da..d4d38117c2 100644 --- a/examples/guestbook/redis-slave-controller.json +++ b/examples/guestbook/redis-slave-controller.json @@ -2,7 +2,7 @@ "id": "redisSlaveController", "desiredState": { "replicas": 2, - "replicasInSet": {"name": "redisslave"}, + "replicaSelector": {"name": "redisslave"}, "podTemplate": { "desiredState": { "manifest": { diff --git a/pkg/api/types.go b/pkg/api/types.go index 9fd31f7036..29a71cde7c 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -108,9 +108,9 @@ type Pod struct { // ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get) type ReplicationControllerState struct { - Replicas int `json:"replicas" yaml:"replicas"` - ReplicasInSet map[string]string `json:"replicasInSet,omitempty" yaml:"replicasInSet,omitempty"` - PodTemplate PodTemplate `json:"podTemplate,omitempty" yaml:"podTemplate,omitempty"` + Replicas int `json:"replicas" yaml:"replicas"` + ReplicaSelector map[string]string `json:"replicaSelector,omitempty" yaml:"replicaSelector,omitempty"` + PodTemplate PodTemplate `json:"podTemplate,omitempty" yaml:"podTemplate,omitempty"` } type ReplicationControllerList struct { diff --git a/pkg/cloudcfg/cloudcfg.go b/pkg/cloudcfg/cloudcfg.go index feb53f284b..fb097703c7 100644 --- a/pkg/cloudcfg/cloudcfg.go +++ b/pkg/cloudcfg/cloudcfg.go @@ -74,7 +74,7 @@ func Update(name string, client client.ClientInterface, updatePeriod time.Durati if err != nil { return err } - labels := controller.DesiredState.ReplicasInSet + labels := controller.DesiredState.ReplicaSelector podList, err := client.ListPods(labels) if err != nil { @@ -200,7 +200,7 @@ func RunController(image, name string, replicas int, client client.ClientInterfa }, DesiredState: api.ReplicationControllerState{ Replicas: replicas, - ReplicasInSet: map[string]string{ + ReplicaSelector: map[string]string{ "name": name, }, PodTemplate: api.PodTemplate{ diff --git a/pkg/cloudcfg/resource_printer.go b/pkg/cloudcfg/resource_printer.go index 648e42c7f1..9368d57d50 100644 --- a/pkg/cloudcfg/resource_printer.go +++ b/pkg/cloudcfg/resource_printer.go @@ -107,7 +107,7 @@ func (h *HumanReadablePrinter) printPodList(podList api.PodList, w io.Writer) er func (h *HumanReadablePrinter) printReplicationController(ctrl api.ReplicationController, w io.Writer) error { _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", - ctrl.ID, h.makeImageList(ctrl.DesiredState.PodTemplate.DesiredState.Manifest), labels.Set(ctrl.DesiredState.ReplicasInSet), ctrl.DesiredState.Replicas) + ctrl.ID, h.makeImageList(ctrl.DesiredState.PodTemplate.DesiredState.Manifest), labels.Set(ctrl.DesiredState.ReplicaSelector), ctrl.DesiredState.Replicas) return err } diff --git a/pkg/controller/replication_controller.go b/pkg/controller/replication_controller.go index 35f6499b9c..a3876f2d48 100644 --- a/pkg/controller/replication_controller.go +++ b/pkg/controller/replication_controller.go @@ -177,7 +177,7 @@ func (rm *ReplicationManager) filterActivePods(pods []api.Pod) []api.Pod { } func (rm *ReplicationManager) syncReplicationController(controllerSpec api.ReplicationController) error { - podList, err := rm.kubeClient.ListPods(controllerSpec.DesiredState.ReplicasInSet) + podList, err := rm.kubeClient.ListPods(controllerSpec.DesiredState.ReplicaSelector) if err != nil { return err } diff --git a/pkg/registry/controller_registry_test.go b/pkg/registry/controller_registry_test.go index 14eac7e498..8d2257ec0d 100644 --- a/pkg/registry/controller_registry_test.go +++ b/pkg/registry/controller_registry_test.go @@ -139,7 +139,7 @@ func TestControllerParsing(t *testing.T) { }, DesiredState: api.ReplicationControllerState{ Replicas: 2, - ReplicasInSet: map[string]string{ + ReplicaSelector: map[string]string{ "name": "nginx", }, PodTemplate: api.PodTemplate{ From 64b419072f77812b7d37ec31c33eb1a117db88a0 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Thu, 19 Jun 2014 13:27:35 -0700 Subject: [PATCH 6/7] Fix one more service example --- api/examples/service-list.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/examples/service-list.json b/api/examples/service-list.json index 5fe2e8461c..2d9dea5129 100644 --- a/api/examples/service-list.json +++ b/api/examples/service-list.json @@ -6,6 +6,9 @@ "labels": { "name": "nginx" } + "selector": { + "name": "nginx" + } }, { "id": "example2", @@ -14,6 +17,10 @@ "env": "prod", "name": "jetty" } + "selector": { + "env": "prod", + "name": "jetty" + } } ] } From 5a1a79378404cdb719baf48966b629c1c68a6319 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Thu, 19 Jun 2014 13:33:36 -0700 Subject: [PATCH 7/7] fix after rebase --- pkg/registry/mock_registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/registry/mock_registry.go b/pkg/registry/mock_registry.go index b2e1bff59b..7a3db516cb 100644 --- a/pkg/registry/mock_registry.go +++ b/pkg/registry/mock_registry.go @@ -33,13 +33,13 @@ func MakeMockPodRegistry(pods []api.Pod) *MockPodRegistry { } } -func (registry *MockPodRegistry) ListPods(query labels.Query) ([]api.Pod, error) { +func (registry *MockPodRegistry) ListPods(selector labels.Selector) ([]api.Pod, error) { if registry.err != nil { return registry.pods, registry.err } var filtered []api.Pod for _, pod := range registry.pods { - if query.Matches(labels.Set(pod.Labels)) { + if selector.Matches(labels.Set(pod.Labels)) { filtered = append(filtered, pod) } }