Skip to content

Commit

Permalink
Merge pull request #114 from kkulak/feat/simulations-format
Browse files Browse the repository at this point in the history
feat: simulations format
  • Loading branch information
kane8n authored Apr 12, 2024
2 parents 8ce9dd1 + 9681bc1 commit ffebe49
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 20 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ The desired state of a distributed Gatling load testing is described through a K

## Features

- Allows Gatling load testing scenario, resources, Gatling configurations files to be added in 2 ways:
- Allows Gatling load testing scenario, resources, Gatling configurations files to be added in 3 ways:
- Bundle them with Gatling runtime packages in a Gatling container
- Run the simulations through build tool plugin (e.g. `gradle gatlingRun`) in a Docker container
- Add them as multi-line definition in Gatling CR
- Scaling Gatling load testing
- Horizontal scaling: number of pods running in parallel during a load testing can be configured
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/gatling_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ type TestScenarioSpec struct {
// +kubebuilder:validation:Optional
Parallelism int32 `json:"parallelism,omitempty"`

// (Optional) Gatling simulation format, supports `bundle` and `gradle`. Defaults to `bundle`
// +kubebuilder:validation:Optional
// +kubebuilder:validation:Enum=bundle;gradle
SimulationsFormat string `json:"simulationsFormat,omitempty"`

// (Optional) Gatling Resources directory path where simulation files are stored. Defaults to `/opt/gatling/user-files/simulations`
// +kubebuilder:validation:Optional
SimulationsDirectoryPath string `json:"simulationsDirectoryPath,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions config/crd/bases/gatling-operator.tech.zozo.com_gatlings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4096,6 +4096,13 @@ spec:
description: (Optional) Gatling Resources directory path where
simulation files are stored. Defaults to `/opt/gatling/user-files/simulations`
type: string
simulationsFormat:
description: (Optional) Gatling simulation format, supports `bundle`
and `gradle`. Defaults to `bundle`
enum:
- bundle
- gradle
type: string
startTime:
description: (Optional) Test Start time.
type: string
Expand Down
10 changes: 10 additions & 0 deletions controllers/gatling_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const (
maxJobRunWaitTimeInSeconds = 10800 // 10800 sec (3 hours)
defaultGatlingImage = "ghcr.io/st-tech/gatling:latest"
defaultRcloneImage = "rclone/rclone:latest"
defaultSimulationFormat = "bundle"
defaultSimulationsDirectoryPath = "/opt/gatling/user-files/simulations"
defaultResourcesDirectoryPath = "/opt/gatling/user-files/resources"
defaultResultsDirectoryPath = "/opt/gatling/results"
Expand Down Expand Up @@ -521,6 +522,7 @@ func (r *GatlingReconciler) newGatlingRunnerJobForCR(gatling *gatlingv1alpha1.Ga
)

gatlingRunnerCommand := commands.GetGatlingRunnerCommand(
r.getSimulationFormat(gatling),
r.getSimulationsDirectoryPath(gatling),
r.getTempSimulationsDirectoryPath(gatling),
r.getResourcesDirectoryPath(gatling),
Expand Down Expand Up @@ -1072,6 +1074,14 @@ func (r *GatlingReconciler) getPodServiceAccountName(gatling *gatlingv1alpha1.Ga
return serviceAccountName
}

func (r *GatlingReconciler) getSimulationFormat(gatling *gatlingv1alpha1.Gatling) string {
format := defaultSimulationFormat
if &gatling.Spec.TestScenarioSpec != nil && gatling.Spec.TestScenarioSpec.SimulationsFormat != "" {
format = gatling.Spec.TestScenarioSpec.SimulationsFormat
}
return format
}

func (r *GatlingReconciler) getSimulationsDirectoryPath(gatling *gatlingv1alpha1.Gatling) string {
path := defaultSimulationsDirectoryPath
if &gatling.Spec.TestScenarioSpec != nil && gatling.Spec.TestScenarioSpec.SimulationsDirectoryPath != "" {
Expand Down
7 changes: 5 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ _Appears in:_

| Field | Description |
| --- | --- |
| `provider` _string_ | (Required) Provider specifies the cloud provider that will be used. Supported providers: `aws`, `gcp`, and `azure` |
| `provider` _string_ | (Required) Provider specifies the cloud provider that will be used.
Supported providers: `aws`, `gcp`, and `azure` |
| `bucket` _string_ | (Required) Storage Bucket Name. |
| `region` _string_ | (Optional) Region Name. |
| `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. |
Expand Down Expand Up @@ -82,7 +83,8 @@ _Appears in:_

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


Expand Down Expand Up @@ -149,6 +151,7 @@ _Appears in:_
| --- | --- |
| `startTime` _string_ | (Optional) Test Start time. |
| `parallelism` _integer_ | (Optional) Number of pods running at the same time. Defaults to `1` (Minimum `1`) |
| `simulationsFormat` _string_ | (Optional) Gatling simulation format, supports `bundle` and `gradle`. Defaults to `bundle` |
| `simulationsDirectoryPath` _string_ | (Optional) Gatling Resources directory path where simulation files are stored. Defaults to `/opt/gatling/user-files/simulations` |
| `resourcesDirectoryPath` _string_ | (Optional) Gatling Simulation directory path where resources are stored. Defaults to `/opt/gatling/user-files/resources` |
| `resultsDirectoryPath` _string_ | (Optional) Gatling Results directory path where results are stored. Defaults to `/opt/gatling/results` |
Expand Down
77 changes: 76 additions & 1 deletion docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ As described in Configuration Overview, there are 2 things that you need to cons

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

For `Gatling load testing related files`, you have 3 options:
For `Gatling load testing related files`, you have 4 options:

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

Expand Down Expand Up @@ -179,6 +180,80 @@ spec:
...omit...
```

### Create Custom Gatling Image with Gradle Gatling plugin

Example project can be found [here](https://github.com/gatling/gatling-gradle-plugin-demo-kotlin).

Let's start with cloning the repo:
```bash
git clone [email protected]:gatling/gatling-gradle-plugin-demo-kotlin.git
cd gatling-gradle-plugin-demo-kotlin
```

In order to run this example on Gatling Operator, we have to build a custom docker image with both gradle and gatling baked in.

Let's create a `Dockerfile` in the root directory of the `gatling-gradle-plugin-demo-kotlin` project:
```bash
FROM azul/zulu-openjdk:21-latest

# dependency versions
ENV GATLING_VERSION 3.10.5
ENV GRADLE_VERSION 8.7

# install gatling
RUN mkdir /opt/gatling && \
apt-get update && apt-get upgrade -y && apt-get install -y wget unzip && \
mkdir -p /tmp/downloads && \
wget -q -O /tmp/downloads/gatling-$GATLING_VERSION.zip \
https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/$GATLING_VERSION/gatling-charts-highcharts-bundle-$GATLING_VERSION-bundle.zip && \
mkdir -p /tmp/archive && cd /tmp/archive && \
unzip /tmp/downloads/gatling-$GATLING_VERSION.zip && \
mv /tmp/archive/gatling-charts-highcharts-bundle-$GATLING_VERSION/* /opt/gatling/ && \
rm -rf /opt/gatling/user-files/simulations/computerdatabase /tmp/*

# install gradle
RUN mkdir /opt/gradle && \
wget -q -O /tmp/gradle-$GRADLE_VERSION.zip https://services.gradle.org/distributions/gradle-$GRADLE_VERSION-bin.zip && \
unzip -d /opt/gradle /tmp/gradle-$GRADLE_VERSION.zip && \
rm -rf /tmp/*

# change context to gatling directory
WORKDIR /opt/gatling

# copy gradle files to gatling directory
COPY . .

# set environment variables
ENV PATH /opt/gatling/bin:/opt/gradle/gradle-$GRADLE_VERSION/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV GATLING_HOME /opt/gatling

ENTRYPOINT ["gatling.sh"]
```

Now we have to build the custom image:
```bash
# Build Docker image
docker build -t <your-registry>/gatling:<tag> .
# Push the image to your container registry
docker push <your-registry>/gatling:<tag>
```

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.

```yaml
apiVersion: gatling-operator.tech.zozo.com/v1alpha1
kind: Gatling
metadata:
name: gatling-gradle
spec:
podSpec:
serviceAccountName: "gatling-operator-worker"
gatlingImage: <your-registry>/gatling:<tag>
testScenarioSpec:
simulationsFormat: gradle
...omit...
```

### Add Gatling Load Testing Files in Gatling CR

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.
Expand Down
15 changes: 12 additions & 3 deletions pkg/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ done
}

func GetGatlingRunnerCommand(
simulationsDirectoryPath string, tempSimulationsDirectoryPath string, resourcesDirectoryPath string,
resultsDirectoryPath string, startTime string, simulationClass string, generateLocalReport bool) string {
simulationsFormat string, simulationsDirectoryPath string, tempSimulationsDirectoryPath string,
resourcesDirectoryPath string, resultsDirectoryPath string, startTime string, simulationClass string,
generateLocalReport bool) string {

template := `
SIMULATIONS_FORMAT=%s
SIMULATIONS_DIR_PATH=%s
TEMP_SIMULATIONS_DIR_PATH=%s
RESOURCES_DIR_PATH=%s
RESULTS_DIR_PATH=%s
START_TIME="%s"
SIMULATION_CLASS=%s
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/COMPLETED"
if [ -z "${START_TIME}" ]; then
START_TIME=$(date +"%%Y-%%m-%%d %%H:%%M:%%S" --utc)
Expand All @@ -66,7 +69,12 @@ fi
if [ ! -d ${RESULTS_DIR_PATH} ]; then
mkdir -p ${RESULTS_DIR_PATH}
fi
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s %s -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} %s %s
if [ ${SIMULATIONS_FORMAT} = "bundle" ]; then
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s ${SIMULATION_CLASS} -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} %s %s
elif [ ${SIMULATIONS_FORMAT} = "gradle" ]; then
gradle -Dgatling.core.directory.results=${RESULTS_DIR_PATH} gatlingRun-${SIMULATION_CLASS}
fi
GATLING_EXIT_STATUS=$?
if [ $GATLING_EXIT_STATUS -ne 0 ]; then
Expand All @@ -84,6 +92,7 @@ exit $GATLING_EXIT_STATUS
runModeOptionLocal := "-rm local"

return fmt.Sprintf(template,
simulationsFormat,
simulationsDirectoryPath,
tempSimulationsDirectoryPath,
resourcesDirectoryPath,
Expand Down
66 changes: 53 additions & 13 deletions pkg/commands/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ done

var _ = Describe("GetGatlingRunnerCommand", func() {
var (
simulationsFormat string
simulationsDirectoryPath string
tempSimulationsDirectoryPath string
resourcesDirectoryPath string
Expand All @@ -42,6 +43,7 @@ var _ = Describe("GetGatlingRunnerCommand", func() {
)

BeforeEach(func() {
simulationsFormat = "bundle"
simulationsDirectoryPath = "testSimulationDirectoryPath"
tempSimulationsDirectoryPath = "testTempSimulationsDirectoryPath"
resourcesDirectoryPath = "testResourcesDirectoryPath"
Expand All @@ -53,11 +55,13 @@ var _ = Describe("GetGatlingRunnerCommand", func() {
It("GetCommandsWithLocalReport", func() {
generateLocalReport = true
expectedValue = `
SIMULATIONS_FORMAT=bundle
SIMULATIONS_DIR_PATH=testSimulationDirectoryPath
TEMP_SIMULATIONS_DIR_PATH=testTempSimulationsDirectoryPath
RESOURCES_DIR_PATH=testResourcesDirectoryPath
RESULTS_DIR_PATH=testResultsDirectoryPath
START_TIME="2021-09-10 08:45:31"
SIMULATION_CLASS=testSimulationClass
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/COMPLETED"
if [ -z "${START_TIME}" ]; then
START_TIME=$(date +"%Y-%m-%d %H:%M:%S" --utc)
Expand All @@ -83,25 +87,34 @@ fi
if [ ! -d ${RESULTS_DIR_PATH} ]; then
mkdir -p ${RESULTS_DIR_PATH}
fi
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s testSimulationClass -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH}
if [ $? -ne 0 ]; then
if [ ${SIMULATIONS_FORMAT} = "bundle" ]; then
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s ${SIMULATION_CLASS} -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} -rm local
elif [ ${SIMULATIONS_FORMAT} = "gradle" ]; then
gradle -Dgatling.core.directory.results=${RESULTS_DIR_PATH} gatlingRun-${SIMULATION_CLASS}
fi
GATLING_EXIT_STATUS=$?
if [ $GATLING_EXIT_STATUS -ne 0 ]; then
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/FAILED"
echo "gatling.sh has failed!" 1>&2
fi
touch ${RUN_STATUS_FILE}
exit $GATLING_EXIT_STATUS
`
Expect(GetGatlingRunnerCommand(simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
Expect(GetGatlingRunnerCommand(simulationsFormat, simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
})

It("GetCommandWithoutLocalReport", func() {
generateLocalReport = false
expectedValue = `
SIMULATIONS_FORMAT=bundle
SIMULATIONS_DIR_PATH=testSimulationDirectoryPath
TEMP_SIMULATIONS_DIR_PATH=testTempSimulationsDirectoryPath
RESOURCES_DIR_PATH=testResourcesDirectoryPath
RESULTS_DIR_PATH=testResultsDirectoryPath
START_TIME="2021-09-10 08:45:31"
SIMULATION_CLASS=testSimulationClass
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/COMPLETED"
if [ -z "${START_TIME}" ]; then
START_TIME=$(date +"%Y-%m-%d %H:%M:%S" --utc)
Expand All @@ -127,15 +140,22 @@ fi
if [ ! -d ${RESULTS_DIR_PATH} ]; then
mkdir -p ${RESULTS_DIR_PATH}
fi
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s testSimulationClass -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} -nr
if [ $? -ne 0 ]; then
if [ ${SIMULATIONS_FORMAT} = "bundle" ]; then
gatling.sh -sf ${SIMULATIONS_DIR_PATH} -s ${SIMULATION_CLASS} -rsf ${RESOURCES_DIR_PATH} -rf ${RESULTS_DIR_PATH} -nr -rm local
elif [ ${SIMULATIONS_FORMAT} = "gradle" ]; then
gradle -Dgatling.core.directory.results=${RESULTS_DIR_PATH} gatlingRun-${SIMULATION_CLASS}
fi
GATLING_EXIT_STATUS=$?
if [ $GATLING_EXIT_STATUS -ne 0 ]; then
RUN_STATUS_FILE="${RESULTS_DIR_PATH}/FAILED"
echo "gatling.sh has failed!" 1>&2
fi
touch ${RUN_STATUS_FILE}
exit $GATLING_EXIT_STATUS
`
Expect(GetGatlingRunnerCommand(simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
Expect(GetGatlingRunnerCommand(simulationsFormat, simulationsDirectoryPath, tempSimulationsDirectoryPath, resourcesDirectoryPath, resultsDirectoryPath, startTime, simulationClass, generateLocalReport)).To(Equal(expectedValue))
})
})

Expand All @@ -160,9 +180,19 @@ var _ = Describe("GetGatlingTransferResultCommand", func() {
expectedValue = `
RESULTS_DIR_PATH=testResultsDirectoryPath
rclone config create s3 s3 env_auth=true region ap-northeast-1
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
do
rclone copyto ${source} --s3-no-check-bucket --s3-env-auth testStoragePath/${HOSTNAME}.log
while true; do
if [ -f "${RESULTS_DIR_PATH}/FAILED" ]; then
echo "Skip transfering gatling results"
break
fi
if [ -f "${RESULTS_DIR_PATH}/COMPLETED" ]; then
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
do
rclone copyto ${source} --s3-no-check-bucket --s3-env-auth testStoragePath/${HOSTNAME}.log
done
break
fi
sleep 1;
done
`
})
Expand All @@ -178,10 +208,20 @@ done
RESULTS_DIR_PATH=testResultsDirectoryPath
# assumes gcs bucket using uniform bucket-level access control
rclone config create gs "google cloud storage" bucket_policy_only true --non-interactive
# assumes each pod only contain single gatling log file but use for loop to use find command result
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
do
rclone copyto ${source} testStoragePath/${HOSTNAME}.log
while true; do
if [ -f "${RESULTS_DIR_PATH}/FAILED" ]; then
echo "Skip transfering gatling results"
break
fi
if [ -f "${RESULTS_DIR_PATH}/COMPLETED" ]; then
# assumes each pod only contain single gatling log file but use for loop to use find command result
for source in $(find ${RESULTS_DIR_PATH} -type f -name *.log)
do
rclone copyto ${source} testStoragePath/${HOSTNAME}.log
done
break
fi
sleep 1;
done
`
})
Expand Down
13 changes: 13 additions & 0 deletions pkg/commands/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package commands

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestBucket(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Commands Suite")
}

0 comments on commit ffebe49

Please sign in to comment.