Skip to content

Commit ffebe49

Browse files
authored
Merge pull request #114 from kkulak/feat/simulations-format
feat: simulations format
2 parents 8ce9dd1 + 9681bc1 commit ffebe49

File tree

9 files changed

+183
-20
lines changed

9 files changed

+183
-20
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ The desired state of a distributed Gatling load testing is described through a K
1212

1313
## Features
1414

15-
- Allows Gatling load testing scenario, resources, Gatling configurations files to be added in 2 ways:
15+
- Allows Gatling load testing scenario, resources, Gatling configurations files to be added in 3 ways:
1616
- Bundle them with Gatling runtime packages in a Gatling container
17+
- Run the simulations through build tool plugin (e.g. `gradle gatlingRun`) in a Docker container
1718
- Add them as multi-line definition in Gatling CR
1819
- Scaling Gatling load testing
1920
- Horizontal scaling: number of pods running in parallel during a load testing can be configured

api/v1alpha1/gatling_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ type TestScenarioSpec struct {
114114
// +kubebuilder:validation:Optional
115115
Parallelism int32 `json:"parallelism,omitempty"`
116116

117+
// (Optional) Gatling simulation format, supports `bundle` and `gradle`. Defaults to `bundle`
118+
// +kubebuilder:validation:Optional
119+
// +kubebuilder:validation:Enum=bundle;gradle
120+
SimulationsFormat string `json:"simulationsFormat,omitempty"`
121+
117122
// (Optional) Gatling Resources directory path where simulation files are stored. Defaults to `/opt/gatling/user-files/simulations`
118123
// +kubebuilder:validation:Optional
119124
SimulationsDirectoryPath string `json:"simulationsDirectoryPath,omitempty"`

config/crd/bases/gatling-operator.tech.zozo.com_gatlings.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,6 +4096,13 @@ spec:
40964096
description: (Optional) Gatling Resources directory path where
40974097
simulation files are stored. Defaults to `/opt/gatling/user-files/simulations`
40984098
type: string
4099+
simulationsFormat:
4100+
description: (Optional) Gatling simulation format, supports `bundle`
4101+
and `gradle`. Defaults to `bundle`
4102+
enum:
4103+
- bundle
4104+
- gradle
4105+
type: string
40994106
startTime:
41004107
description: (Optional) Test Start time.
41014108
type: string

controllers/gatling_controller.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const (
4949
maxJobRunWaitTimeInSeconds = 10800 // 10800 sec (3 hours)
5050
defaultGatlingImage = "ghcr.io/st-tech/gatling:latest"
5151
defaultRcloneImage = "rclone/rclone:latest"
52+
defaultSimulationFormat = "bundle"
5253
defaultSimulationsDirectoryPath = "/opt/gatling/user-files/simulations"
5354
defaultResourcesDirectoryPath = "/opt/gatling/user-files/resources"
5455
defaultResultsDirectoryPath = "/opt/gatling/results"
@@ -521,6 +522,7 @@ func (r *GatlingReconciler) newGatlingRunnerJobForCR(gatling *gatlingv1alpha1.Ga
521522
)
522523

523524
gatlingRunnerCommand := commands.GetGatlingRunnerCommand(
525+
r.getSimulationFormat(gatling),
524526
r.getSimulationsDirectoryPath(gatling),
525527
r.getTempSimulationsDirectoryPath(gatling),
526528
r.getResourcesDirectoryPath(gatling),
@@ -1072,6 +1074,14 @@ func (r *GatlingReconciler) getPodServiceAccountName(gatling *gatlingv1alpha1.Ga
10721074
return serviceAccountName
10731075
}
10741076

1077+
func (r *GatlingReconciler) getSimulationFormat(gatling *gatlingv1alpha1.Gatling) string {
1078+
format := defaultSimulationFormat
1079+
if &gatling.Spec.TestScenarioSpec != nil && gatling.Spec.TestScenarioSpec.SimulationsFormat != "" {
1080+
format = gatling.Spec.TestScenarioSpec.SimulationsFormat
1081+
}
1082+
return format
1083+
}
1084+
10751085
func (r *GatlingReconciler) getSimulationsDirectoryPath(gatling *gatlingv1alpha1.Gatling) string {
10761086
path := defaultSimulationsDirectoryPath
10771087
if &gatling.Spec.TestScenarioSpec != nil && gatling.Spec.TestScenarioSpec.SimulationsDirectoryPath != "" {

docs/api.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ _Appears in:_
2424

2525
| Field | Description |
2626
| --- | --- |
27-
| `provider` _string_ | (Required) Provider specifies the cloud provider that will be used. Supported providers: `aws`, `gcp`, and `azure` |
27+
| `provider` _string_ | (Required) Provider specifies the cloud provider that will be used.
28+
Supported providers: `aws`, `gcp`, and `azure` |
2829
| `bucket` _string_ | (Required) Storage Bucket Name. |
2930
| `region` _string_ | (Optional) Region Name. |
3031
| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvar-v1-core) array_ | (Optional) Environment variables used for connecting to the cloud providers. |
@@ -82,7 +83,8 @@ _Appears in:_
8283

8384
| Field | Description |
8485
| --- | --- |
85-
| `provider` _string_ | (Required) Provider specifies notification service provider. Supported providers: `slack` |
86+
| `provider` _string_ | (Required) Provider specifies notification service provider.
87+
Supported providers: `slack` |
8688
| `secretName` _string_ | (Required) The name of secret in which all key/value sets needed for the notification are stored. |
8789

8890

@@ -149,6 +151,7 @@ _Appears in:_
149151
| --- | --- |
150152
| `startTime` _string_ | (Optional) Test Start time. |
151153
| `parallelism` _integer_ | (Optional) Number of pods running at the same time. Defaults to `1` (Minimum `1`) |
154+
| `simulationsFormat` _string_ | (Optional) Gatling simulation format, supports `bundle` and `gradle`. Defaults to `bundle` |
152155
| `simulationsDirectoryPath` _string_ | (Optional) Gatling Resources directory path where simulation files are stored. Defaults to `/opt/gatling/user-files/simulations` |
153156
| `resourcesDirectoryPath` _string_ | (Optional) Gatling Simulation directory path where resources are stored. Defaults to `/opt/gatling/user-files/resources` |
154157
| `resultsDirectoryPath` _string_ | (Optional) Gatling Results directory path where results are stored. Defaults to `/opt/gatling/results` |

docs/user-guide.md

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ As described in Configuration Overview, there are 2 things that you need to cons
5151

5252
For `Gatling docker image`, you can use default image `ghcr.io/st-tech/gatling:latest`, or you can create custom image to use.
5353

54-
For `Gatling load testing related files`, you have 3 options:
54+
For `Gatling load testing related files`, you have 4 options:
5555

5656
- Create custom image to bundle Gatling load testing files with Java runtime and Gatling standalone bundle package
57+
- Create custom image to bundle all Gatling related files with a gradle gatling plugin
5758
- Add Gatling load testing files as multi-line definitions in `.spec.testScenatioSpec` part of `Gatling CR`
5859
- Set up persistent volume in `.persistentVolume` and `.persistentVolumeClaim` in `Gatling CR` and load test files from the persistent volume in Gatling load test files.
5960

@@ -179,6 +180,80 @@ spec:
179180
...omit...
180181
```
181182

183+
### Create Custom Gatling Image with Gradle Gatling plugin
184+
185+
Example project can be found [here](https://github.com/gatling/gatling-gradle-plugin-demo-kotlin).
186+
187+
Let's start with cloning the repo:
188+
```bash
189+
git clone git@github.com:gatling/gatling-gradle-plugin-demo-kotlin.git
190+
cd gatling-gradle-plugin-demo-kotlin
191+
```
192+
193+
In order to run this example on Gatling Operator, we have to build a custom docker image with both gradle and gatling baked in.
194+
195+
Let's create a `Dockerfile` in the root directory of the `gatling-gradle-plugin-demo-kotlin` project:
196+
```bash
197+
FROM azul/zulu-openjdk:21-latest
198+
199+
# dependency versions
200+
ENV GATLING_VERSION 3.10.5
201+
ENV GRADLE_VERSION 8.7
202+
203+
# install gatling
204+
RUN mkdir /opt/gatling && \
205+
apt-get update && apt-get upgrade -y && apt-get install -y wget unzip && \
206+
mkdir -p /tmp/downloads && \
207+
wget -q -O /tmp/downloads/gatling-$GATLING_VERSION.zip \
208+
https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/$GATLING_VERSION/gatling-charts-highcharts-bundle-$GATLING_VERSION-bundle.zip && \
209+
mkdir -p /tmp/archive && cd /tmp/archive && \
210+
unzip /tmp/downloads/gatling-$GATLING_VERSION.zip && \
211+
mv /tmp/archive/gatling-charts-highcharts-bundle-$GATLING_VERSION/* /opt/gatling/ && \
212+
rm -rf /opt/gatling/user-files/simulations/computerdatabase /tmp/*
213+
214+
# install gradle
215+
RUN mkdir /opt/gradle && \
216+
wget -q -O /tmp/gradle-$GRADLE_VERSION.zip https://services.gradle.org/distributions/gradle-$GRADLE_VERSION-bin.zip && \
217+
unzip -d /opt/gradle /tmp/gradle-$GRADLE_VERSION.zip && \
218+
rm -rf /tmp/*
219+
220+
# change context to gatling directory
221+
WORKDIR /opt/gatling
222+
223+
# copy gradle files to gatling directory
224+
COPY . .
225+
226+
# set environment variables
227+
ENV PATH /opt/gatling/bin:/opt/gradle/gradle-$GRADLE_VERSION/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
228+
ENV GATLING_HOME /opt/gatling
229+
230+
ENTRYPOINT ["gatling.sh"]
231+
```
232+
233+
Now we have to build the custom image:
234+
```bash
235+
# Build Docker image
236+
docker build -t <your-registry>/gatling:<tag> .
237+
# Push the image to your container registry
238+
docker push <your-registry>/gatling:<tag>
239+
```
240+
241+
Finally, specify the image in `.spec.podSpec.gatlingImage` of Gatling CR and change the value of property `testScenarioSpec.simulationsFormat` to use it in your distributed load testing.
242+
243+
```yaml
244+
apiVersion: gatling-operator.tech.zozo.com/v1alpha1
245+
kind: Gatling
246+
metadata:
247+
name: gatling-gradle
248+
spec:
249+
podSpec:
250+
serviceAccountName: "gatling-operator-worker"
251+
gatlingImage: <your-registry>/gatling:<tag>
252+
testScenarioSpec:
253+
simulationsFormat: gradle
254+
...omit...
255+
```
256+
182257
### Add Gatling Load Testing Files in Gatling CR
183258

184259
As explained previously, instead of bundling Gatling load testing files in the Gatling docker image, you can add them as multi-line definitions in `.spec.testScenatioSpec` of `Gatling CR`, based on which Gatling Controller automatically creates `ConfigMap` resources and injects Gatling runner Pod with the files.

pkg/commands/commands.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,18 @@ done
3232
}
3333

3434
func GetGatlingRunnerCommand(
35-
simulationsDirectoryPath string, tempSimulationsDirectoryPath string, resourcesDirectoryPath string,
36-
resultsDirectoryPath string, startTime string, simulationClass string, generateLocalReport bool) string {
35+
simulationsFormat string, simulationsDirectoryPath string, tempSimulationsDirectoryPath string,
36+
resourcesDirectoryPath string, resultsDirectoryPath string, startTime string, simulationClass string,
37+
generateLocalReport bool) string {
3738

3839
template := `
40+
SIMULATIONS_FORMAT=%s
3941
SIMULATIONS_DIR_PATH=%s
4042
TEMP_SIMULATIONS_DIR_PATH=%s
4143
RESOURCES_DIR_PATH=%s
4244
RESULTS_DIR_PATH=%s
4345
START_TIME="%s"
46+
SIMULATION_CLASS=%s
4447
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/COMPLETED"
4548
if [ -z "${START_TIME}" ]; then
4649
START_TIME=$(date +"%%Y-%%m-%%d %%H:%%M:%%S" --utc)
@@ -66,7 +69,12 @@ fi
6669
if [ ! -d ${RESULTS_DIR_PATH} ]; then
6770
mkdir -p ${RESULTS_DIR_PATH}
6871
fi
69-
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s %s -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} %s %s
72+
73+
if [ ${SIMULATIONS_FORMAT} = "bundle" ]; then
74+
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s ${SIMULATION_CLASS} -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} %s %s
75+
elif [ ${SIMULATIONS_FORMAT} = "gradle" ]; then
76+
gradle -Dgatling.core.directory.results=${RESULTS_DIR_PATH} gatlingRun-${SIMULATION_CLASS}
77+
fi
7078
7179
GATLING_EXIT_STATUS=$?
7280
if [ $GATLING_EXIT_STATUS -ne 0 ]; then
@@ -84,6 +92,7 @@ exit $GATLING_EXIT_STATUS
8492
runModeOptionLocal := "-rm local"
8593

8694
return fmt.Sprintf(template,
95+
simulationsFormat,
8796
simulationsDirectoryPath,
8897
tempSimulationsDirectoryPath,
8998
resourcesDirectoryPath,

pkg/commands/commands_test.go

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ done
3131

3232
var _ = Describe("GetGatlingRunnerCommand", func() {
3333
var (
34+
simulationsFormat string
3435
simulationsDirectoryPath string
3536
tempSimulationsDirectoryPath string
3637
resourcesDirectoryPath string
@@ -42,6 +43,7 @@ var _ = Describe("GetGatlingRunnerCommand", func() {
4243
)
4344

4445
BeforeEach(func() {
46+
simulationsFormat = "bundle"
4547
simulationsDirectoryPath = "testSimulationDirectoryPath"
4648
tempSimulationsDirectoryPath = "testTempSimulationsDirectoryPath"
4749
resourcesDirectoryPath = "testResourcesDirectoryPath"
@@ -53,11 +55,13 @@ var _ = Describe("GetGatlingRunnerCommand", func() {
5355
It("GetCommandsWithLocalReport", func() {
5456
generateLocalReport = true
5557
expectedValue = `
58+
SIMULATIONS_FORMAT=bundle
5659
SIMULATIONS_DIR_PATH=testSimulationDirectoryPath
5760
TEMP_SIMULATIONS_DIR_PATH=testTempSimulationsDirectoryPath
5861
RESOURCES_DIR_PATH=testResourcesDirectoryPath
5962
RESULTS_DIR_PATH=testResultsDirectoryPath
6063
START_TIME="2021-09-10 08:45:31"
64+
SIMULATION_CLASS=testSimulationClass
6165
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/COMPLETED"
6266
if [ -z "${START_TIME}" ]; then
6367
START_TIME=$(date +"%Y-%m-%d %H:%M:%S" --utc)
@@ -83,25 +87,34 @@ fi
8387
if [ ! -d ${RESULTS_DIR_PATH} ]; then
8488
mkdir -p ${RESULTS_DIR_PATH}
8589
fi
86-
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s testSimulationClass -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH}
8790
88-
if [ $? -ne 0 ]; then
91+
if [ ${SIMULATIONS_FORMAT} = "bundle" ]; then
92+
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s ${SIMULATION_CLASS} -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} -rm local
93+
elif [ ${SIMULATIONS_FORMAT} = "gradle" ]; then
94+
gradle -Dgatling.core.directory.results=${RESULTS_DIR_PATH} gatlingRun-${SIMULATION_CLASS}
95+
fi
96+
97+
GATLING_EXIT_STATUS=$?
98+
if [ $GATLING_EXIT_STATUS -ne 0 ]; then
8999
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/FAILED"
90100
echo "gatling.sh has failed!" 1>&2
91101
fi
92102
touch ${RUN_STATUS_FILE}
103+
exit $GATLING_EXIT_STATUS
93104
`
94-
Expect(GetGatlingRunnerCommand(simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
105+
Expect(GetGatlingRunnerCommand(simulationsFormat, simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
95106
})
96107

97108
It("GetCommandWithoutLocalReport", func() {
98109
generateLocalReport = false
99110
expectedValue = `
111+
SIMULATIONS_FORMAT=bundle
100112
SIMULATIONS_DIR_PATH=testSimulationDirectoryPath
101113
TEMP_SIMULATIONS_DIR_PATH=testTempSimulationsDirectoryPath
102114
RESOURCES_DIR_PATH=testResourcesDirectoryPath
103115
RESULTS_DIR_PATH=testResultsDirectoryPath
104116
START_TIME="2021-09-10 08:45:31"
117+
SIMULATION_CLASS=testSimulationClass
105118
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/COMPLETED"
106119
if [ -z "${START_TIME}" ]; then
107120
START_TIME=$(date +"%Y-%m-%d %H:%M:%S" --utc)
@@ -127,15 +140,22 @@ fi
127140
if [ ! -d ${RESULTS_DIR_PATH} ]; then
128141
mkdir -p ${RESULTS_DIR_PATH}
129142
fi
130-
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s testSimulationClass -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} -nr
131143
132-
if [ $? -ne 0 ]; then
144+
if [ ${SIMULATIONS_FORMAT} = "bundle" ]; then
145+
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s ${SIMULATION_CLASS} -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} -nr -rm local
146+
elif [ ${SIMULATIONS_FORMAT} = "gradle" ]; then
147+
gradle -Dgatling.core.directory.results=${RESULTS_DIR_PATH} gatlingRun-${SIMULATION_CLASS}
148+
fi
149+
150+
GATLING_EXIT_STATUS=$?
151+
if [ $GATLING_EXIT_STATUS -ne 0 ]; then
133152
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/FAILED"
134153
echo "gatling.sh has failed!" 1>&2
135154
fi
136155
touch ${RUN_STATUS_FILE}
156+
exit $GATLING_EXIT_STATUS
137157
`
138-
Expect(GetGatlingRunnerCommand(simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
158+
Expect(GetGatlingRunnerCommand(simulationsFormat, simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
139159
})
140160
})
141161

@@ -160,9 +180,19 @@ var _ = Describe("GetGatlingTransferResultCommand", func() {
160180
expectedValue = `
161181
RESULTS_DIR_PATH=testResultsDirectoryPath
162182
rclone config create s3 s3 env_auth=true region ap-northeast-1
163-
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
164-
do
165-
rclone copyto ${source} --s3-no-check-bucket --s3-env-auth testStoragePath/${HOSTNAME}.log
183+
while true; do
184+
if [ -f "${RESULTS_DIR_PATH}/FAILED" ]; then
185+
echo "Skip transfering gatling results"
186+
break
187+
fi
188+
if [ -f "${RESULTS_DIR_PATH}/COMPLETED" ]; then
189+
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
190+
do
191+
rclone copyto ${source} --s3-no-check-bucket --s3-env-auth testStoragePath/${HOSTNAME}.log
192+
done
193+
break
194+
fi
195+
sleep 1;
166196
done
167197
`
168198
})
@@ -178,10 +208,20 @@ done
178208
RESULTS_DIR_PATH=testResultsDirectoryPath
179209
# assumes gcs bucket using uniform bucket-level access control
180210
rclone config create gs "google cloud storage" bucket_policy_only true --non-interactive
181-
# assumes each pod only contain single gatling log file but use for loop to use find command result
182-
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
183-
do
184-
rclone copyto ${source} testStoragePath/${HOSTNAME}.log
211+
while true; do
212+
if [ -f "${RESULTS_DIR_PATH}/FAILED" ]; then
213+
echo "Skip transfering gatling results"
214+
break
215+
fi
216+
if [ -f "${RESULTS_DIR_PATH}/COMPLETED" ]; then
217+
# assumes each pod only contain single gatling log file but use for loop to use find command result
218+
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
219+
do
220+
rclone copyto ${source} testStoragePath/${HOSTNAME}.log
221+
done
222+
break
223+
fi
224+
sleep 1;
185225
done
186226
`
187227
})

pkg/commands/suite_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package commands
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestBucket(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Commands Suite")
13+
}

0 commit comments

Comments
 (0)