diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 6ac71549788..96e1d73d2ed 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -17,26 +17,7 @@ # This command checks that the built commands can function together for # simple scenarios. It does not require Docker so it can run in travis. -function wait_for_url { - url=$1 - prefix=${2:-} - wait=${3:-0.2} - times=${4:-10} - - set +e - for i in $(seq 1 $times); do - out=$(curl -fs $url 2>/dev/null) - if [ $? -eq 0 ]; then - set -e - echo ${prefix}${out} - return 0 - fi - sleep $wait - done - echo "ERROR: timed out for $url" - set -e - return 1 -} +source $(dirname $0)/util.sh function cleanup() { @@ -47,47 +28,31 @@ function cleanup() kill ${PROXY_PID} 1>&2 2>/dev/null kill ${ETCD_PID} 1>&2 2>/dev/null rm -rf ${ETCD_DIR} 1>&2 2>/dev/null + echo echo "Complete" } trap cleanup EXIT SIGINT -ETCD_HOST=127.0.0.1 -ETCD_PORT=4001 +set -e + +# Start etcd +start_etcd + +ETCD_HOST=${ETCD_HOST:-127.0.0.1} +ETCD_PORT=${ETCD_PORT:-4001} API_PORT=${API_PORT:-8080} API_HOST=${API_HOST:-127.0.0.1} KUBELET_PORT=${KUBELET_PORT:-10250} GO_OUT=$(dirname $0)/../output/go/bin -if [ "$(which etcd)" == "" ]; then - echo "etcd must be in your PATH" - exit 1 -fi - -running_etcd=$(ps -ef | grep etcd | grep -c name) -if [ "$running_etcd" != "0" ]; then - echo "etcd appears to already be running on this machine, please kill and restart the test." - exit 1 -fi - -# Stop on any failures -set -e - -# Start etcd -ETCD_DIR=$(mktemp -d -t test-cmd.XXXXXX) -etcd -name test -data-dir ${ETCD_DIR} -bind-addr $ETCD_HOST:$ETCD_PORT >/dev/null 2>/dev/null & -ETCD_PID=$! - -wait_for_url "http://localhost:4001/version" "etcd: " - - # Check kubecfg out=$(${GO_OUT}/kubecfg -version) echo kubecfg: $out # Start kubelet ${GO_OUT}/kubelet \ - --etcd_servers="http://127.0.0.1:${ETCD_PORT}" \ + --etcd_servers="http://${ETCD_HOST}:${ETCD_PORT}" \ --hostname_override="127.0.0.1" \ --address="127.0.0.1" \ --port="$KUBELET_PORT" 1>&2 & @@ -99,7 +64,7 @@ wait_for_url "http://127.0.0.1:${KUBELET_PORT}/healthz" "kubelet: " ${GO_OUT}/apiserver \ --address="127.0.0.1" \ --port="${API_PORT}" \ - --etcd_servers="http://127.0.0.1:${ETCD_PORT}" \ + --etcd_servers="http://${ETCD_HOST}:${ETCD_PORT}" \ --machines="127.0.0.1" \ --minion_port=${KUBELET_PORT} 1>&2 & APISERVER_PID=$! diff --git a/hack/test-integration.sh b/hack/test-integration.sh index 906a1614149..86926bf008f 100755 --- a/hack/test-integration.sh +++ b/hack/test-integration.sh @@ -14,30 +14,36 @@ # See the License for the specific language governing permissions and # limitations under the License. -if [ "$(which etcd)" == "" ]; then - echo "etcd must be in your PATH" - exit 1 -fi +source $(dirname $0)/util.sh -running_etcd=$(ps -ef | grep etcd | grep -c name) -if [ "$running_etcd" != "0" ]; then - echo "etcd appears to already be running on this machine, please kill and restart the test." - exit 1 -fi +function cleanup() +{ + set +e + kill ${ETCD_PID} 1>&2 2>/dev/null + rm -rf ${ETCD_DIR} 1>&2 2>/dev/null + echo + echo "Complete" +} # Stop right away if the build fails set -e - $(dirname $0)/build-go.sh cmd/integration -ETCD_DIR=$(mktemp -d -t kube-integration.XXXXXX) -trap "rm -rf ${ETCD_DIR}" EXIT +start_etcd -(etcd -name test -data-dir ${ETCD_DIR} > /tmp/etcd.log) & -ETCD_PID=$! +trap cleanup EXIT SIGINT -sleep 5 +echo +echo Integration test cases ... +echo +$(dirname $0)/../hack/test-go.sh test/integration -tags 'integration no-docker' +# leave etcd running if integration tests fail +trap "echo etcd still running" EXIT +echo +echo Integration scenario ... +echo $(dirname $0)/../output/go/bin/integration -kill $ETCD_PID +# nuke etcd +trap cleanup EXIT SIGINT diff --git a/hack/util.sh b/hack/util.sh new file mode 100644 index 00000000000..84abb8f5307 --- /dev/null +++ b/hack/util.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# 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. + +# Provides simple utility functions + +function wait_for_url { + url=$1 + prefix=${2:-} + wait=${3:-0.2} + times=${4:-10} + + set +e + for i in $(seq 1 $times); do + out=$(curl -fs $url 2>/dev/null) + if [ $? -eq 0 ]; then + set -e + echo ${prefix}${out} + return 0 + fi + sleep $wait + done + echo "ERROR: timed out for $url" + set -e + return 1 +} + +function start_etcd { + host=${ETCD_HOST:-127.0.0.1} + port=${ETCD_PORT:-4001} + + set +e + + if [ "$(which etcd)" == "" ]; then + echo "etcd must be in your PATH" + exit 1 + fi + + running_etcd=$(ps -ef | grep etcd | grep -c name) + if [ "$running_etcd" != "0" ]; then + echo "etcd appears to already be running on this machine, please kill and restart the test." + exit 1 + fi + + # Stop on any failures + set -e + + # Start etcd + export ETCD_DIR=$(mktemp -d -t test-etcd.XXXXXX) + etcd -name test -data-dir ${ETCD_DIR} -bind-addr ${host}:${port} >/dev/null 2>/dev/null & + export ETCD_PID=$! + + wait_for_url "http://localhost:4001/v2/keys/" "etcd: " +} \ No newline at end of file diff --git a/test/integration/doc.go b/test/integration/doc.go new file mode 100644 index 00000000000..029b5ae8637 --- /dev/null +++ b/test/integration/doc.go @@ -0,0 +1,20 @@ +/* +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 integration provides integration tests for Kubernetes. Use the integration +// build tag during `go test` to start the tests. Some tests require a running etcd +// or Docker installation on the system which you can skip with no-docker and no-etcd. +package integration diff --git a/test/integration/etcd_tools_test.go b/test/integration/etcd_tools_test.go new file mode 100644 index 00000000000..0a4c1e497e4 --- /dev/null +++ b/test/integration/etcd_tools_test.go @@ -0,0 +1,135 @@ +// +build integration,!no-etcd + +/* +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 integration + +import ( + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/tools" + "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" +) + +func init() { + requireEtcd() +} + +type stringCodec struct{} + +func (c stringCodec) Encode(obj interface{}) ([]byte, error) { + return []byte(obj.(string)), nil +} + +func (c stringCodec) Decode(data []byte) (interface{}, error) { + return string(data), nil +} + +func (c stringCodec) DecodeInto(data []byte, obj interface{}) error { + o := obj.(*string) + *o = string(data) + return nil +} + +func TestSetObj(t *testing.T) { + client := newEtcdClient() + helper := tools.EtcdHelper{Client: client, Codec: stringCodec{}} + withEtcdKey(func(key string) { + if err := helper.SetObj(key, "object"); err != nil { + t.Fatalf("unexpected error: %v", err) + } + resp, err := client.Get(key, false, false) + if err != nil || resp.Node == nil { + t.Fatalf("unexpected error: %v %v", err, resp) + } + if resp.Node.Value != "object" { + t.Errorf("unexpected response: %#v", resp.Node) + } + }) +} + +func TestExtractObj(t *testing.T) { + client := newEtcdClient() + helper := tools.EtcdHelper{Client: client, Codec: stringCodec{}} + withEtcdKey(func(key string) { + _, err := client.Set(key, "object", 0) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + s := "" + if err := helper.ExtractObj(key, &s, false); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if s != "object" { + t.Errorf("unexpected response: %#v", s) + } + }) +} + +func TestWatch(t *testing.T) { + client := newEtcdClient() + helper := tools.EtcdHelper{Client: client, Codec: api.Codec, ResourceVersioner: api.ResourceVersioner} + withEtcdKey(func(key string) { + resp, err := client.Set(key, api.EncodeOrDie(api.Pod{JSONBase: api.JSONBase{ID: "foo"}}), 0) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expectedVersion := resp.Node.ModifiedIndex + + // watch should load the object at the current index + w, err := helper.Watch(key, 0) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + event := <-w.ResultChan() + if event.Type != watch.Added || event.Object == nil { + t.Fatalf("expected first value to be set to ADDED, got %#v", event) + } + + // version should match what we set + pod := event.Object.(*api.Pod) + if pod.ResourceVersion != expectedVersion { + t.Errorf("expected version %d, got %#v", expectedVersion, pod) + } + + // should be no events in the stream + select { + case event, ok := <-w.ResultChan(): + if !ok { + t.Fatalf("channel closed unexpectedly") + } + t.Fatalf("unexpected object in channel: %#v", event) + default: + } + + // should return the previously deleted item in the watch, but with the latest index + resp, err = client.Delete(key, false) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + expectedVersion = resp.Node.ModifiedIndex + event = <-w.ResultChan() + if event.Type != watch.Deleted { + t.Fatalf("expected deleted event", event) + } + pod = event.Object.(*api.Pod) + if pod.ResourceVersion != expectedVersion { + t.Errorf("expected version %d, got %#v", expectedVersion, pod) + } + }) +} diff --git a/test/integration/utils.go b/test/integration/utils.go new file mode 100644 index 00000000000..8bfceeebcd0 --- /dev/null +++ b/test/integration/utils.go @@ -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. +*/ + +// +build integration + +package integration + +import ( + "fmt" + "math/rand" + + "github.com/coreos/go-etcd/etcd" + "github.com/golang/glog" +) + +func newEtcdClient() *etcd.Client { + return etcd.NewClient([]string{}) +} + +func requireEtcd() { + if _, err := newEtcdClient().Get("/", false, false); err != nil { + glog.Fatalf("unable to connect to etcd for integration testing: %v", err) + } +} + +func withEtcdKey(f func(string)) { + prefix := fmt.Sprintf("/test-%d", rand.Int63()) + defer newEtcdClient().Delete(prefix, true) + f(prefix) +}