Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: simulations format #114

Merged
merged 2 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"`
kkulak marked this conversation as resolved.
Show resolved Hide resolved

// (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")
}
Loading