Skip to content

Commit 07179d0

Browse files
authored
Merge pull request kubernetes#87998 from msau42/e2e-reattach-stress
Add stress test to repeatedly restart Pods with PVCs in parallel
2 parents 4362bf7 + 6596e20 commit 07179d0

File tree

9 files changed

+242
-49
lines changed

9 files changed

+242
-49
lines changed

test/e2e/framework/pod/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func MakeSecPod(podConfig *Config) (*v1.Pod, error) {
177177
if len(podConfig.Command) == 0 {
178178
podConfig.Command = "trap exit TERM; while true; do sleep 1; done"
179179
}
180-
podName := "security-context-" + string(uuid.NewUUID())
180+
podName := "pod-" + string(uuid.NewUUID())
181181
if podConfig.FsGroup == nil {
182182
podConfig.FsGroup = func(i int64) *int64 {
183183
return &i

test/e2e/storage/csi_volumes.go

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,29 +31,13 @@ var csiTestDrivers = []func() testsuites.TestDriver{
3131
// Don't run tests with mock driver (drivers.InitMockCSIDriver), it does not provide persistent storage.
3232
}
3333

34-
// List of testSuites to be executed in below loop
35-
var csiTestSuites = []func() testsuites.TestSuite{
36-
testsuites.InitEphemeralTestSuite,
37-
testsuites.InitVolumesTestSuite,
38-
testsuites.InitVolumeIOTestSuite,
39-
testsuites.InitVolumeModeTestSuite,
40-
testsuites.InitSubPathTestSuite,
41-
testsuites.InitProvisioningTestSuite,
42-
testsuites.InitSnapshottableTestSuite,
43-
testsuites.InitMultiVolumeTestSuite,
44-
testsuites.InitDisruptiveTestSuite,
45-
testsuites.InitVolumeExpandTestSuite,
46-
testsuites.InitVolumeLimitsTestSuite,
47-
testsuites.InitTopologyTestSuite,
48-
}
49-
5034
// This executes testSuites for csi volumes.
5135
var _ = utils.SIGDescribe("CSI Volumes", func() {
5236
for _, initDriver := range csiTestDrivers {
5337
curDriver := initDriver()
5438

5539
ginkgo.Context(testsuites.GetDriverNameWithFeatureTags(curDriver), func() {
56-
testsuites.DefineTestSuite(curDriver, csiTestSuites)
40+
testsuites.DefineTestSuite(curDriver, testsuites.CSISuites)
5741
})
5842
}
5943
})

test/e2e/storage/drivers/csi.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,10 @@ func InitGcePDCSIDriver() testsuites.TestDriver {
426426
},
427427
RequiredAccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
428428
TopologyKeys: []string{GCEPDCSIZoneTopologyKey},
429+
StressTestOptions: &testsuites.StressTestOptions{
430+
NumPods: 10,
431+
NumRestarts: 10,
432+
},
429433
},
430434
}
431435
}

test/e2e/storage/external/external.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,6 @@ type driverDefinition struct {
130130
ClientNodeName string
131131
}
132132

133-
// List of testSuites to be executed for each external driver.
134-
var csiTestSuites = []func() testsuites.TestSuite{
135-
testsuites.InitEphemeralTestSuite,
136-
testsuites.InitMultiVolumeTestSuite,
137-
testsuites.InitProvisioningTestSuite,
138-
testsuites.InitSnapshottableTestSuite,
139-
testsuites.InitSubPathTestSuite,
140-
testsuites.InitVolumeIOTestSuite,
141-
testsuites.InitVolumeModeTestSuite,
142-
testsuites.InitVolumesTestSuite,
143-
testsuites.InitVolumeExpandTestSuite,
144-
testsuites.InitDisruptiveTestSuite,
145-
testsuites.InitVolumeLimitsTestSuite,
146-
}
147-
148133
func init() {
149134
e2econfig.Flags.Var(testDriverParameter{}, "storage.testdriver", "name of a .yaml or .json file that defines a driver for storage testing, can be used more than once")
150135
}
@@ -182,7 +167,7 @@ func AddDriverDefinition(filename string) error {
182167

183168
description := "External Storage " + testsuites.GetDriverNameWithFeatureTags(driver)
184169
ginkgo.Describe(description, func() {
185-
testsuites.DefineTestSuite(driver, csiTestSuites)
170+
testsuites.DefineTestSuite(driver, testsuites.CSISuites)
186171
})
187172

188173
return nil

test/e2e/storage/in_tree_volumes.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,13 @@ var testDrivers = []func() testsuites.TestDriver{
4848
drivers.InitLocalDriverWithVolumeType(utils.LocalVolumeGCELocalSSD),
4949
}
5050

51-
// List of testSuites to be executed in below loop
52-
var testSuites = []func() testsuites.TestSuite{
53-
testsuites.InitVolumesTestSuite,
54-
testsuites.InitVolumeIOTestSuite,
55-
testsuites.InitVolumeModeTestSuite,
56-
testsuites.InitSubPathTestSuite,
57-
testsuites.InitProvisioningTestSuite,
58-
testsuites.InitMultiVolumeTestSuite,
59-
testsuites.InitVolumeExpandTestSuite,
60-
testsuites.InitDisruptiveTestSuite,
61-
testsuites.InitVolumeLimitsTestSuite,
62-
testsuites.InitTopologyTestSuite,
63-
}
64-
6551
// This executes testSuites for in-tree volumes.
6652
var _ = utils.SIGDescribe("In-tree Volumes", func() {
6753
for _, initDriver := range testDrivers {
6854
curDriver := initDriver()
6955

7056
ginkgo.Context(testsuites.GetDriverNameWithFeatureTags(curDriver), func() {
71-
testsuites.DefineTestSuite(curDriver, testSuites)
57+
testsuites.DefineTestSuite(curDriver, testsuites.BaseSuites)
7258
})
7359
}
7460
})

test/e2e/storage/testsuites/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ go_library(
1010
"multivolume.go",
1111
"provisioning.go",
1212
"snapshottable.go",
13+
"stress.go",
1314
"subpath.go",
1415
"testdriver.go",
1516
"topology.go",

test/e2e/storage/testsuites/base.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@ func init() {
6060

6161
type opCounts map[string]int64
6262

63+
// BaseSuites is a list of storage test suites that work for in-tree and CSI drivers
64+
var BaseSuites = []func() TestSuite{
65+
InitVolumesTestSuite,
66+
InitVolumeIOTestSuite,
67+
InitVolumeModeTestSuite,
68+
InitSubPathTestSuite,
69+
InitProvisioningTestSuite,
70+
InitMultiVolumeTestSuite,
71+
InitVolumeExpandTestSuite,
72+
InitDisruptiveTestSuite,
73+
InitVolumeLimitsTestSuite,
74+
InitTopologyTestSuite,
75+
InitStressTestSuite,
76+
}
77+
78+
// CSISuites is a list of storage test suites that work only for CSI drivers
79+
var CSISuites = append(BaseSuites,
80+
InitEphemeralTestSuite,
81+
InitSnapshottableTestSuite,
82+
)
83+
6384
// TestSuite represents an interface for a set of tests which works with TestDriver
6485
type TestSuite interface {
6586
// GetTestSuiteInfo returns the TestSuiteInfo for this TestSuite
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// This suite tests volumes under stress conditions
18+
19+
package testsuites
20+
21+
import (
22+
"context"
23+
"sync"
24+
25+
"github.com/onsi/ginkgo"
26+
27+
v1 "k8s.io/api/core/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
errors "k8s.io/apimachinery/pkg/util/errors"
30+
clientset "k8s.io/client-go/kubernetes"
31+
"k8s.io/kubernetes/test/e2e/framework"
32+
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
33+
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
34+
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
35+
"k8s.io/kubernetes/test/e2e/storage/testpatterns"
36+
)
37+
38+
type stressTestSuite struct {
39+
tsInfo TestSuiteInfo
40+
}
41+
42+
type stressTest struct {
43+
config *PerTestConfig
44+
driverCleanup func()
45+
46+
intreeOps opCounts
47+
migratedOps opCounts
48+
49+
resources []*VolumeResource
50+
pods []*v1.Pod
51+
// stop and wait for any async routines
52+
wg sync.WaitGroup
53+
stopChs []chan struct{}
54+
55+
testOptions StressTestOptions
56+
}
57+
58+
var _ TestSuite = &stressTestSuite{}
59+
60+
// InitStressTestSuite returns stressTestSuite that implements TestSuite interface
61+
func InitStressTestSuite() TestSuite {
62+
return &stressTestSuite{
63+
tsInfo: TestSuiteInfo{
64+
Name: "stress",
65+
TestPatterns: []testpatterns.TestPattern{
66+
testpatterns.DefaultFsDynamicPV,
67+
testpatterns.BlockVolModeDynamicPV,
68+
},
69+
},
70+
}
71+
}
72+
73+
func (t *stressTestSuite) GetTestSuiteInfo() TestSuiteInfo {
74+
return t.tsInfo
75+
}
76+
77+
func (t *stressTestSuite) SkipRedundantSuite(driver TestDriver, pattern testpatterns.TestPattern) {
78+
}
79+
80+
func (t *stressTestSuite) DefineTests(driver TestDriver, pattern testpatterns.TestPattern) {
81+
var (
82+
dInfo = driver.GetDriverInfo()
83+
cs clientset.Interface
84+
)
85+
86+
ginkgo.BeforeEach(func() {
87+
// Check preconditions.
88+
if dInfo.StressTestOptions == nil {
89+
e2eskipper.Skipf("Driver %s doesn't specify stress test options -- skipping", dInfo.Name)
90+
}
91+
92+
if _, ok := driver.(DynamicPVTestDriver); !ok {
93+
e2eskipper.Skipf("Driver %s doesn't implement DynamicPVTestDriver -- skipping", dInfo.Name)
94+
}
95+
96+
if !driver.GetDriverInfo().Capabilities[CapBlock] && pattern.VolMode == v1.PersistentVolumeBlock {
97+
e2eskipper.Skipf("Driver %q does not support block volume mode - skipping", dInfo.Name)
98+
}
99+
})
100+
101+
// This intentionally comes after checking the preconditions because it
102+
// registers its own BeforeEach which creates the namespace. Beware that it
103+
// also registers an AfterEach which renders f unusable. Any code using
104+
// f must run inside an It or Context callback.
105+
f := framework.NewDefaultFramework("stress")
106+
107+
init := func() *stressTest {
108+
cs = f.ClientSet
109+
l := &stressTest{}
110+
111+
// Now do the more expensive test initialization.
112+
l.config, l.driverCleanup = driver.PrepareTest(f)
113+
l.intreeOps, l.migratedOps = getMigrationVolumeOpCounts(f.ClientSet, dInfo.InTreePluginName)
114+
l.resources = []*VolumeResource{}
115+
l.pods = []*v1.Pod{}
116+
l.stopChs = []chan struct{}{}
117+
l.testOptions = *dInfo.StressTestOptions
118+
119+
return l
120+
}
121+
122+
cleanup := func(l *stressTest) {
123+
var errs []error
124+
125+
framework.Logf("Stopping and waiting for all test routines to finish")
126+
for _, stopCh := range l.stopChs {
127+
close(stopCh)
128+
}
129+
l.wg.Wait()
130+
131+
for _, pod := range l.pods {
132+
framework.Logf("Deleting pod %v", pod.Name)
133+
err := e2epod.DeletePodWithWait(cs, pod)
134+
errs = append(errs, err)
135+
}
136+
137+
for _, resource := range l.resources {
138+
errs = append(errs, resource.CleanupResource())
139+
}
140+
141+
errs = append(errs, tryFunc(l.driverCleanup))
142+
framework.ExpectNoError(errors.NewAggregate(errs), "while cleaning up resource")
143+
validateMigrationVolumeOpCounts(f.ClientSet, dInfo.InTreePluginName, l.intreeOps, l.migratedOps)
144+
}
145+
146+
ginkgo.It("multiple pods should access different volumes repeatedly [Slow] [Serial]", func() {
147+
var err error
148+
149+
l := init()
150+
defer func() {
151+
cleanup(l)
152+
}()
153+
154+
for i := 0; i < l.testOptions.NumPods; i++ {
155+
framework.Logf("Creating resources for pod %v/%v", i, l.testOptions.NumPods-1)
156+
r := CreateVolumeResource(driver, l.config, pattern, t.GetTestSuiteInfo().SupportedSizeRange)
157+
l.resources = append(l.resources, r)
158+
podConfig := e2epod.Config{
159+
NS: f.Namespace.Name,
160+
PVCs: []*v1.PersistentVolumeClaim{r.Pvc},
161+
SeLinuxLabel: e2epv.SELinuxLabel,
162+
}
163+
pod, err := e2epod.MakeSecPod(&podConfig)
164+
framework.ExpectNoError(err)
165+
166+
l.pods = append(l.pods, pod)
167+
l.stopChs = append(l.stopChs, make(chan struct{}))
168+
}
169+
170+
// Restart pod repeatedly
171+
for i := 0; i < l.testOptions.NumPods; i++ {
172+
podIndex := i
173+
l.wg.Add(1)
174+
go func() {
175+
defer ginkgo.GinkgoRecover()
176+
defer l.wg.Done()
177+
for j := 0; j < l.testOptions.NumRestarts; j++ {
178+
select {
179+
case <-l.stopChs[podIndex]:
180+
return
181+
default:
182+
pod := l.pods[podIndex]
183+
framework.Logf("Pod %v, Iteration %v/%v", podIndex, j, l.testOptions.NumRestarts-1)
184+
_, err = cs.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
185+
framework.ExpectNoError(err)
186+
187+
err = e2epod.WaitForPodRunningInNamespace(cs, pod)
188+
framework.ExpectNoError(err)
189+
190+
// TODO: write data per pod and validate it everytime
191+
192+
err = e2epod.DeletePodWithWait(f.ClientSet, pod)
193+
framework.ExpectNoError(err)
194+
}
195+
}
196+
}()
197+
}
198+
199+
l.wg.Wait()
200+
})
201+
}

test/e2e/storage/testsuites/testdriver.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,17 @@ type DriverInfo struct {
188188
// Only relevant if TopologyKeys is set. Defaults to 1.
189189
// Example: multi-zonal disk requires at least 2 allowed topologies.
190190
NumAllowedTopologies int
191+
// [Optional] Scale parameters for stress tests.
192+
StressTestOptions *StressTestOptions
193+
}
194+
195+
// StressTestOptions contains parameters used for stress tests.
196+
type StressTestOptions struct {
197+
// Number of pods to create in the test. This may also create
198+
// up to 1 volume per pod.
199+
NumPods int
200+
// Number of times to restart each Pod.
201+
NumRestarts int
191202
}
192203

193204
// PerTestConfig represents parameters that control test execution.

0 commit comments

Comments
 (0)