mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 01:06:27 +00:00
Merge pull request #6694 from bprashanth/bench
Add a simple master benchmark and a wrapper to run it.
This commit is contained in:
commit
5e056f119d
48
hack/benchmark-integration.sh
Executable file
48
hack/benchmark-integration.sh
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2014 The Kubernetes Authors 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.
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
|
||||||
|
BENCHMARK_REGEX=${BENCHMARK_REGEX:-"."}
|
||||||
|
|
||||||
|
source "${KUBE_ROOT}/hack/lib/init.sh"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
kube::etcd::cleanup
|
||||||
|
kube::log::status "Benchmark cleanup complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests() {
|
||||||
|
kube::etcd::start
|
||||||
|
kube::log::status "Running benchmarks"
|
||||||
|
KUBE_GOFLAGS="-tags 'benchmark no-docker' -bench . -benchtime 1s -cpu 4" \
|
||||||
|
KUBE_RACE="-race" \
|
||||||
|
KUBE_TEST_API_VERSIONS="v1beta3" \
|
||||||
|
KUBE_TIMEOUT="-timeout 10m" \
|
||||||
|
KUBE_TEST_ETCD_PREFIXES="registry"\
|
||||||
|
ETCD_CUSTOM_PREFIX="None" \
|
||||||
|
KUBE_TEST_ARGS="-bench-quiet 0 -bench-pods 30 -bench-tasks 1"\
|
||||||
|
"${KUBE_ROOT}/hack/test-go.sh" test/integration
|
||||||
|
cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run cleanup to stop etcd on interrupt or other kill signal.
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
runTests
|
@ -101,6 +101,7 @@ shift $((OPTIND - 1))
|
|||||||
|
|
||||||
# Use eval to preserve embedded quoted strings.
|
# Use eval to preserve embedded quoted strings.
|
||||||
eval "goflags=(${KUBE_GOFLAGS:-})"
|
eval "goflags=(${KUBE_GOFLAGS:-})"
|
||||||
|
eval "testargs=(${KUBE_TEST_ARGS:-})"
|
||||||
|
|
||||||
# Filter out arguments that start with "-" and move them to goflags.
|
# Filter out arguments that start with "-" and move them to goflags.
|
||||||
testcases=()
|
testcases=()
|
||||||
@ -134,7 +135,8 @@ runTests() {
|
|||||||
count=0
|
count=0
|
||||||
for i in $(seq 1 ${iterations}); do
|
for i in $(seq 1 ${iterations}); do
|
||||||
if go test "${goflags[@]:+${goflags[@]}}" \
|
if go test "${goflags[@]:+${goflags[@]}}" \
|
||||||
${KUBE_RACE} ${KUBE_TIMEOUT} "${pkg}"; then
|
${KUBE_RACE} ${KUBE_TIMEOUT} "${pkg}" \
|
||||||
|
"${testargs[@]:+${testargs[@]}}"; then
|
||||||
pass=$((pass + 1))
|
pass=$((pass + 1))
|
||||||
else
|
else
|
||||||
fails=$((fails + 1))
|
fails=$((fails + 1))
|
||||||
@ -155,7 +157,8 @@ runTests() {
|
|||||||
if [[ ! ${KUBE_COVER} =~ ^[yY]$ ]]; then
|
if [[ ! ${KUBE_COVER} =~ ^[yY]$ ]]; then
|
||||||
kube::log::status "Running unit tests without code coverage"
|
kube::log::status "Running unit tests without code coverage"
|
||||||
go test "${goflags[@]:+${goflags[@]}}" \
|
go test "${goflags[@]:+${goflags[@]}}" \
|
||||||
${KUBE_RACE} ${KUBE_TIMEOUT} "${@+${@/#/${KUBE_GO_PACKAGE}/}}"
|
${KUBE_RACE} ${KUBE_TIMEOUT} "${@+${@/#/${KUBE_GO_PACKAGE}/}}" \
|
||||||
|
"${testargs[@]:+${testargs[@]}}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -177,7 +180,8 @@ runTests() {
|
|||||||
-cover -covermode="${KUBE_COVERMODE}" \
|
-cover -covermode="${KUBE_COVERMODE}" \
|
||||||
-coverprofile="${cover_report_dir}/{}/${cover_profile}" \
|
-coverprofile="${cover_report_dir}/{}/${cover_profile}" \
|
||||||
"${cover_params[@]+${cover_params[@]}}" \
|
"${cover_params[@]+${cover_params[@]}}" \
|
||||||
"${KUBE_GO_PACKAGE}/{}"
|
"${KUBE_GO_PACKAGE}/{}" \
|
||||||
|
"${testargs[@]:+${testargs[@]}}"
|
||||||
|
|
||||||
COMBINED_COVER_PROFILE="${cover_report_dir}/combined-coverage.out"
|
COMBINED_COVER_PROFILE="${cover_report_dir}/combined-coverage.out"
|
||||||
{
|
{
|
||||||
|
@ -169,6 +169,14 @@ func NewReplicationManager(kubeClient client.Interface, burstReplicas int) *Repl
|
|||||||
return rm
|
return rm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetEventRecorder replaces the event recorder used by the replication manager
|
||||||
|
// with the given recorder. Only used for testing.
|
||||||
|
func (rm *ReplicationManager) SetEventRecorder(recorder record.EventRecorder) {
|
||||||
|
// TODO: Hack. We can't cleanly shutdown the event recorder, so benchmarks
|
||||||
|
// need to pass in a fake.
|
||||||
|
rm.podControl = RealPodControl{rm.kubeClient, recorder}
|
||||||
|
}
|
||||||
|
|
||||||
// Run begins watching and syncing.
|
// Run begins watching and syncing.
|
||||||
func (rm *ReplicationManager) Run(workers int, stopCh <-chan struct{}) {
|
func (rm *ReplicationManager) Run(workers int, stopCh <-chan struct{}) {
|
||||||
defer util.HandleCrash()
|
defer util.HandleCrash()
|
||||||
@ -178,6 +186,7 @@ func (rm *ReplicationManager) Run(workers int, stopCh <-chan struct{}) {
|
|||||||
go util.Until(rm.worker, time.Second, stopCh)
|
go util.Until(rm.worker, time.Second, stopCh)
|
||||||
}
|
}
|
||||||
<-stopCh
|
<-stopCh
|
||||||
|
glog.Infof("Shutting down RC Manager")
|
||||||
rm.queue.ShutDown()
|
rm.queue.ShutDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +101,7 @@ type ReplicationControllerResizer struct {
|
|||||||
c ResizerClient
|
c ResizerClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RetryParams encapsulates the retry parameters used by kubectl's resizer.
|
||||||
type RetryParams struct {
|
type RetryParams struct {
|
||||||
Interval, Timeout time.Duration
|
Interval, Timeout time.Duration
|
||||||
}
|
}
|
||||||
|
@ -46,15 +46,14 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokentest"
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokentest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/test/integration/framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nodeResourceName string
|
var nodeResourceName string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
requireEtcd()
|
|
||||||
if api.PreV1Beta3(testapi.Version()) {
|
if api.PreV1Beta3(testapi.Version()) {
|
||||||
nodeResourceName = "minions"
|
nodeResourceName = "minions"
|
||||||
} else {
|
} else {
|
||||||
@ -375,15 +374,13 @@ func getTestRequests() []struct {
|
|||||||
//
|
//
|
||||||
// TODO(etune): write a fuzz test of the REST API.
|
// TODO(etune): write a fuzz test of the REST API.
|
||||||
func TestAuthModeAlwaysAllow(t *testing.T) {
|
func TestAuthModeAlwaysAllow(t *testing.T) {
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
|
helper, err := framework.NewHelper()
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var m *master.Master
|
var m *master.Master
|
||||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
m.Handler.ServeHTTP(w, req)
|
m.Handler.ServeHTTP(w, req)
|
||||||
@ -516,10 +513,10 @@ func getPreviousResourceVersionKey(url, id string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthModeAlwaysDeny(t *testing.T) {
|
func TestAuthModeAlwaysDeny(t *testing.T) {
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -581,12 +578,12 @@ func (allowAliceAuthorizer) Authorize(a authorizer.Attributes) error {
|
|||||||
// the authentication system and authorized to do any actions.
|
// the authentication system and authorized to do any actions.
|
||||||
func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
||||||
|
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -670,12 +667,10 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
|||||||
// the authentication system but not authorized to do any actions
|
// the authentication system but not authorized to do any actions
|
||||||
// should receive "Forbidden".
|
// should receive "Forbidden".
|
||||||
func TestBobIsForbidden(t *testing.T) {
|
func TestBobIsForbidden(t *testing.T) {
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
helper, err := framework.NewHelper()
|
||||||
// Set up a master
|
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -731,12 +726,12 @@ func TestBobIsForbidden(t *testing.T) {
|
|||||||
// An authorization module is installed in this scenario for integration
|
// An authorization module is installed in this scenario for integration
|
||||||
// test purposes, but requests aren't expected to reach it.
|
// test purposes, but requests aren't expected to reach it.
|
||||||
func TestUnknownUserIsUnauthorized(t *testing.T) {
|
func TestUnknownUserIsUnauthorized(t *testing.T) {
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -810,10 +805,10 @@ func newAuthorizerWithContents(t *testing.T, contents string) authorizer.Authori
|
|||||||
// TestNamespaceAuthorization tests that authorization can be controlled
|
// TestNamespaceAuthorization tests that authorization can be controlled
|
||||||
// by namespace.
|
// by namespace.
|
||||||
func TestNamespaceAuthorization(t *testing.T) {
|
func TestNamespaceAuthorization(t *testing.T) {
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -923,12 +918,12 @@ func TestNamespaceAuthorization(t *testing.T) {
|
|||||||
// TestKindAuthorization tests that authorization can be controlled
|
// TestKindAuthorization tests that authorization can be controlled
|
||||||
// by namespace.
|
// by namespace.
|
||||||
func TestKindAuthorization(t *testing.T) {
|
func TestKindAuthorization(t *testing.T) {
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -1026,12 +1021,12 @@ func TestKindAuthorization(t *testing.T) {
|
|||||||
// TestReadOnlyAuthorization tests that authorization can be controlled
|
// TestReadOnlyAuthorization tests that authorization can be controlled
|
||||||
// by namespace.
|
// by namespace.
|
||||||
func TestReadOnlyAuthorization(t *testing.T) {
|
func TestReadOnlyAuthorization(t *testing.T) {
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
// This file has alice and bob in it.
|
// This file has alice and bob in it.
|
||||||
|
|
||||||
// Set up a master
|
// Set up a master
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
25
test/integration/benchmark-controller.json
Normal file
25
test/integration/benchmark-controller.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1beta3",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-controller",
|
||||||
|
"namespace": "test",
|
||||||
|
"labels": {"name": "test-controller"}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"replicas": 0,
|
||||||
|
"selector": {"name": "test-pod"},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"namespace": "test",
|
||||||
|
"labels": {"name": "test-pod"}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [{
|
||||||
|
"name": "test-container",
|
||||||
|
"image": "kubernetes/pause"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,18 +34,15 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/test/integration/framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
requireEtcd()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestClient(t *testing.T) {
|
func TestClient(t *testing.T) {
|
||||||
_, s := runAMaster(t)
|
_, s := framework.RunAMaster(t)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()})
|
client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()})
|
||||||
|
|
||||||
info, err := client.ServerVersion()
|
info, err := client.ServerVersion()
|
||||||
@ -194,9 +191,9 @@ func TestMultiWatch(t *testing.T) {
|
|||||||
const watcherCount = 50
|
const watcherCount = 50
|
||||||
runtime.GOMAXPROCS(watcherCount)
|
runtime.GOMAXPROCS(watcherCount)
|
||||||
|
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
defer deleteAllEtcdKeys()
|
defer framework.DeleteAllEtcdKeys()
|
||||||
_, s := runAMaster(t)
|
_, s := framework.RunAMaster(t)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
ns := api.NamespaceDefault
|
ns := api.NamespaceDefault
|
||||||
|
@ -28,12 +28,9 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/test/integration/framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
requireEtcd()
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringCodec struct{}
|
type stringCodec struct{}
|
||||||
|
|
||||||
type fakeAPIObject string
|
type fakeAPIObject string
|
||||||
@ -56,9 +53,9 @@ func (c stringCodec) DecodeInto(data []byte, obj runtime.Object) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSetObj(t *testing.T) {
|
func TestSetObj(t *testing.T) {
|
||||||
client := newEtcdClient()
|
client := framework.NewEtcdClient()
|
||||||
helper := tools.EtcdHelper{Client: client, Codec: stringCodec{}}
|
helper := tools.EtcdHelper{Client: client, Codec: stringCodec{}}
|
||||||
withEtcdKey(func(key string) {
|
framework.WithEtcdKey(func(key string) {
|
||||||
fakeObject := fakeAPIObject("object")
|
fakeObject := fakeAPIObject("object")
|
||||||
if err := helper.SetObj(key, &fakeObject, nil, 0); err != nil {
|
if err := helper.SetObj(key, &fakeObject, nil, 0); err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
@ -74,9 +71,9 @@ func TestSetObj(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExtractObj(t *testing.T) {
|
func TestExtractObj(t *testing.T) {
|
||||||
client := newEtcdClient()
|
client := framework.NewEtcdClient()
|
||||||
helper := tools.EtcdHelper{Client: client, Codec: stringCodec{}}
|
helper := tools.EtcdHelper{Client: client, Codec: stringCodec{}}
|
||||||
withEtcdKey(func(key string) {
|
framework.WithEtcdKey(func(key string) {
|
||||||
_, err := client.Set(key, "object", 0)
|
_, err := client.Set(key, "object", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
@ -92,9 +89,9 @@ func TestExtractObj(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
client := newEtcdClient()
|
client := framework.NewEtcdClient()
|
||||||
helper := tools.NewEtcdHelper(client, testapi.Codec(), etcdtest.PathPrefix())
|
helper := tools.NewEtcdHelper(client, testapi.Codec(), etcdtest.PathPrefix())
|
||||||
withEtcdKey(func(key string) {
|
framework.WithEtcdKey(func(key string) {
|
||||||
key = etcdtest.AddPrefix(key)
|
key = etcdtest.AddPrefix(key)
|
||||||
resp, err := client.Set(key, runtime.EncodeOrDie(testapi.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0)
|
resp, err := client.Set(key, runtime.EncodeOrDie(testapi.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
77
test/integration/framework/etcd_utils.go
Normal file
77
test/integration/framework/etcd_utils.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors 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 framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
||||||
|
"github.com/coreos/go-etcd/etcd"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If you need to start an etcd instance by hand, you also need to insert a key
|
||||||
|
// for this check to pass (*any* key will do, eg:
|
||||||
|
//curl -L http://127.0.0.1:4001/v2/keys/message -XPUT -d value="Hello world").
|
||||||
|
func init() {
|
||||||
|
RequireEtcd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEtcdClient() *etcd.Client {
|
||||||
|
return etcd.NewClient([]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHelper() (tools.EtcdHelper, error) {
|
||||||
|
return master.NewEtcdHelper(NewEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequireEtcd() {
|
||||||
|
if _, err := NewEtcdClient().Get("/", false, false); err != nil {
|
||||||
|
glog.Fatalf("unable to connect to etcd for testing: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithEtcdKey(f func(string)) {
|
||||||
|
prefix := fmt.Sprintf("/test-%d", rand.Int63())
|
||||||
|
defer NewEtcdClient().Delete(prefix, true)
|
||||||
|
f(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAllEtcdKeys deletes all keys from etcd.
|
||||||
|
// TODO: Instead of sprinkling calls to this throughout the code, adjust the
|
||||||
|
// prefix in etcdtest package; then just delete everything once at the end
|
||||||
|
// of the test run.
|
||||||
|
func DeleteAllEtcdKeys() {
|
||||||
|
glog.Infof("Deleting all etcd keys")
|
||||||
|
client := NewEtcdClient()
|
||||||
|
keys, err := client.Get("/", false, false)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Unable to list root etcd keys: %v", err)
|
||||||
|
}
|
||||||
|
for _, node := range keys.Node.Nodes {
|
||||||
|
if _, err := client.Delete(node.Key, true); err != nil {
|
||||||
|
glog.Fatalf("Unable delete key: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
313
test/integration/framework/master_utils.go
Normal file
313
test/integration/framework/master_utils.go
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors 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 framework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/controller"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Timeout used in benchmarks, to eg: resize an rc
|
||||||
|
DefaultTimeout = 30 * time.Minute
|
||||||
|
|
||||||
|
// Rc manifest used to create pods for benchmarks.
|
||||||
|
// TODO: Convert this to a full path?
|
||||||
|
TestRCManifest = "benchmark-controller.json"
|
||||||
|
|
||||||
|
// Test Namspace, for pods and rcs.
|
||||||
|
TestNS = "test"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MasterComponents is a control struct for all master components started via NewMasterComponents.
|
||||||
|
// TODO: Include all master components (scheduler, nodecontroller).
|
||||||
|
// TODO: Reconcile with integration.go, currently the master used there doesn't understand
|
||||||
|
// how to restart cleanly, which is required for each iteration of a benchmark. The integration
|
||||||
|
// tests also don't make it easy to isolate and turn off components at will.
|
||||||
|
type MasterComponents struct {
|
||||||
|
// Raw http server in front of the master
|
||||||
|
ApiServer *httptest.Server
|
||||||
|
// Kubernetes master, contains an embedded etcd helper
|
||||||
|
KubeMaster *master.Master
|
||||||
|
// Restclient used to talk to the kubernetes master
|
||||||
|
RestClient *client.Client
|
||||||
|
// Replication controller manager
|
||||||
|
ControllerManager *controller.ReplicationManager
|
||||||
|
// Channel for stop signals to rc manager
|
||||||
|
rcStopCh chan struct{}
|
||||||
|
// Used to stop master components individually, and via MasterComponents.Stop
|
||||||
|
once sync.Once
|
||||||
|
// Kubernetes etcd helper, has embedded etcd client
|
||||||
|
EtcdHelper *tools.EtcdHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is a struct of configuration directives for NewMasterComponents.
|
||||||
|
type Config struct {
|
||||||
|
// If nil, a default is used, partially filled configs will not get populated.
|
||||||
|
MasterConfig *master.Config
|
||||||
|
StartReplicationManager bool
|
||||||
|
// If true, all existing etcd keys are purged before starting master components
|
||||||
|
DeleteEtcdKeys bool
|
||||||
|
// Client throttling qps
|
||||||
|
QPS float32
|
||||||
|
// Client burst qps, also burst replicas allowed in rc manager
|
||||||
|
Burst int
|
||||||
|
// TODO: Add configs for endpoints controller, scheduler etc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMasterComponents creates, initializes and starts master components based on the given config.
|
||||||
|
func NewMasterComponents(c *Config) *MasterComponents {
|
||||||
|
m, s, h := startMasterOrDie(c.MasterConfig)
|
||||||
|
// TODO: Allow callers to pipe through a different master url and create a client/start components using it.
|
||||||
|
glog.Infof("Master %+v", s.URL)
|
||||||
|
if c.DeleteEtcdKeys {
|
||||||
|
DeleteAllEtcdKeys()
|
||||||
|
}
|
||||||
|
restClient := client.NewOrDie(&client.Config{Host: s.URL, Version: "v1beta3", QPS: c.QPS, Burst: c.Burst})
|
||||||
|
rcStopCh := make(chan struct{})
|
||||||
|
controllerManager := controller.NewReplicationManager(restClient, c.Burst)
|
||||||
|
|
||||||
|
// TODO: Support events once we can cleanly shutdown an event recorder.
|
||||||
|
controllerManager.SetEventRecorder(&record.FakeRecorder{})
|
||||||
|
if c.StartReplicationManager {
|
||||||
|
go controllerManager.Run(runtime.NumCPU(), rcStopCh)
|
||||||
|
}
|
||||||
|
var once sync.Once
|
||||||
|
return &MasterComponents{
|
||||||
|
ApiServer: s,
|
||||||
|
KubeMaster: m,
|
||||||
|
RestClient: restClient,
|
||||||
|
ControllerManager: controllerManager,
|
||||||
|
rcStopCh: rcStopCh,
|
||||||
|
EtcdHelper: h,
|
||||||
|
once: once,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startMasterOrDie starts a kubernetes master and an httpserver to handle api requests
|
||||||
|
func startMasterOrDie(masterConfig *master.Config) (*master.Master, *httptest.Server, *tools.EtcdHelper) {
|
||||||
|
var m *master.Master
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
m.Handler.ServeHTTP(w, req)
|
||||||
|
}))
|
||||||
|
|
||||||
|
var helper tools.EtcdHelper
|
||||||
|
var err error
|
||||||
|
if masterConfig == nil {
|
||||||
|
helper, err = master.NewEtcdHelper(NewEtcdClient(), "", etcdtest.PathPrefix())
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Failed to create etcd helper for master %v", err)
|
||||||
|
}
|
||||||
|
masterConfig = &master.Config{
|
||||||
|
EtcdHelper: helper,
|
||||||
|
KubeletClient: client.FakeKubeletClient{},
|
||||||
|
EnableLogsSupport: false,
|
||||||
|
EnableProfiling: true,
|
||||||
|
EnableUISupport: false,
|
||||||
|
APIPrefix: "/api",
|
||||||
|
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||||
|
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
helper = masterConfig.EtcdHelper
|
||||||
|
}
|
||||||
|
m = master.New(masterConfig)
|
||||||
|
return m, s, &helper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MasterComponents) stopRCManager() {
|
||||||
|
close(m.rcStopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MasterComponents) Stop(apiServer, rcManager bool) {
|
||||||
|
glog.Infof("Stopping master components")
|
||||||
|
if rcManager {
|
||||||
|
// Ordering matters because the apiServer will only shutdown when pending
|
||||||
|
// requests are done
|
||||||
|
m.once.Do(m.stopRCManager)
|
||||||
|
}
|
||||||
|
if apiServer {
|
||||||
|
m.ApiServer.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RCFromManifest reads a .json file and returns the rc in it.
|
||||||
|
func RCFromManifest(fileName string) *api.ReplicationController {
|
||||||
|
data, err := ioutil.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Unexpected error reading rc manifest %v", err)
|
||||||
|
}
|
||||||
|
var controller api.ReplicationController
|
||||||
|
if err := api.Scheme.DecodeInto(data, &controller); err != nil {
|
||||||
|
glog.Fatalf("Unexpected error reading rc manifest %v", err)
|
||||||
|
}
|
||||||
|
return &controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopRC stops the rc via kubectl's stop library
|
||||||
|
func StopRC(rc *api.ReplicationController, restClient *client.Client) error {
|
||||||
|
reaper, err := kubectl.ReaperFor("ReplicationController", restClient)
|
||||||
|
if err != nil || reaper == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = reaper.Stop(rc.Namespace, rc.Name, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizeRC resizes the given rc to the given replicas.
|
||||||
|
func ResizeRC(name, ns string, replicas int, restClient *client.Client) (*api.ReplicationController, error) {
|
||||||
|
resizer, err := kubectl.ResizerFor("ReplicationController", kubectl.NewResizerClient(restClient))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
retry := &kubectl.RetryParams{50 * time.Millisecond, DefaultTimeout}
|
||||||
|
waitForReplicas := &kubectl.RetryParams{50 * time.Millisecond, DefaultTimeout}
|
||||||
|
err = resizer.Resize(ns, name, uint(replicas), nil, retry, waitForReplicas)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resized, err := restClient.ReplicationControllers(ns).Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resized, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartRC creates given rc if it doesn't already exist, then updates it via kubectl's resizer.
|
||||||
|
func StartRC(controller *api.ReplicationController, restClient *client.Client) (*api.ReplicationController, error) {
|
||||||
|
created, err := restClient.ReplicationControllers(controller.Namespace).Get(controller.Name)
|
||||||
|
if err != nil {
|
||||||
|
glog.Infof("Rc %v doesn't exist, creating", controller.Name)
|
||||||
|
created, err = restClient.ReplicationControllers(controller.Namespace).Create(controller)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we just created an rc, wait till it creates its replicas.
|
||||||
|
return ResizeRC(created.Name, created.Namespace, controller.Spec.Replicas, restClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartPods check for numPods in TestNS. If they exist, it no-ops, otherwise it starts up
|
||||||
|
// a temp rc, resizes it to match numPods, then deletes the rc leaving behind the pods.
|
||||||
|
func StartPods(numPods int, host string, restClient *client.Client) error {
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
glog.Infof("StartPods took %v with numPods %d", time.Since(start), numPods)
|
||||||
|
}()
|
||||||
|
hostField := fields.OneTermEqualSelector(client.PodHost, host)
|
||||||
|
pods, err := restClient.Pods(TestNS).List(labels.Everything(), hostField)
|
||||||
|
if err != nil || len(pods.Items) == numPods {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
glog.Infof("Found %d pods that match host %v, require %d", len(pods.Items), hostField, numPods)
|
||||||
|
// For the sake of simplicity, assume all pods in TestNS have selectors matching TestRCManifest.
|
||||||
|
controller := RCFromManifest(TestRCManifest)
|
||||||
|
|
||||||
|
// Make the rc unique to the given host.
|
||||||
|
controller.Spec.Replicas = numPods
|
||||||
|
controller.Spec.Template.Spec.Host = host
|
||||||
|
controller.Name = controller.Name + host
|
||||||
|
controller.Spec.Selector["host"] = host
|
||||||
|
controller.Spec.Template.Labels["host"] = host
|
||||||
|
|
||||||
|
if rc, err := StartRC(controller, restClient); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
// Delete the rc, otherwise when we restart master components for the next benchmark
|
||||||
|
// the rc controller will race with the pods controller in the rc manager.
|
||||||
|
return restClient.ReplicationControllers(TestNS).Delete(rc.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Merge this into startMasterOrDie.
|
||||||
|
func RunAMaster(t *testing.T) (*master.Master, *httptest.Server) {
|
||||||
|
helper, err := master.NewEtcdHelper(NewEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := master.New(&master.Config{
|
||||||
|
EtcdHelper: helper,
|
||||||
|
KubeletClient: client.FakeKubeletClient{},
|
||||||
|
EnableLogsSupport: false,
|
||||||
|
EnableProfiling: true,
|
||||||
|
EnableUISupport: false,
|
||||||
|
APIPrefix: "/api",
|
||||||
|
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||||
|
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||||
|
})
|
||||||
|
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
m.Handler.ServeHTTP(w, req)
|
||||||
|
}))
|
||||||
|
|
||||||
|
return m, s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task is a function passed to worker goroutines by RunParallel.
|
||||||
|
// The function needs to implement its own thread safety.
|
||||||
|
type Task func(id int) error
|
||||||
|
|
||||||
|
// RunParallel spawns a goroutine per task in the given queue
|
||||||
|
func RunParallel(task Task, numTasks, numWorkers int) {
|
||||||
|
start := time.Now()
|
||||||
|
if numWorkers <= 0 {
|
||||||
|
numWorkers = numTasks
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
glog.Infof("RunParallel took %v for %d tasks and %d workers", time.Since(start), numTasks, numWorkers)
|
||||||
|
}()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
semCh := make(chan struct{}, numWorkers)
|
||||||
|
wg.Add(numTasks)
|
||||||
|
for id := 0; id < numTasks; id++ {
|
||||||
|
go func(id int) {
|
||||||
|
semCh <- struct{}{}
|
||||||
|
err := task(id)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Worker failed with %v", err)
|
||||||
|
}
|
||||||
|
<-semCh
|
||||||
|
wg.Done()
|
||||||
|
}(id)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(semCh)
|
||||||
|
}
|
195
test/integration/master_benchmark_test.go
Normal file
195
test/integration/master_benchmark_test.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// +build benchmark,!no-etcd,!integration
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors 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 (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
etcdgeneric "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic/etcd"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/test/integration/framework"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command line flag globals, parsed in init and used by the benchmarks:
|
||||||
|
// * pods && !tasks: Start -pods, scale number of parallel tasks with b.N
|
||||||
|
// * !pods && tasks: Start -tasks, scale pods with b.N
|
||||||
|
// * pods && tasks: Ignore b.N, benchmark behaves like a test constrained by -benchtime.
|
||||||
|
// * !pods && !tasks: scale pods and workers with b.N.
|
||||||
|
// -workers specifies the number of workers to shard tasks across.
|
||||||
|
// Eg: go test bench . -bench-pods 3000 -bench-tasks 100 -bench-tasks 10:
|
||||||
|
// Create 100 tasks each listing 3000 pods, and run them 10 at a time.
|
||||||
|
var (
|
||||||
|
Workers int
|
||||||
|
Pods int
|
||||||
|
Tasks int
|
||||||
|
)
|
||||||
|
|
||||||
|
const Glog_fatalf = 3
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
q := flag.Int("bench-quiet", 3, "Quietness, don't glog severities <= given level during the benchmark.")
|
||||||
|
pods := flag.Int("bench-pods", -1, "Number of pods for the benchmark. If unspecified, uses b.N.")
|
||||||
|
workers := flag.Int("bench-workers", -1, "Number workers for the benchmark. If unspecified, uses tasks.")
|
||||||
|
tasks := flag.Int("bench-tasks", -1, "Number of tasks for the benchmark. These tasks are sharded across the workers. If unspecified, uses b.N.")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Unfortunately this v level goes in the opposite direction as stderrthreshold.
|
||||||
|
flag.Set("v", fmt.Sprintf("%d", *q))
|
||||||
|
|
||||||
|
// We need quiet logs to parse benchmark results, which includes Errorf.
|
||||||
|
flag.Set("logtostderr", "false")
|
||||||
|
flag.Set("stderrthreshold", fmt.Sprintf("%d", Glog_fatalf-*q))
|
||||||
|
Pods = *pods
|
||||||
|
Workers = *workers
|
||||||
|
Tasks = *tasks
|
||||||
|
framework.DeleteAllEtcdKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPods returns the cmd line -pods or b.N if -pods wasn't specified.
|
||||||
|
// Benchmarks can call getPods to get the number of pods they need to
|
||||||
|
// create for a given benchmark.
|
||||||
|
func getPods(bN int) int {
|
||||||
|
if Pods < 0 {
|
||||||
|
return bN
|
||||||
|
}
|
||||||
|
return Pods
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTasks returns the cmd line -workers or b.N if -workers wasn't specified.
|
||||||
|
// Benchmarks would call getTasks to get the number of workers required to
|
||||||
|
// perform the benchmark in parallel.
|
||||||
|
func getTasks(bN int) int {
|
||||||
|
if Tasks < 0 {
|
||||||
|
return bN
|
||||||
|
}
|
||||||
|
return Tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIterations returns the number of iterations required by each benchmark for
|
||||||
|
// go to produce reliable timing results.
|
||||||
|
func getIterations(bN int) int {
|
||||||
|
// Anything with constant pods is only linear if we iterate b.N times.
|
||||||
|
if Pods > 0 {
|
||||||
|
return bN
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// startPodsOnNodes creates numPods sharded across numNodes
|
||||||
|
func startPodsOnNodes(numPods, numNodes int, restClient *client.Client) {
|
||||||
|
podsPerNode := numPods / numNodes
|
||||||
|
if podsPerNode < 1 {
|
||||||
|
podsPerNode = 1
|
||||||
|
}
|
||||||
|
framework.RunParallel(func(id int) error {
|
||||||
|
return framework.StartPods(podsPerNode, fmt.Sprintf("host.%d", id), restClient)
|
||||||
|
}, numNodes, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark pod listing by waiting on `Tasks` listers to list `Pods` pods via `Workers`.
|
||||||
|
func BenchmarkPodList(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
|
||||||
|
}
|
||||||
|
glog.Infof("Starting benchmark: b.N %d, pods %d, workers %d, podsPerNode %d",
|
||||||
|
b.N, numPods, numTasks, podsPerNode)
|
||||||
|
|
||||||
|
startPodsOnNodes(numPods, numTasks, m.RestClient)
|
||||||
|
// Stop the rc manager so it doesn't steal resources
|
||||||
|
m.Stop(false, true)
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < iter; i++ {
|
||||||
|
framework.RunParallel(func(id int) error {
|
||||||
|
host := fmt.Sprintf("host.%d", id)
|
||||||
|
now := time.Now()
|
||||||
|
defer func() {
|
||||||
|
glog.V(3).Infof("Worker %d: Node %v listing pods took %v", id, host, time.Since(now))
|
||||||
|
}()
|
||||||
|
if pods, err := m.RestClient.Pods(framework.TestNS).List(
|
||||||
|
labels.Everything(),
|
||||||
|
fields.OneTermEqualSelector(client.PodHost, host)); err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(pods.Items) < podsPerNode {
|
||||||
|
glog.Fatalf("List retrieved %d pods, which is less than %d", len(pods.Items), podsPerNode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, numTasks, Workers)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.EtcdHelper.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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/test/integration/framework"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
@ -38,10 +39,6 @@ import (
|
|||||||
|
|
||||||
const scrapeRequestHeader = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text"
|
const scrapeRequestHeader = "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text"
|
||||||
|
|
||||||
func init() {
|
|
||||||
requireEtcd()
|
|
||||||
}
|
|
||||||
|
|
||||||
func scrapeMetrics(s *httptest.Server) ([]*prometheuspb.MetricFamily, error) {
|
func scrapeMetrics(s *httptest.Server) ([]*prometheuspb.MetricFamily, error) {
|
||||||
req, err := http.NewRequest("GET", s.URL+"/metrics", nil)
|
req, err := http.NewRequest("GET", s.URL+"/metrics", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -90,7 +87,7 @@ func checkForExpectedMetrics(t *testing.T, metrics []*prometheuspb.MetricFamily,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMasterProcessMetrics(t *testing.T) {
|
func TestMasterProcessMetrics(t *testing.T) {
|
||||||
_, s := runAMaster(t)
|
_, s := framework.RunAMaster(t)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
metrics, err := scrapeMetrics(s)
|
metrics, err := scrapeMetrics(s)
|
||||||
@ -107,7 +104,7 @@ func TestMasterProcessMetrics(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestApiserverMetrics(t *testing.T) {
|
func TestApiserverMetrics(t *testing.T) {
|
||||||
_, s := runAMaster(t)
|
_, s := framework.RunAMaster(t)
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
|
|
||||||
// Make a request to the apiserver to ensure there's at least one data point
|
// Make a request to the apiserver to ensure there's at least one data point
|
||||||
|
@ -36,19 +36,15 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler"
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler"
|
||||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/algorithmprovider"
|
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/algorithmprovider"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/factory"
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/factory"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/test/integration/framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
requireEtcd()
|
|
||||||
}
|
|
||||||
|
|
||||||
type nodeMutationFunc func(t *testing.T, n *api.Node, nodeStore cache.Store, c *client.Client)
|
type nodeMutationFunc func(t *testing.T, n *api.Node, nodeStore cache.Store, c *client.Client)
|
||||||
|
|
||||||
type nodeStateManager struct {
|
type nodeStateManager struct {
|
||||||
@ -57,11 +53,11 @@ type nodeStateManager struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUnschedulableNodes(t *testing.T) {
|
func TestUnschedulableNodes(t *testing.T) {
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Couldn't create etcd helper: %v", err)
|
t.Fatalf("Couldn't create etcd helper: %v", err)
|
||||||
}
|
}
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
var m *master.Master
|
var m *master.Master
|
||||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -30,14 +30,10 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools/etcdtest"
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/test/integration/framework"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
requireEtcd()
|
|
||||||
}
|
|
||||||
|
|
||||||
func deletePodOrErrorf(t *testing.T, c *client.Client, ns, name string) {
|
func deletePodOrErrorf(t *testing.T, c *client.Client, ns, name string) {
|
||||||
if err := c.Pods(ns).Delete(name, nil); err != nil {
|
if err := c.Pods(ns).Delete(name, nil); err != nil {
|
||||||
t.Errorf("unable to delete pod %v: %v", name, err)
|
t.Errorf("unable to delete pod %v: %v", name, err)
|
||||||
@ -51,7 +47,7 @@ func deleteSecretOrErrorf(t *testing.T, c *client.Client, ns, name string) {
|
|||||||
|
|
||||||
// TestSecrets tests apiserver-side behavior of creation of secret objects and their use by pods.
|
// TestSecrets tests apiserver-side behavior of creation of secret objects and their use by pods.
|
||||||
func TestSecrets(t *testing.T) {
|
func TestSecrets(t *testing.T) {
|
||||||
helper, err := master.NewEtcdHelper(newEtcdClient(), testapi.Version(), etcdtest.PathPrefix())
|
helper, err := framework.NewHelper()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -74,7 +70,7 @@ func TestSecrets(t *testing.T) {
|
|||||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||||
})
|
})
|
||||||
|
|
||||||
deleteAllEtcdKeys()
|
framework.DeleteAllEtcdKeys()
|
||||||
client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()})
|
client := client.NewOrDie(&client.Config{Host: s.URL, Version: testapi.Version()})
|
||||||
DoTestSecrets(t, client, testapi.Version())
|
DoTestSecrets(t, client, testapi.Version())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user