Skip to content

Commit eef3b40

Browse files
authored
feat: Add a k6-loadgen chaos fault (#687)
* feat: add k6-loadgen Signed-off-by: namkyu1999 <[email protected]>
1 parent 96f6571 commit eef3b40

File tree

10 files changed

+483
-1
lines changed

10 files changed

+483
-1
lines changed

bin/experiment/experiment.go

+3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import (
5656
ebsLossByTag "github.com/litmuschaos/litmus-go/experiments/kube-aws/ebs-loss-by-tag/experiment"
5757
ec2TerminateByID "github.com/litmuschaos/litmus-go/experiments/kube-aws/ec2-terminate-by-id/experiment"
5858
ec2TerminateByTag "github.com/litmuschaos/litmus-go/experiments/kube-aws/ec2-terminate-by-tag/experiment"
59+
k6Loadgen "github.com/litmuschaos/litmus-go/experiments/load/k6-loadgen/experiment"
5960
springBootFaults "github.com/litmuschaos/litmus-go/experiments/spring-boot/spring-boot-faults/experiment"
6061
vmpoweroff "github.com/litmuschaos/litmus-go/experiments/vmware/vm-poweroff/experiment"
6162

@@ -184,6 +185,8 @@ func main() {
184185
gcpVMDiskLossByLabel.GCPVMDiskLossByLabel(clients)
185186
case "spring-boot-cpu-stress", "spring-boot-memory-stress", "spring-boot-exceptions", "spring-boot-app-kill", "spring-boot-faults", "spring-boot-latency":
186187
springBootFaults.Experiment(clients, *experimentName)
188+
case "k6-loadgen":
189+
k6Loadgen.Experiment(clients)
187190
default:
188191
log.Errorf("Unsupported -name %v, please provide the correct value of -name args", *experimentName)
189192
return

build/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ COPY --from=dep /usr/lib/sudo /usr/lib/sudo
3636
COPY --from=dep /sbin/tc /sbin/
3737
COPY --from=dep /sbin/iptables /sbin/
3838

39-
#Copying Necessary Files
39+
# Copying Necessary Files
4040
COPY ./pkg/cloud/aws/common/ssm-docs/LitmusChaos-AWS-SSM-Docs.yml .
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package lib
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
8+
"github.com/litmuschaos/litmus-go/pkg/cerrors"
9+
clients "github.com/litmuschaos/litmus-go/pkg/clients"
10+
"github.com/litmuschaos/litmus-go/pkg/events"
11+
experimentTypes "github.com/litmuschaos/litmus-go/pkg/load/k6-loadgen/types"
12+
"github.com/litmuschaos/litmus-go/pkg/log"
13+
"github.com/litmuschaos/litmus-go/pkg/probe"
14+
"github.com/litmuschaos/litmus-go/pkg/status"
15+
"github.com/litmuschaos/litmus-go/pkg/types"
16+
"github.com/litmuschaos/litmus-go/pkg/utils/common"
17+
"github.com/litmuschaos/litmus-go/pkg/utils/stringutils"
18+
"github.com/palantir/stacktrace"
19+
corev1 "k8s.io/api/core/v1"
20+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
func experimentExecution(experimentsDetails *experimentTypes.ExperimentDetails, clients clients.ClientSets, resultDetails *types.ResultDetails, eventsDetails *types.EventDetails, chaosDetails *types.ChaosDetails) error {
24+
if experimentsDetails.EngineName != "" {
25+
msg := "Injecting " + experimentsDetails.ExperimentName + " chaos"
26+
types.SetEngineEventAttributes(eventsDetails, types.ChaosInject, msg, "Normal", chaosDetails)
27+
events.GenerateEvents(eventsDetails, clients, chaosDetails, "ChaosEngine")
28+
}
29+
// run the probes during chaos
30+
if len(resultDetails.ProbeDetails) != 0 {
31+
if err := probe.RunProbes(chaosDetails, clients, resultDetails, "DuringChaos", eventsDetails); err != nil {
32+
return err
33+
}
34+
}
35+
36+
runID := stringutils.GetRunID()
37+
38+
// creating the helper pod to perform k6-loadgen chaos
39+
if err := createHelperPod(experimentsDetails, clients, chaosDetails, runID); err != nil {
40+
return stacktrace.Propagate(err, "could not create helper pod")
41+
}
42+
43+
appLabel := fmt.Sprintf("app=%s-helper-%s", experimentsDetails.ExperimentName, runID)
44+
45+
//checking the status of the helper pod, wait till the pod comes to running state else fail the experiment
46+
log.Info("[Status]: Checking the status of the helper pod")
47+
if err := status.CheckHelperStatus(experimentsDetails.ChaosNamespace, appLabel, experimentsDetails.Timeout, experimentsDetails.Delay, clients); err != nil {
48+
common.DeleteAllHelperPodBasedOnJobCleanupPolicy(appLabel, chaosDetails, clients)
49+
return stacktrace.Propagate(err, "could not check helper status")
50+
}
51+
52+
// Wait till the completion of the helper pod
53+
// set an upper limit for the waiting time
54+
log.Info("[Wait]: Waiting till the completion of the helper pod")
55+
podStatus, err := status.WaitForCompletion(experimentsDetails.ChaosNamespace, appLabel, clients, experimentsDetails.ChaosDuration+experimentsDetails.Timeout, common.GetContainerNames(chaosDetails)...)
56+
if err != nil || podStatus == "Failed" {
57+
common.DeleteAllHelperPodBasedOnJobCleanupPolicy(appLabel, chaosDetails, clients)
58+
return common.HelperFailedError(err, appLabel, experimentsDetails.ChaosNamespace, true)
59+
}
60+
61+
//Deleting all the helper pod for container-kill chaos
62+
log.Info("[Cleanup]: Deleting all the helper pods")
63+
if err = common.DeleteAllPod(appLabel, experimentsDetails.ChaosNamespace, chaosDetails.Timeout, chaosDetails.Delay, clients); err != nil {
64+
return stacktrace.Propagate(err, "could not delete helper pod(s)")
65+
}
66+
67+
return nil
68+
}
69+
70+
// PrepareChaos contains the preparation steps before chaos injection
71+
func PrepareChaos(experimentsDetails *experimentTypes.ExperimentDetails, clients clients.ClientSets, resultDetails *types.ResultDetails, eventsDetails *types.EventDetails, chaosDetails *types.ChaosDetails) error {
72+
// Waiting for the ramp time before chaos injection
73+
if experimentsDetails.RampTime != 0 {
74+
log.Infof("[Ramp]: Waiting for the %vs ramp time before injecting chaos", experimentsDetails.RampTime)
75+
common.WaitForDuration(experimentsDetails.RampTime)
76+
}
77+
78+
// Starting the k6-loadgen experiment
79+
if err := experimentExecution(experimentsDetails, clients, resultDetails, eventsDetails, chaosDetails); err != nil {
80+
return stacktrace.Propagate(err, "could not execute chaos")
81+
}
82+
83+
// Waiting for the ramp time after chaos injection
84+
if experimentsDetails.RampTime != 0 {
85+
log.Infof("[Ramp]: Waiting for the %vs ramp time after injecting chaos", experimentsDetails.RampTime)
86+
common.WaitForDuration(experimentsDetails.RampTime)
87+
}
88+
return nil
89+
}
90+
91+
// createHelperPod derive the attributes for helper pod and create the helper pod
92+
func createHelperPod(experimentsDetails *experimentTypes.ExperimentDetails, clients clients.ClientSets, chaosDetails *types.ChaosDetails, runID string) error {
93+
const volumeName = "script-volume"
94+
const mountPath = "/mnt"
95+
helperPod := &corev1.Pod{
96+
ObjectMeta: v1.ObjectMeta{
97+
GenerateName: experimentsDetails.ExperimentName + "-helper-",
98+
Namespace: experimentsDetails.ChaosNamespace,
99+
Labels: common.GetHelperLabels(chaosDetails.Labels, runID, experimentsDetails.ExperimentName),
100+
Annotations: chaosDetails.Annotations,
101+
},
102+
Spec: corev1.PodSpec{
103+
RestartPolicy: corev1.RestartPolicyNever,
104+
ImagePullSecrets: chaosDetails.ImagePullSecrets,
105+
Containers: []corev1.Container{
106+
{
107+
Name: experimentsDetails.ExperimentName,
108+
Image: experimentsDetails.LIBImage,
109+
ImagePullPolicy: corev1.PullPolicy(experimentsDetails.LIBImagePullPolicy),
110+
Command: []string{
111+
"k6",
112+
"run",
113+
},
114+
Args: []string{
115+
mountPath + "/" + experimentsDetails.ScriptSecretKey,
116+
"-q",
117+
"--duration",
118+
strconv.Itoa(experimentsDetails.ChaosDuration) + "s",
119+
},
120+
Resources: chaosDetails.Resources,
121+
VolumeMounts: []corev1.VolumeMount{
122+
{
123+
Name: volumeName,
124+
MountPath: "/mnt",
125+
},
126+
},
127+
},
128+
},
129+
Volumes: []corev1.Volume{
130+
{
131+
Name: volumeName,
132+
VolumeSource: corev1.VolumeSource{
133+
Secret: &corev1.SecretVolumeSource{
134+
SecretName: experimentsDetails.ScriptSecretName,
135+
},
136+
},
137+
},
138+
},
139+
},
140+
}
141+
142+
_, err := clients.KubeClient.CoreV1().Pods(experimentsDetails.ChaosNamespace).Create(context.Background(), helperPod, v1.CreateOptions{})
143+
if err != nil {
144+
return cerrors.Error{ErrorCode: cerrors.ErrorTypeGeneric, Reason: fmt.Sprintf("unable to create helper pod: %s", err.Error())}
145+
}
146+
return nil
147+
}

experiments/load/k6-loadgen/README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Experiment Metadata
2+
3+
<table>
4+
<tr>
5+
<th> Name </th>
6+
<th> Description </th>
7+
<th> Documentation Link </th>
8+
</tr>
9+
<tr>
10+
<td> k6 Load Generator </td>
11+
<td> k6 is an open-source load testing tool that makes performance testing easy and productive for engineering teams. You can easily run load testing through a single JS script. Learn how to use k6 <a href="https://grafana.com/docs/k6/latest/">here</a> </td>
12+
<td> <a href="https://litmuschaos.github.io/litmus/experiments/categories/load/k6-loadgen/"> Here </a> </td>
13+
</tr>
14+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package experiment
2+
3+
import (
4+
"os"
5+
6+
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
7+
litmusLIB "github.com/litmuschaos/litmus-go/chaoslib/litmus/k6-loadgen/lib"
8+
clients "github.com/litmuschaos/litmus-go/pkg/clients"
9+
"github.com/litmuschaos/litmus-go/pkg/events"
10+
experimentEnv "github.com/litmuschaos/litmus-go/pkg/load/k6-loadgen/environment"
11+
experimentTypes "github.com/litmuschaos/litmus-go/pkg/load/k6-loadgen/types"
12+
"github.com/litmuschaos/litmus-go/pkg/log"
13+
"github.com/litmuschaos/litmus-go/pkg/probe"
14+
"github.com/litmuschaos/litmus-go/pkg/result"
15+
"github.com/litmuschaos/litmus-go/pkg/status"
16+
"github.com/litmuschaos/litmus-go/pkg/types"
17+
"github.com/litmuschaos/litmus-go/pkg/utils/common"
18+
"github.com/sirupsen/logrus"
19+
)
20+
21+
// Experiment contains steps to inject chaos
22+
func Experiment(clients clients.ClientSets) {
23+
24+
experimentsDetails := experimentTypes.ExperimentDetails{}
25+
resultDetails := types.ResultDetails{}
26+
eventsDetails := types.EventDetails{}
27+
chaosDetails := types.ChaosDetails{}
28+
29+
//Fetching all the ENV passed from the runner pod
30+
log.Infof("[PreReq]: Getting the ENV for the %v experiment", os.Getenv("EXPERIMENT_NAME"))
31+
experimentEnv.GetENV(&experimentsDetails)
32+
33+
// Initialize the chaos attributes
34+
types.InitialiseChaosVariables(&chaosDetails)
35+
36+
// Initialize Chaos Result Parameters
37+
types.SetResultAttributes(&resultDetails, chaosDetails)
38+
39+
if experimentsDetails.EngineName != "" {
40+
// Get values from chaosengine. Bail out upon error, as we haven't entered exp business logic yet
41+
if err := types.GetValuesFromChaosEngine(&chaosDetails, clients, &resultDetails); err != nil {
42+
log.Errorf("Unable to initialize the probes, err: %v", err)
43+
return
44+
}
45+
}
46+
47+
//Updating the chaos result in the beginning of experiment
48+
log.Infof("[PreReq]: Updating the chaos result of %v experiment (SOT)", experimentsDetails.ExperimentName)
49+
if err := result.ChaosResult(&chaosDetails, clients, &resultDetails, "SOT"); err != nil {
50+
log.Errorf("Unable to Create the Chaos Result, err: %v", err)
51+
result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails)
52+
return
53+
}
54+
55+
// Set the chaos result uid
56+
result.SetResultUID(&resultDetails, clients, &chaosDetails)
57+
58+
// generating the event in chaosresult to marked the verdict as awaited
59+
msg := "experiment: " + experimentsDetails.ExperimentName + ", Result: Awaited"
60+
types.SetResultEventAttributes(&eventsDetails, types.AwaitedVerdict, msg, "Normal", &resultDetails)
61+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosResult")
62+
63+
//DISPLAY THE APP INFORMATION
64+
log.InfoWithValues("[Info]: The application information is as follows", logrus.Fields{
65+
"Namespace": experimentsDetails.AppNS,
66+
"Label": experimentsDetails.AppLabel,
67+
"Chaos Duration": experimentsDetails.ChaosDuration,
68+
})
69+
70+
// Calling AbortWatcher go routine, it will continuously watch for the abort signal and generate the required events and result
71+
go common.AbortWatcher(experimentsDetails.ExperimentName, clients, &resultDetails, &chaosDetails, &eventsDetails)
72+
73+
//PRE-CHAOS APPLICATION STATUS CHECK
74+
if chaosDetails.DefaultHealthCheck {
75+
log.Info("[Status]: Verify that the AUT (Application Under Test) is running (pre-chaos)")
76+
if err := status.AUTStatusCheck(clients, &chaosDetails); err != nil {
77+
log.Errorf("Application status check failed, err: %v", err)
78+
types.SetEngineEventAttributes(&eventsDetails, types.PreChaosCheck, "AUT: Not Running", "Warning", &chaosDetails)
79+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine")
80+
result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails)
81+
return
82+
}
83+
}
84+
85+
if experimentsDetails.EngineName != "" {
86+
// marking AUT as running, as we already checked the status of application under test
87+
msg := "AUT: Running"
88+
89+
// run the probes in the pre-chaos check
90+
if len(resultDetails.ProbeDetails) != 0 {
91+
92+
if err := probe.RunProbes(&chaosDetails, clients, &resultDetails, "PreChaos", &eventsDetails); err != nil {
93+
log.Errorf("Probe Failed, err: %v", err)
94+
msg := "AUT: Running, Probes: Unsuccessful"
95+
types.SetEngineEventAttributes(&eventsDetails, types.PreChaosCheck, msg, "Warning", &chaosDetails)
96+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine")
97+
result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails)
98+
return
99+
}
100+
msg = "AUT: Running, Probes: Successful"
101+
}
102+
// generating the events for the pre-chaos check
103+
types.SetEngineEventAttributes(&eventsDetails, types.PreChaosCheck, msg, "Normal", &chaosDetails)
104+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine")
105+
}
106+
chaosDetails.Phase = types.ChaosInjectPhase
107+
if err := litmusLIB.PrepareChaos(&experimentsDetails, clients, &resultDetails, &eventsDetails, &chaosDetails); err != nil {
108+
log.Errorf("Chaos injection failed, err: %v", err)
109+
result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails)
110+
return
111+
}
112+
log.Infof("[Confirmation]: %v chaos has been injected successfully", experimentsDetails.ExperimentName)
113+
resultDetails.Verdict = v1alpha1.ResultVerdictPassed
114+
chaosDetails.Phase = types.PostChaosPhase
115+
116+
//POST-CHAOS APPLICATION STATUS CHECK
117+
if chaosDetails.DefaultHealthCheck {
118+
log.Info("[Status]: Verify that the AUT (Application Under Test) is running (post-chaos)")
119+
if err := status.AUTStatusCheck(clients, &chaosDetails); err != nil {
120+
log.Errorf("Application status check failed, err: %v", err)
121+
types.SetEngineEventAttributes(&eventsDetails, types.PostChaosCheck, "AUT: Not Running", "Warning", &chaosDetails)
122+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine")
123+
result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails)
124+
return
125+
}
126+
}
127+
128+
if experimentsDetails.EngineName != "" {
129+
// marking AUT as running, as we already checked the status of application under test
130+
msg := "AUT: Running"
131+
132+
// run the probes in the post-chaos check
133+
if len(resultDetails.ProbeDetails) != 0 {
134+
if err := probe.RunProbes(&chaosDetails, clients, &resultDetails, "PostChaos", &eventsDetails); err != nil {
135+
log.Errorf("Probes Failed, err: %v", err)
136+
msg := "AUT: Running, Probes: Unsuccessful"
137+
types.SetEngineEventAttributes(&eventsDetails, types.PostChaosCheck, msg, "Warning", &chaosDetails)
138+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine")
139+
result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails)
140+
return
141+
}
142+
msg = "AUT: Running, Probes: Successful"
143+
}
144+
145+
// generating post chaos event
146+
types.SetEngineEventAttributes(&eventsDetails, types.PostChaosCheck, msg, "Normal", &chaosDetails)
147+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine")
148+
}
149+
150+
//Updating the chaosResult in the end of experiment
151+
log.Infof("[The End]: Updating the chaos result of %v experiment (EOT)", experimentsDetails.ExperimentName)
152+
if err := result.ChaosResult(&chaosDetails, clients, &resultDetails, "EOT"); err != nil {
153+
log.Errorf("Unable to Update the Chaos Result, err: %v", err)
154+
result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails)
155+
return
156+
}
157+
158+
// generating the event in chaosresult to mark the verdict as pass/fail
159+
msg = "experiment: " + experimentsDetails.ExperimentName + ", Result: " + string(resultDetails.Verdict)
160+
reason, eventType := types.GetChaosResultVerdictEvent(resultDetails.Verdict)
161+
types.SetResultEventAttributes(&eventsDetails, reason, msg, eventType, &resultDetails)
162+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosResult")
163+
164+
if experimentsDetails.EngineName != "" {
165+
msg := experimentsDetails.ExperimentName + " experiment has been " + string(resultDetails.Verdict) + "ed"
166+
types.SetEngineEventAttributes(&eventsDetails, types.Summary, msg, "Normal", &chaosDetails)
167+
events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine")
168+
}
169+
}

experiments/load/k6-loadgen/rbac.yaml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
apiVersion: v1
3+
kind: ServiceAccount
4+
metadata:
5+
name: k6-loadgen-sa
6+
namespace: default
7+
labels:
8+
name: k6-loadgen-sa
9+
---
10+
apiVersion: rbac.authorization.k8s.io/v1
11+
kind: Role
12+
metadata:
13+
name: k6-loadgen-sa
14+
namespace: default
15+
labels:
16+
name: k6-loadgen-sa
17+
rules:
18+
- apiGroups: ["","litmuschaos.io","batch","apps"]
19+
resources: ["pods","configmaps","jobs","pods/exec","pods/log","events","chaosengines","chaosexperiments","chaosresults"]
20+
verbs: ["create","list","get","patch","update","delete","deletecollection"]
21+
---
22+
apiVersion: rbac.authorization.k8s.io/v1
23+
kind: RoleBinding
24+
metadata:
25+
name: k6-loadgen-sa
26+
namespace: default
27+
labels:
28+
name: k6-loadgen-sa
29+
roleRef:
30+
apiGroup: rbac.authorization.k8s.io
31+
kind: Role
32+
name: k6-loadgen-sa
33+
subjects:
34+
- kind: ServiceAccount
35+
name: k6-loadgen-sa
36+
namespace: default
37+

0 commit comments

Comments
 (0)