2015-05-05 14:48:50 +00:00
|
|
|
/*
|
2016-06-03 00:25:58 +00:00
|
|
|
Copyright 2015 The Kubernetes Authors.
|
2015-05-05 14:48:50 +00:00
|
|
|
|
|
|
|
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 e2e
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
2016-04-19 21:21:45 +00:00
|
|
|
"os"
|
2015-05-13 14:06:32 +00:00
|
|
|
"strconv"
|
2015-05-05 14:48:50 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2015-12-10 09:39:03 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2015-08-13 19:01:50 +00:00
|
|
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/labels"
|
2016-04-19 21:21:45 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/intstr"
|
2016-04-07 17:21:31 +00:00
|
|
|
"k8s.io/kubernetes/test/e2e/framework"
|
2015-05-05 14:48:50 +00:00
|
|
|
|
|
|
|
. "github.com/onsi/ginkgo"
|
|
|
|
. "github.com/onsi/gomega"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2015-06-02 13:17:25 +00:00
|
|
|
smallRCSize = 5
|
|
|
|
mediumRCSize = 30
|
|
|
|
bigRCSize = 250
|
2016-04-19 21:21:45 +00:00
|
|
|
smallRCGroupName = "load-small-rc"
|
|
|
|
mediumRCGroupName = "load-medium-rc"
|
|
|
|
bigRCGroupName = "load-big-rc"
|
2015-06-09 14:13:02 +00:00
|
|
|
smallRCBatchSize = 30
|
2015-06-02 13:17:25 +00:00
|
|
|
mediumRCBatchSize = 5
|
|
|
|
bigRCBatchSize = 1
|
2016-05-13 11:14:16 +00:00
|
|
|
// We start RCs/Services/pods/... in different namespace in this test.
|
|
|
|
// nodeCountPerNamespace determines how many namespaces we will be using
|
|
|
|
// depending on the number of nodes in the underlying cluster.
|
|
|
|
nodeCountPerNamespace = 250
|
2015-05-05 14:48:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// This test suite can take a long time to run, so by default it is added to
|
|
|
|
// the ginkgo.skip list (see driver.go).
|
|
|
|
// To run this suite you must explicitly ask for it by setting the
|
|
|
|
// -t/--test flag or ginkgo.focus flag.
|
2016-04-07 17:21:31 +00:00
|
|
|
var _ = framework.KubeDescribe("Load capacity", func() {
|
2015-05-05 14:48:50 +00:00
|
|
|
var c *client.Client
|
|
|
|
var nodeCount int
|
|
|
|
var ns string
|
2016-04-07 17:21:31 +00:00
|
|
|
var configs []*framework.RCConfig
|
2016-05-13 11:14:16 +00:00
|
|
|
var namespaces []*api.Namespace
|
2015-10-27 13:07:51 +00:00
|
|
|
|
|
|
|
// Gathers metrics before teardown
|
|
|
|
// TODO add flag that allows to skip cleanup on failure
|
|
|
|
AfterEach(func() {
|
|
|
|
// Verify latency metrics
|
2016-04-07 17:21:31 +00:00
|
|
|
highLatencyRequests, err := framework.HighLatencyRequests(c)
|
|
|
|
framework.ExpectNoError(err, "Too many instances metrics above the threshold")
|
2015-10-27 13:07:51 +00:00
|
|
|
Expect(highLatencyRequests).NotTo(BeNumerically(">", 0))
|
|
|
|
})
|
|
|
|
|
2015-12-03 10:15:44 +00:00
|
|
|
// Explicitly put here, to delete namespace at the end of the test
|
|
|
|
// (after measuring latency metrics, etc.).
|
2016-04-07 17:21:31 +00:00
|
|
|
options := framework.FrameworkOptions{
|
|
|
|
ClientQPS: 50,
|
|
|
|
ClientBurst: 100,
|
2016-02-24 15:24:36 +00:00
|
|
|
}
|
2016-04-18 20:12:19 +00:00
|
|
|
f := framework.NewFramework("load", options, nil)
|
2016-04-07 17:21:31 +00:00
|
|
|
f.NamespaceDeletionTimeout = time.Hour
|
2015-05-05 14:48:50 +00:00
|
|
|
|
|
|
|
BeforeEach(func() {
|
2016-04-07 17:21:31 +00:00
|
|
|
c = f.Client
|
2016-02-05 15:02:00 +00:00
|
|
|
|
2016-06-02 08:12:44 +00:00
|
|
|
// In large clusters we may get to this point but still have a bunch
|
|
|
|
// of nodes without Routes created. Since this would make a node
|
|
|
|
// unschedulable, we need to wait until all of them are schedulable.
|
|
|
|
framework.ExpectNoError(framework.WaitForAllNodesSchedulable(c))
|
|
|
|
|
2016-04-07 17:21:31 +00:00
|
|
|
ns = f.Namespace.Name
|
2016-05-05 20:56:25 +00:00
|
|
|
nodes := framework.GetReadySchedulableNodesOrDie(c)
|
2015-05-05 14:48:50 +00:00
|
|
|
nodeCount = len(nodes.Items)
|
|
|
|
Expect(nodeCount).NotTo(BeZero())
|
2015-06-22 11:47:05 +00:00
|
|
|
|
|
|
|
// Terminating a namespace (deleting the remaining objects from it - which
|
|
|
|
// generally means events) can affect the current run. Thus we wait for all
|
|
|
|
// terminating namespace to be finally deleted before starting this test.
|
2016-04-07 17:21:31 +00:00
|
|
|
err := framework.CheckTestingNSDeletedExcept(c, ns)
|
|
|
|
framework.ExpectNoError(err)
|
2015-06-23 09:36:03 +00:00
|
|
|
|
2016-04-07 17:21:31 +00:00
|
|
|
framework.ExpectNoError(framework.ResetMetrics(c))
|
2015-05-05 14:48:50 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
type Load struct {
|
|
|
|
podsPerNode int
|
2015-07-07 12:29:16 +00:00
|
|
|
image string
|
|
|
|
command []string
|
2015-05-05 14:48:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loadTests := []Load{
|
2015-07-07 12:29:16 +00:00
|
|
|
// The container will consume 1 cpu and 512mb of memory.
|
|
|
|
{podsPerNode: 3, image: "jess/stress", command: []string{"stress", "-c", "1", "-m", "2"}},
|
2016-03-21 19:53:22 +00:00
|
|
|
{podsPerNode: 30, image: "gcr.io/google_containers/serve_hostname:v1.4"},
|
2015-05-05 14:48:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, testArg := range loadTests {
|
2015-11-12 22:30:06 +00:00
|
|
|
name := fmt.Sprintf("should be able to handle %v pods per node", testArg.podsPerNode)
|
2015-12-04 09:58:50 +00:00
|
|
|
if testArg.podsPerNode == 30 {
|
2016-01-21 19:06:45 +00:00
|
|
|
name = "[Feature:Performance] " + name
|
|
|
|
} else {
|
|
|
|
name = "[Feature:ManualPerformance] " + name
|
2015-12-04 09:58:50 +00:00
|
|
|
}
|
2015-07-07 12:29:16 +00:00
|
|
|
itArg := testArg
|
2015-05-05 14:48:50 +00:00
|
|
|
|
|
|
|
It(name, func() {
|
2016-05-13 11:14:16 +00:00
|
|
|
// Create a number of namespaces.
|
|
|
|
namespaces = createNamespaces(f, nodeCount, itArg.podsPerNode)
|
|
|
|
|
2016-02-05 15:44:08 +00:00
|
|
|
totalPods := itArg.podsPerNode * nodeCount
|
2016-05-13 11:14:16 +00:00
|
|
|
configs = generateRCConfigs(totalPods, itArg.image, itArg.command, c, namespaces)
|
2016-04-19 21:21:45 +00:00
|
|
|
var services []*api.Service
|
|
|
|
// Read the environment variable to see if we want to create services
|
|
|
|
createServices := os.Getenv("CREATE_SERVICES")
|
|
|
|
if createServices == "true" {
|
|
|
|
framework.Logf("Creating services")
|
|
|
|
services := generateServicesForConfigs(configs)
|
|
|
|
for _, service := range services {
|
2016-05-17 07:17:51 +00:00
|
|
|
_, err := c.Services(service.Namespace).Create(service)
|
2016-04-19 21:21:45 +00:00
|
|
|
framework.ExpectNoError(err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
framework.Logf("Skipping service creation")
|
|
|
|
}
|
2015-06-09 14:13:02 +00:00
|
|
|
|
|
|
|
// Simulate lifetime of RC:
|
|
|
|
// * create with initial size
|
|
|
|
// * scale RC to a random size and list all pods
|
|
|
|
// * scale RC to a random size and list all pods
|
|
|
|
// * delete it
|
|
|
|
//
|
|
|
|
// This will generate ~5 creations/deletions per second assuming:
|
2016-02-05 15:44:08 +00:00
|
|
|
// - X small RCs each 5 pods [ 5 * X = totalPods / 2 ]
|
|
|
|
// - Y medium RCs each 30 pods [ 30 * Y = totalPods / 4 ]
|
|
|
|
// - Z big RCs each 250 pods [ 250 * Z = totalPods / 4]
|
|
|
|
|
|
|
|
// We would like to spread creating replication controllers over time
|
|
|
|
// to make it possible to create/schedule them in the meantime.
|
2016-03-23 13:54:02 +00:00
|
|
|
// Currently we assume 10 pods/second average throughput.
|
2016-03-02 11:24:13 +00:00
|
|
|
// We may want to revisit it in the future.
|
2016-03-23 13:54:02 +00:00
|
|
|
creatingTime := time.Duration(totalPods/10) * time.Second
|
2016-02-05 15:44:08 +00:00
|
|
|
createAllRC(configs, creatingTime)
|
2015-06-09 14:13:02 +00:00
|
|
|
By("============================================================================")
|
2016-02-09 07:38:12 +00:00
|
|
|
|
|
|
|
// We would like to spread scaling replication controllers over time
|
|
|
|
// to make it possible to create/schedule & delete them in the meantime.
|
2016-03-23 13:54:02 +00:00
|
|
|
// Currently we assume that 10 pods/second average throughput.
|
2016-02-09 07:38:12 +00:00
|
|
|
// The expected number of created/deleted pods is less than totalPods/3.
|
2016-03-23 13:54:02 +00:00
|
|
|
scalingTime := time.Duration(totalPods/30) * time.Second
|
2016-02-09 07:38:12 +00:00
|
|
|
scaleAllRC(configs, scalingTime)
|
2015-06-09 14:13:02 +00:00
|
|
|
By("============================================================================")
|
2016-02-09 07:38:12 +00:00
|
|
|
|
|
|
|
scaleAllRC(configs, scalingTime)
|
2015-06-09 14:13:02 +00:00
|
|
|
By("============================================================================")
|
2016-02-05 15:44:08 +00:00
|
|
|
|
|
|
|
// Cleanup all created replication controllers.
|
2016-05-25 07:58:30 +00:00
|
|
|
// Currently we assume 10 pods/second average deletion throughput.
|
2016-03-02 11:24:13 +00:00
|
|
|
// We may want to revisit it in the future.
|
2016-05-25 07:58:30 +00:00
|
|
|
deletingTime := time.Duration(totalPods/10) * time.Second
|
2016-02-05 15:44:08 +00:00
|
|
|
deleteAllRC(configs, deletingTime)
|
2016-04-19 21:21:45 +00:00
|
|
|
if createServices == "true" {
|
|
|
|
for _, service := range services {
|
|
|
|
err := c.Services(ns).Delete(service.Name)
|
|
|
|
framework.ExpectNoError(err)
|
|
|
|
}
|
|
|
|
framework.Logf("%v Services created.", len(services))
|
|
|
|
}
|
2015-05-05 14:48:50 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2016-05-13 11:14:16 +00:00
|
|
|
func createNamespaces(f *framework.Framework, nodeCount, podsPerNode int) []*api.Namespace {
|
|
|
|
namespaceCount := (nodeCount + nodeCountPerNamespace - 1) / nodeCountPerNamespace
|
|
|
|
namespaces := []*api.Namespace{}
|
|
|
|
for i := 1; i <= namespaceCount; i++ {
|
|
|
|
namespace, err := f.CreateNamespace(fmt.Sprintf("load-%d-nodepods-%d", podsPerNode, i), nil)
|
|
|
|
framework.ExpectNoError(err)
|
|
|
|
namespaces = append(namespaces, namespace)
|
|
|
|
}
|
|
|
|
return namespaces
|
|
|
|
}
|
|
|
|
|
2015-05-05 14:48:50 +00:00
|
|
|
func computeRCCounts(total int) (int, int, int) {
|
|
|
|
// Small RCs owns ~0.5 of total number of pods, medium and big RCs ~0.25 each.
|
|
|
|
// For example for 3000 pods (100 nodes, 30 pods per node) there are:
|
2015-06-09 14:13:02 +00:00
|
|
|
// - 300 small RCs each 5 pods
|
2015-05-05 14:48:50 +00:00
|
|
|
// - 25 medium RCs each 30 pods
|
|
|
|
// - 3 big RCs each 250 pods
|
|
|
|
bigRCCount := total / 4 / bigRCSize
|
2015-07-07 12:29:16 +00:00
|
|
|
total -= bigRCCount * bigRCSize
|
|
|
|
mediumRCCount := total / 3 / mediumRCSize
|
|
|
|
total -= mediumRCCount * mediumRCSize
|
|
|
|
smallRCCount := total / smallRCSize
|
2015-05-05 14:48:50 +00:00
|
|
|
return smallRCCount, mediumRCCount, bigRCCount
|
|
|
|
}
|
|
|
|
|
2016-05-13 11:14:16 +00:00
|
|
|
func generateRCConfigs(totalPods int, image string, command []string, c *client.Client, nss []*api.Namespace) []*framework.RCConfig {
|
2016-04-07 17:21:31 +00:00
|
|
|
configs := make([]*framework.RCConfig, 0)
|
2015-06-09 14:13:02 +00:00
|
|
|
|
|
|
|
smallRCCount, mediumRCCount, bigRCCount := computeRCCounts(totalPods)
|
2016-05-13 11:14:16 +00:00
|
|
|
configs = append(configs, generateRCConfigsForGroup(c, nss, smallRCGroupName, smallRCSize, smallRCCount, image, command)...)
|
|
|
|
configs = append(configs, generateRCConfigsForGroup(c, nss, mediumRCGroupName, mediumRCSize, mediumRCCount, image, command)...)
|
|
|
|
configs = append(configs, generateRCConfigsForGroup(c, nss, bigRCGroupName, bigRCSize, bigRCCount, image, command)...)
|
2015-06-09 14:13:02 +00:00
|
|
|
|
|
|
|
return configs
|
|
|
|
}
|
|
|
|
|
2016-05-13 11:14:16 +00:00
|
|
|
func generateRCConfigsForGroup(c *client.Client, nss []*api.Namespace, groupName string, size, count int, image string, command []string) []*framework.RCConfig {
|
2016-04-07 17:21:31 +00:00
|
|
|
configs := make([]*framework.RCConfig, 0, count)
|
2015-06-09 14:13:02 +00:00
|
|
|
for i := 1; i <= count; i++ {
|
2016-04-07 17:21:31 +00:00
|
|
|
config := &framework.RCConfig{
|
2016-03-01 10:53:06 +00:00
|
|
|
Client: c,
|
|
|
|
Name: groupName + "-" + strconv.Itoa(i),
|
2016-05-13 11:14:16 +00:00
|
|
|
Namespace: nss[i%len(nss)].Name,
|
2016-03-01 10:53:06 +00:00
|
|
|
Timeout: 10 * time.Minute,
|
|
|
|
Image: image,
|
|
|
|
Command: command,
|
|
|
|
Replicas: size,
|
2016-03-01 14:04:08 +00:00
|
|
|
CpuRequest: 10, // 0.01 core
|
|
|
|
MemRequest: 26214400, // 25MB
|
2015-05-05 14:48:50 +00:00
|
|
|
}
|
2015-06-09 14:13:02 +00:00
|
|
|
configs = append(configs, config)
|
2015-05-05 14:48:50 +00:00
|
|
|
}
|
2015-06-09 14:13:02 +00:00
|
|
|
return configs
|
2015-05-05 14:48:50 +00:00
|
|
|
}
|
|
|
|
|
2016-04-19 21:21:45 +00:00
|
|
|
func generateServicesForConfigs(configs []*framework.RCConfig) []*api.Service {
|
|
|
|
services := make([]*api.Service, 0, len(configs))
|
|
|
|
for _, config := range configs {
|
|
|
|
serviceName := config.Name + "-svc"
|
|
|
|
labels := map[string]string{"name": config.Name}
|
|
|
|
service := &api.Service{
|
|
|
|
ObjectMeta: api.ObjectMeta{
|
2016-05-13 11:14:16 +00:00
|
|
|
Name: serviceName,
|
|
|
|
Namespace: config.Namespace,
|
2016-04-19 21:21:45 +00:00
|
|
|
},
|
|
|
|
Spec: api.ServiceSpec{
|
|
|
|
Selector: labels,
|
|
|
|
Ports: []api.ServicePort{{
|
|
|
|
Port: 80,
|
|
|
|
TargetPort: intstr.FromInt(80),
|
|
|
|
}},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
services = append(services, service)
|
|
|
|
}
|
|
|
|
return services
|
|
|
|
}
|
|
|
|
|
2015-06-09 14:13:02 +00:00
|
|
|
func sleepUpTo(d time.Duration) {
|
|
|
|
time.Sleep(time.Duration(rand.Int63n(d.Nanoseconds())))
|
|
|
|
}
|
|
|
|
|
2016-04-07 17:21:31 +00:00
|
|
|
func createAllRC(configs []*framework.RCConfig, creatingTime time.Duration) {
|
2015-06-09 14:13:02 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(len(configs))
|
|
|
|
for _, config := range configs {
|
2016-02-05 15:44:08 +00:00
|
|
|
go createRC(&wg, config, creatingTime)
|
2015-05-05 14:48:50 +00:00
|
|
|
}
|
2015-06-09 14:13:02 +00:00
|
|
|
wg.Wait()
|
2015-05-05 14:48:50 +00:00
|
|
|
}
|
2015-06-02 13:17:25 +00:00
|
|
|
|
2016-04-07 17:21:31 +00:00
|
|
|
func createRC(wg *sync.WaitGroup, config *framework.RCConfig, creatingTime time.Duration) {
|
2015-06-09 14:13:02 +00:00
|
|
|
defer GinkgoRecover()
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
sleepUpTo(creatingTime)
|
2016-04-07 17:21:31 +00:00
|
|
|
framework.ExpectNoError(framework.RunRC(*config), fmt.Sprintf("creating rc %s", config.Name))
|
2015-06-09 14:13:02 +00:00
|
|
|
}
|
|
|
|
|
2016-04-07 17:21:31 +00:00
|
|
|
func scaleAllRC(configs []*framework.RCConfig, scalingTime time.Duration) {
|
2015-06-09 14:13:02 +00:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(len(configs))
|
|
|
|
for _, config := range configs {
|
2016-02-09 07:38:12 +00:00
|
|
|
go scaleRC(&wg, config, scalingTime)
|
2015-06-02 13:17:25 +00:00
|
|
|
}
|
2015-06-09 14:13:02 +00:00
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scales RC to a random size within [0.5*size, 1.5*size] and lists all the pods afterwards.
|
|
|
|
// Scaling happens always based on original size, not the current size.
|
2016-04-07 17:21:31 +00:00
|
|
|
func scaleRC(wg *sync.WaitGroup, config *framework.RCConfig, scalingTime time.Duration) {
|
2015-06-09 14:13:02 +00:00
|
|
|
defer GinkgoRecover()
|
|
|
|
defer wg.Done()
|
|
|
|
|
2016-02-09 07:38:12 +00:00
|
|
|
sleepUpTo(scalingTime)
|
2015-06-09 14:13:02 +00:00
|
|
|
newSize := uint(rand.Intn(config.Replicas) + config.Replicas/2)
|
2016-04-07 17:21:31 +00:00
|
|
|
framework.ExpectNoError(framework.ScaleRC(config.Client, config.Namespace, config.Name, newSize, true),
|
2015-06-09 14:13:02 +00:00
|
|
|
fmt.Sprintf("scaling rc %s for the first time", config.Name))
|
|
|
|
selector := labels.SelectorFromSet(labels.Set(map[string]string{"name": config.Name}))
|
2015-12-10 09:39:03 +00:00
|
|
|
options := api.ListOptions{
|
|
|
|
LabelSelector: selector,
|
2015-12-04 13:56:33 +00:00
|
|
|
ResourceVersion: "0",
|
|
|
|
}
|
2015-12-02 11:12:57 +00:00
|
|
|
_, err := config.Client.Pods(config.Namespace).List(options)
|
2016-04-07 17:21:31 +00:00
|
|
|
framework.ExpectNoError(err, fmt.Sprintf("listing pods from rc %v", config.Name))
|
2015-06-02 13:17:25 +00:00
|
|
|
}
|
|
|
|
|
2016-04-07 17:21:31 +00:00
|
|
|
func deleteAllRC(configs []*framework.RCConfig, deletingTime time.Duration) {
|
2015-06-02 13:17:25 +00:00
|
|
|
var wg sync.WaitGroup
|
2015-06-09 14:13:02 +00:00
|
|
|
wg.Add(len(configs))
|
|
|
|
for _, config := range configs {
|
2016-02-05 15:44:08 +00:00
|
|
|
go deleteRC(&wg, config, deletingTime)
|
2015-06-02 13:17:25 +00:00
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
}
|
2015-06-09 14:13:02 +00:00
|
|
|
|
2016-04-07 17:21:31 +00:00
|
|
|
func deleteRC(wg *sync.WaitGroup, config *framework.RCConfig, deletingTime time.Duration) {
|
2015-06-09 14:13:02 +00:00
|
|
|
defer GinkgoRecover()
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
sleepUpTo(deletingTime)
|
2016-04-07 17:21:31 +00:00
|
|
|
framework.ExpectNoError(framework.DeleteRC(config.Client, config.Namespace, config.Name), fmt.Sprintf("deleting rc %s", config.Name))
|
2015-06-09 14:13:02 +00:00
|
|
|
}
|