Skip to content

Commit

Permalink
Merge pull request kubernetes#130 from lavalamp/test_fix
Browse files Browse the repository at this point in the history
Move labels and tests to new package
  • Loading branch information
brendandburns committed Jun 17, 2014
2 parents bad4318 + e10e5b9 commit e7125fb
Show file tree
Hide file tree
Showing 22 changed files with 428 additions and 171 deletions.
2 changes: 1 addition & 1 deletion hack/integration-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ $(dirname $0)/build-go.sh
ETCD_DIR=$(mktemp -d -t kube-integration.XXXXXX)
trap "rm -rf ${ETCD_DIR}" EXIT

etcd -name test -data-dir ${ETCD_DIR} > /tmp/etcd.log &
(etcd -name test -data-dir ${ETCD_DIR} > /tmp/etcd.log) &
ETCD_PID=$!

sleep 5
Expand Down
2 changes: 1 addition & 1 deletion hack/local-up-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ echo "Starting etcd"
ETCD_DIR=$(mktemp -d -t kube-integration.XXXXXX)
trap "rm -rf ${ETCD_DIR}" EXIT

etcd -name test -data-dir ${ETCD_DIR} &> /tmp/etcd.log &
(etcd -name test -data-dir ${ETCD_DIR} &> /tmp/etcd.log) &
ETCD_PID=$!

sleep 5
Expand Down
2 changes: 1 addition & 1 deletion hack/local-up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ echo "Starting etcd"
ETCD_DIR=$(mktemp -d -t kube-integration.XXXXXX)
trap "rm -rf ${ETCD_DIR}" EXIT

etcd -name test -data-dir ${ETCD_DIR} > /tmp/etcd.log &
(etcd -name test -data-dir ${ETCD_DIR} > /tmp/etcd.log) &
ETCD_PID=$!

sleep 5
Expand Down
23 changes: 15 additions & 8 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import (
"net/http"
"net/url"
"strings"

"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)

// RESTStorage is a generic interface for RESTful storage services
type RESTStorage interface {
List(*url.URL) (interface{}, error)
List(labels.Query) (interface{}, error)
Get(id string) (interface{}, error)
Delete(id string) error
Extract(body string) (interface{}, error)
Expand All @@ -38,7 +40,7 @@ type RESTStorage interface {

// Status is a return value for calls that don't return other objects
type Status struct {
success bool
Success bool
}

// ApiServer is an HTTPHandler that delegates to RESTStorage objects.
Expand Down Expand Up @@ -141,28 +143,33 @@ func (server *ApiServer) readBody(req *http.Request) (string, error) {
// PUT /foo/bar update 'bar'
// DELETE /foo/bar delete 'bar'
// Returns 404 if the method/pattern doesn't match one of these entries
func (server *ApiServer) handleREST(parts []string, url *url.URL, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
func (server *ApiServer) handleREST(parts []string, requestUrl *url.URL, req *http.Request, w http.ResponseWriter, storage RESTStorage) {
switch req.Method {
case "GET":
switch len(parts) {
case 1:
controllers, err := storage.List(url)
query, err := labels.ParseQuery(requestUrl.Query().Get("labels"))
if err != nil {
server.error(err, w)
return
}
controllers, err := storage.List(query)
if err != nil {
server.error(err, w)
return
}
server.write(200, controllers, w)
case 2:
pod, err := storage.Get(parts[1])
item, err := storage.Get(parts[1])
if err != nil {
server.error(err, w)
return
}
if pod == nil {
if item == nil {
server.notFound(req, w)
return
}
server.write(200, pod, w)
server.write(200, item, w)
default:
server.notFound(req, w)
}
Expand Down Expand Up @@ -195,7 +202,7 @@ func (server *ApiServer) handleREST(parts []string, url *url.URL, req *http.Requ
server.error(err, w)
return
}
server.write(200, Status{success: true}, w)
server.write(200, Status{Success: true}, w)
return
case "PUT":
if len(parts) != 2 {
Expand Down
5 changes: 3 additions & 2 deletions pkg/apiserver/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"

"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)

// TODO: This doesn't reduce typing enough to make it worth the less readable errors. Remove.
Expand All @@ -50,7 +51,7 @@ type SimpleRESTStorage struct {
updated Simple
}

func (storage *SimpleRESTStorage) List(*url.URL) (interface{}, error) {
func (storage *SimpleRESTStorage) List(labels.Query) (interface{}, error) {
result := SimpleList{
Items: storage.list,
}
Expand Down
24 changes: 0 additions & 24 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,30 +245,6 @@ func expectEqual(t *testing.T, expected, observed interface{}) {
}
}

func TestEncodeDecodeLabelQuery(t *testing.T) {
queryIn := map[string]string{
"foo": "bar",
"baz": "blah",
}
queryString, _ := url.QueryUnescape(EncodeLabelQuery(queryIn))
queryOut := DecodeLabelQuery(queryString)
expectEqual(t, queryIn, queryOut)
}

func TestDecodeEmpty(t *testing.T) {
query := DecodeLabelQuery("")
if len(query) != 0 {
t.Errorf("Unexpected query: %#v", query)
}
}

func TestDecodeBad(t *testing.T) {
query := DecodeLabelQuery("foo")
if len(query) != 0 {
t.Errorf("Unexpected query: %#v", query)
}
}

func TestGetController(t *testing.T) {
expectedController := api.ReplicationController{
JSONBase: api.JSONBase{
Expand Down
19 changes: 19 additions & 0 deletions pkg/labels/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
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 implements a simple label system, parsing and matching queries
// with sets of labels.
package labels
47 changes: 47 additions & 0 deletions pkg/labels/labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
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 (
"sort"
"strings"
)

// Labels allows you to present labels independently from their storage.
type Labels interface {
Get(label string) (value string)
}

// A map of label:value. Implements Labels.
type Set map[string]string

// All labels listed as a human readable string. Conveniently, exactly the format
// that ParseQuery takes.
func (ls Set) String() string {
query := make([]string, 0, len(ls))
for key, value := range ls {
query = append(query, key+"="+value)
}
// Sort for determinism.
sort.StringSlice(query).Sort()
return strings.Join(query, ",")
}

// Implement Labels interface.
func (ls Set) Get(label string) string {
return ls[label]
}
43 changes: 43 additions & 0 deletions pkg/labels/labels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
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 matches(t *testing.T, ls Set, want string) {
if ls.String() != want {
t.Errorf("Expected '%s', but got '%s'", want, ls.String())
}
}

func TestSetString(t *testing.T) {
matches(t, Set{"x": "y"}, "x=y")
matches(t, Set{"foo": "bar"}, "foo=bar")
matches(t, Set{"foo": "bar", "baz": "qup"}, "baz=qup,foo=bar")

// TODO: Make our label representation robust enough to handle labels
// with ",=!" characters in their names.
}

func TestLabelGet(t *testing.T) {
ls := Set{"x": "y"}
if ls.Get("x") != "y" {
t.Errorf("Set.Get is broken")
}
}
123 changes: 123 additions & 0 deletions pkg/labels/query.go
Original file line number Diff line number Diff line change
@@ -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 (
"fmt"
"strings"
)

// Represents a label query.
type Query interface {
// Returns true if this query matches the given set of labels.
Matches(Labels) bool

// Prints a human readable version of this label query.
String() string
}

// Everything returns a query that matches all labels.
func Everything() Query {
return andTerm{}
}

type hasTerm struct {
label, value string
}

func (t *hasTerm) Matches(ls Labels) bool {
return ls.Get(t.label) == t.value
}

func (t *hasTerm) String() string {
return fmt.Sprintf("%v=%v", t.label, t.value)
}

type notHasTerm struct {
label, value string
}

func (t *notHasTerm) Matches(ls Labels) bool {
return ls.Get(t.label) != t.value
}

func (t *notHasTerm) String() string {
return fmt.Sprintf("%v!=%v", t.label, t.value)
}

type andTerm []Query

func (t andTerm) Matches(ls Labels) bool {
for _, q := range t {
if !q.Matches(ls) {
return false
}
}
return true
}

func (t andTerm) String() string {
var terms []string
for _, q := range t {
terms = append(terms, q.String())
}
return strings.Join(terms, ",")
}

func try(queryPiece, op string) (lhs, rhs string, ok bool) {
pieces := strings.Split(queryPiece, 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
for label, value := range ls {
items = append(items, &hasTerm{label: label, value: value})
}
if len(items) == 1 {
return items[0]
}
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
for _, part := range parts {
if part == "" {
continue
}
if lhs, rhs, ok := try(part, "!="); ok {
items = append(items, &notHasTerm{label: lhs, value: rhs})
} else if lhs, rhs, ok := try(part, "=="); ok {
items = append(items, &hasTerm{label: lhs, value: rhs})
} 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)
}
}
if len(items) == 1 {
return items[0], nil
}
return andTerm(items), nil
}
Loading

0 comments on commit e7125fb

Please sign in to comment.