From 6e56904d71c41fa56d856a9a46699c27983419ff Mon Sep 17 00:00:00 2001 From: namkyu1999 Date: Thu, 8 Feb 2024 22:57:32 +0900 Subject: [PATCH] feat: add k6-loadgen Signed-off-by: namkyu1999 --- bin/experiment/experiment.go | 3 + chaoslib/litmus/k6-loadgen/lib/k6-loadgen.go | 163 ++++++++++++++++ experiments/load/k6-loadgen/README.md | 14 ++ .../load/k6-loadgen/experiment/k6-loadgen.go | 179 ++++++++++++++++++ experiments/load/k6-loadgen/test/test.yml | 61 ++++++ .../k6-loadgen/environment/environment.go | 40 ++++ pkg/load/k6-loadgen/types/types.go | 36 ++++ pkg/utils/common/configmaps.go | 92 +++++++++ 8 files changed, 588 insertions(+) create mode 100644 chaoslib/litmus/k6-loadgen/lib/k6-loadgen.go create mode 100644 experiments/load/k6-loadgen/README.md create mode 100644 experiments/load/k6-loadgen/experiment/k6-loadgen.go create mode 100644 experiments/load/k6-loadgen/test/test.yml create mode 100644 pkg/load/k6-loadgen/environment/environment.go create mode 100644 pkg/load/k6-loadgen/types/types.go create mode 100644 pkg/utils/common/configmaps.go diff --git a/bin/experiment/experiment.go b/bin/experiment/experiment.go index b99941dd3..1a0be860f 100755 --- a/bin/experiment/experiment.go +++ b/bin/experiment/experiment.go @@ -56,6 +56,7 @@ import ( ebsLossByTag "github.com/litmuschaos/litmus-go/experiments/kube-aws/ebs-loss-by-tag/experiment" ec2TerminateByID "github.com/litmuschaos/litmus-go/experiments/kube-aws/ec2-terminate-by-id/experiment" ec2TerminateByTag "github.com/litmuschaos/litmus-go/experiments/kube-aws/ec2-terminate-by-tag/experiment" + k6Loadgen "github.com/litmuschaos/litmus-go/experiments/load/k6-loadgen/experiment" springBootFaults "github.com/litmuschaos/litmus-go/experiments/spring-boot/spring-boot-faults/experiment" vmpoweroff "github.com/litmuschaos/litmus-go/experiments/vmware/vm-poweroff/experiment" @@ -184,6 +185,8 @@ func main() { gcpVMDiskLossByLabel.GCPVMDiskLossByLabel(clients) case "spring-boot-cpu-stress", "spring-boot-memory-stress", "spring-boot-exceptions", "spring-boot-app-kill", "spring-boot-faults", "spring-boot-latency": springBootFaults.Experiment(clients, *experimentName) + case "k6-loadgen": + k6Loadgen.Experiment(clients) default: log.Errorf("Unsupported -name %v, please provide the correct value of -name args", *experimentName) return diff --git a/chaoslib/litmus/k6-loadgen/lib/k6-loadgen.go b/chaoslib/litmus/k6-loadgen/lib/k6-loadgen.go new file mode 100644 index 000000000..b5b6288fa --- /dev/null +++ b/chaoslib/litmus/k6-loadgen/lib/k6-loadgen.go @@ -0,0 +1,163 @@ +package lib + +import ( + "context" + "fmt" + "path/filepath" + + "github.com/litmuschaos/litmus-go/pkg/cerrors" + clients "github.com/litmuschaos/litmus-go/pkg/clients" + "github.com/litmuschaos/litmus-go/pkg/events" + experimentTypes "github.com/litmuschaos/litmus-go/pkg/load/k6-loadgen/types" + "github.com/litmuschaos/litmus-go/pkg/log" + "github.com/litmuschaos/litmus-go/pkg/probe" + "github.com/litmuschaos/litmus-go/pkg/status" + "github.com/litmuschaos/litmus-go/pkg/types" + "github.com/litmuschaos/litmus-go/pkg/utils/common" + "github.com/litmuschaos/litmus-go/pkg/utils/stringutils" + "github.com/palantir/stacktrace" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func experimentExecution(experimentsDetails *experimentTypes.ExperimentDetails, clients clients.ClientSets, resultDetails *types.ResultDetails, eventsDetails *types.EventDetails, chaosDetails *types.ChaosDetails, configMapName string) error { + if experimentsDetails.EngineName != "" { + msg := "Injecting " + experimentsDetails.ExperimentName + " chaos" + types.SetEngineEventAttributes(eventsDetails, types.ChaosInject, msg, "Normal", chaosDetails) + events.GenerateEvents(eventsDetails, clients, chaosDetails, "ChaosEngine") + } + + // run the probes during chaos + if len(resultDetails.ProbeDetails) != 0 { + if err := probe.RunProbes(chaosDetails, clients, resultDetails, "DuringChaos", eventsDetails); err != nil { + return err + } + } + + runID := stringutils.GetRunID() + + // creating the helper pod to perform k6-loadgen chaos + if err := createHelperPod(experimentsDetails, clients, chaosDetails, runID, configMapName); err != nil { + return stacktrace.Propagate(err, "could not create helper pod") + } + + appLabel := fmt.Sprintf("app=%s-helper-%s", experimentsDetails.ExperimentName, runID) + + //checking the status of the helper pod, wait till the pod comes to running state else fail the experiment + log.Info("[Status]: Checking the status of the helper pod") + if err := status.CheckHelperStatus(experimentsDetails.ChaosNamespace, appLabel, experimentsDetails.Timeout, experimentsDetails.Delay, clients); err != nil { + common.DeleteAllHelperPodBasedOnJobCleanupPolicy(appLabel, chaosDetails, clients) + return stacktrace.Propagate(err, "could not check helper status") + } + + // Wait till the completion of the helper pod + // set an upper limit for the waiting time + log.Info("[Wait]: waiting till the completion of the helper pod") + podStatus, err := status.WaitForCompletion(experimentsDetails.ChaosNamespace, appLabel, clients, experimentsDetails.ChaosDuration+experimentsDetails.Timeout, common.GetContainerNames(chaosDetails)...) + if err != nil || podStatus == "Failed" { + common.DeleteAllHelperPodBasedOnJobCleanupPolicy(appLabel, chaosDetails, clients) + return common.HelperFailedError(err, appLabel, experimentsDetails.ChaosNamespace, true) + } + + //Deleting all the helper pod for container-kill chaos + log.Info("[Cleanup]: Deleting all the helper pods") + if err = common.DeleteAllPod(appLabel, experimentsDetails.ChaosNamespace, chaosDetails.Timeout, chaosDetails.Delay, clients); err != nil { + return stacktrace.Propagate(err, "could not delete helper pod(s)") + } + + // Delete the configmap + log.Info("[Cleanup]: Deleting the configmap") + if err = common.DeleteConfigMap(configMapName, experimentsDetails.ChaosNamespace, clients); err != nil { + return stacktrace.Propagate(err, "could not delete configmap") + } + + return nil +} + +// PrepareChaos contains the preparation steps before chaos injection +func PrepareChaos(experimentsDetails *experimentTypes.ExperimentDetails, clients clients.ClientSets, resultDetails *types.ResultDetails, eventsDetails *types.EventDetails, chaosDetails *types.ChaosDetails) error { + + // Waiting for the ramp time before chaos injection + if experimentsDetails.RampTime != 0 { + log.Infof("[Ramp]: Waiting for the %vs ramp time before injecting chaos", experimentsDetails.RampTime) + common.WaitForDuration(experimentsDetails.RampTime) + } + // Creating configmap for k6 script + configMapName, err := common.CreateConfigMapFromFile(experimentsDetails.ChaosNamespace, experimentsDetails.ScriptPath, clients, chaosDetails) + if err != nil { + return stacktrace.Propagate(err, "could not create script configmap") + } + + // Starting the k6-loadgen experiment + if err := experimentExecution(experimentsDetails, clients, resultDetails, eventsDetails, chaosDetails, configMapName); err != nil { + return stacktrace.Propagate(err, "could not execute chaos") + } + + // Waiting for the ramp time after chaos injection + if experimentsDetails.RampTime != 0 { + log.Infof("[Ramp]: Waiting for the %vs ramp time after injecting chaos", experimentsDetails.RampTime) + common.WaitForDuration(experimentsDetails.RampTime) + } + return nil +} + +// createHelperPod derive the attributes for helper pod and create the helper pod +func createHelperPod(experimentsDetails *experimentTypes.ExperimentDetails, clients clients.ClientSets, chaosDetails *types.ChaosDetails, runID, configMapName string) error { + const volumeName = "script-volume" + const mountPath = "/mnt" + _, configMapKey := filepath.Split(experimentsDetails.ScriptPath) + + helperPod := &corev1.Pod{ + ObjectMeta: v1.ObjectMeta{ + GenerateName: experimentsDetails.ExperimentName + "-helper-", + Namespace: experimentsDetails.ChaosNamespace, + Labels: common.GetHelperLabels(chaosDetails.Labels, runID, experimentsDetails.ExperimentName), + Annotations: chaosDetails.Annotations, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + ImagePullSecrets: chaosDetails.ImagePullSecrets, + ServiceAccountName: experimentsDetails.ChaosServiceAccount, + Containers: []corev1.Container{ + { + Name: experimentsDetails.ExperimentName, + Image: experimentsDetails.LIBImage, + ImagePullPolicy: corev1.PullPolicy(experimentsDetails.LIBImagePullPolicy), + Command: []string{ + "k6", + "run", + }, + Args: []string{ + mountPath + "/" + configMapKey, + "-q", + }, + Resources: chaosDetails.Resources, + VolumeMounts: []corev1.VolumeMount{ + { + Name: volumeName, + MountPath: mountPath, + }, + }, + }, + }, + Volumes: []corev1.Volume{ + { + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: configMapName, + }, + }, + }, + }, + }, + }, + } + + _, err := clients.KubeClient.CoreV1().Pods(experimentsDetails.ChaosNamespace).Create(context.Background(), helperPod, v1.CreateOptions{}) + if err != nil { + return cerrors.Error{ErrorCode: cerrors.ErrorTypeGeneric, Reason: fmt.Sprintf("unable to create helper pod: %s", err.Error())} + } + return nil +} diff --git a/experiments/load/k6-loadgen/README.md b/experiments/load/k6-loadgen/README.md new file mode 100644 index 000000000..c4323e45f --- /dev/null +++ b/experiments/load/k6-loadgen/README.md @@ -0,0 +1,14 @@ +## Experiment Metadata + + + + + + + + + + + + +
Name Description Documentation Link
k6 Load Generator 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 here Here
diff --git a/experiments/load/k6-loadgen/experiment/k6-loadgen.go b/experiments/load/k6-loadgen/experiment/k6-loadgen.go new file mode 100644 index 000000000..4036d5f79 --- /dev/null +++ b/experiments/load/k6-loadgen/experiment/k6-loadgen.go @@ -0,0 +1,179 @@ +package experiment + +import ( + "errors" + "os" + + "github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1" + litmusLIB "github.com/litmuschaos/litmus-go/chaoslib/litmus/k6-loadgen/lib" + clients "github.com/litmuschaos/litmus-go/pkg/clients" + "github.com/litmuschaos/litmus-go/pkg/events" + experimentEnv "github.com/litmuschaos/litmus-go/pkg/load/k6-loadgen/environment" + experimentTypes "github.com/litmuschaos/litmus-go/pkg/load/k6-loadgen/types" + "github.com/litmuschaos/litmus-go/pkg/log" + "github.com/litmuschaos/litmus-go/pkg/probe" + "github.com/litmuschaos/litmus-go/pkg/result" + "github.com/litmuschaos/litmus-go/pkg/status" + "github.com/litmuschaos/litmus-go/pkg/types" + "github.com/litmuschaos/litmus-go/pkg/utils/common" + "github.com/sirupsen/logrus" +) + +// Experiment contains steps to inject chaos +func Experiment(clients clients.ClientSets) { + + experimentsDetails := experimentTypes.ExperimentDetails{} + resultDetails := types.ResultDetails{} + eventsDetails := types.EventDetails{} + chaosDetails := types.ChaosDetails{} + + //Fetching all the ENV passed from the runner pod + log.Infof("[PreReq]: Getting the ENV for the %v experiment", os.Getenv("EXPERIMENT_NAME")) + experimentEnv.GetENV(&experimentsDetails) + + // Initialize the chaos attributes + types.InitialiseChaosVariables(&chaosDetails) + + // Initialize Chaos Result Parameters + types.SetResultAttributes(&resultDetails, chaosDetails) + + if experimentsDetails.EngineName != "" { + // Get values from chaosengine. Bail out upon error, as we haven't entered exp business logic yet + if err := types.GetValuesFromChaosEngine(&chaosDetails, clients, &resultDetails); err != nil { + log.Errorf("Unable to initialize the probes, err: %v", err) + return + } + } + + //Updating the chaos result in the beginning of experiment + log.Infof("[PreReq]: Updating the chaos result of %v experiment (SOT)", experimentsDetails.ExperimentName) + if err := result.ChaosResult(&chaosDetails, clients, &resultDetails, "SOT"); err != nil { + log.Errorf("Unable to Create the Chaos Result, err: %v", err) + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + + // Check if the script path exists + if _, err := os.Stat(experimentsDetails.ScriptPath); errors.Is(err, os.ErrNotExist) { + log.Errorf("The script path %v does not exist", experimentsDetails.ScriptPath) + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + + // Set the chaos result uid + result.SetResultUID(&resultDetails, clients, &chaosDetails) + + // generating the event in chaosresult to marked the verdict as awaited + msg := "experiment: " + experimentsDetails.ExperimentName + ", Result: Awaited" + types.SetResultEventAttributes(&eventsDetails, types.AwaitedVerdict, msg, "Normal", &resultDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosResult") + + //DISPLAY THE APP INFORMATION + log.InfoWithValues("[Info]: The application information is as follows", logrus.Fields{ + "Namespace": experimentsDetails.AppNS, + "Label": experimentsDetails.AppLabel, + "Chaos Duration": experimentsDetails.ChaosDuration, + }) + + // Calling AbortWatcher go routine, it will continuously watch for the abort signal and generate the required events and result + go common.AbortWatcher(experimentsDetails.ExperimentName, clients, &resultDetails, &chaosDetails, &eventsDetails) + + //PRE-CHAOS APPLICATION STATUS CHECK + if chaosDetails.DefaultHealthCheck { + log.Info("[Status]: Verify that the AUT (Application Under Test) is running (pre-chaos)") + if err := status.AUTStatusCheck(clients, &chaosDetails); err != nil { + log.Errorf("Application status check failed, err: %v", err) + types.SetEngineEventAttributes(&eventsDetails, types.PreChaosCheck, "AUT: Not Running", "Warning", &chaosDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine") + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + } + + if experimentsDetails.EngineName != "" { + // marking AUT as running, as we already checked the status of application under test + msg := "AUT: Running" + + // run the probes in the pre-chaos check + if len(resultDetails.ProbeDetails) != 0 { + + if err := probe.RunProbes(&chaosDetails, clients, &resultDetails, "PreChaos", &eventsDetails); err != nil { + log.Errorf("Probe Failed, err: %v", err) + msg := "AUT: Running, Probes: Unsuccessful" + types.SetEngineEventAttributes(&eventsDetails, types.PreChaosCheck, msg, "Warning", &chaosDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine") + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + msg = "AUT: Running, Probes: Successful" + } + // generating the events for the pre-chaos check + types.SetEngineEventAttributes(&eventsDetails, types.PreChaosCheck, msg, "Normal", &chaosDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine") + } + + chaosDetails.Phase = types.ChaosInjectPhase + if err := litmusLIB.PrepareChaos(&experimentsDetails, clients, &resultDetails, &eventsDetails, &chaosDetails); err != nil { + log.Errorf("Chaos injection failed, err: %v", err) + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + + log.Infof("[Confirmation]: %v chaos has been injected successfully", experimentsDetails.ExperimentName) + resultDetails.Verdict = v1alpha1.ResultVerdictPassed + chaosDetails.Phase = types.PostChaosPhase + + //POST-CHAOS APPLICATION STATUS CHECK + if chaosDetails.DefaultHealthCheck { + log.Info("[Status]: Verify that the AUT (Application Under Test) is running (post-chaos)") + if err := status.AUTStatusCheck(clients, &chaosDetails); err != nil { + log.Errorf("Application status check failed, err: %v", err) + types.SetEngineEventAttributes(&eventsDetails, types.PostChaosCheck, "AUT: Not Running", "Warning", &chaosDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine") + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + } + + if experimentsDetails.EngineName != "" { + // marking AUT as running, as we already checked the status of application under test + msg := "AUT: Running" + + // run the probes in the post-chaos check + if len(resultDetails.ProbeDetails) != 0 { + if err := probe.RunProbes(&chaosDetails, clients, &resultDetails, "PostChaos", &eventsDetails); err != nil { + log.Errorf("Probes Failed, err: %v", err) + msg := "AUT: Running, Probes: Unsuccessful" + types.SetEngineEventAttributes(&eventsDetails, types.PostChaosCheck, msg, "Warning", &chaosDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine") + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + msg = "AUT: Running, Probes: Successful" + } + + // generating post chaos event + types.SetEngineEventAttributes(&eventsDetails, types.PostChaosCheck, msg, "Normal", &chaosDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine") + } + + //Updating the chaosResult in the end of experiment + log.Infof("[The End]: Updating the chaos result of %v experiment (EOT)", experimentsDetails.ExperimentName) + if err := result.ChaosResult(&chaosDetails, clients, &resultDetails, "EOT"); err != nil { + log.Errorf("Unable to Update the Chaos Result, err: %v", err) + result.RecordAfterFailure(&chaosDetails, &resultDetails, err, clients, &eventsDetails) + return + } + + // generating the event in chaosresult to mark the verdict as pass/fail + msg = "experiment: " + experimentsDetails.ExperimentName + ", Result: " + string(resultDetails.Verdict) + reason, eventType := types.GetChaosResultVerdictEvent(resultDetails.Verdict) + types.SetResultEventAttributes(&eventsDetails, reason, msg, eventType, &resultDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosResult") + + if experimentsDetails.EngineName != "" { + msg := experimentsDetails.ExperimentName + " experiment has been " + string(resultDetails.Verdict) + "ed" + types.SetEngineEventAttributes(&eventsDetails, types.Summary, msg, "Normal", &chaosDetails) + events.GenerateEvents(&eventsDetails, clients, &chaosDetails, "ChaosEngine") + } +} diff --git a/experiments/load/k6-loadgen/test/test.yml b/experiments/load/k6-loadgen/test/test.yml new file mode 100644 index 000000000..3abc7820e --- /dev/null +++ b/experiments/load/k6-loadgen/test/test.yml @@ -0,0 +1,61 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: litmus-experiment +spec: + replicas: 1 + selector: + matchLabels: + app: litmus-experiment + template: + metadata: + labels: + app: litmus-experiment + spec: + serviceAccountName: k6-loadgen-sa + containers: + - name: gotest + image: busybox + command: + - sleep + - "3600" + env: + # provide application namespace + - name: APP_NAMESPACE + value: '' + + # provide application labels + - name: APP_LABEL + value: '' + + # provide application kind + - name: APP_KIND + value: '' + + - name: TOTAL_CHAOS_DURATION + value: '' + + # provide auxiliary application details - namespace and labels of the applications + # sample input is - "ns1:app=percona,ns2:name=nginx" + - name: AUXILIARY_APPINFO + value: '' + + ## Period to wait before injection of chaos in sec + - name: RAMP_TIME + value: '' + + # provide the chaos namespace + - name: CHAOS_NAMESPACE + value: '' + + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + + - name: CHAOS_SERVICE_ACCOUNT + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName + diff --git a/pkg/load/k6-loadgen/environment/environment.go b/pkg/load/k6-loadgen/environment/environment.go new file mode 100644 index 000000000..bf6db3ab9 --- /dev/null +++ b/pkg/load/k6-loadgen/environment/environment.go @@ -0,0 +1,40 @@ +package environment + +import ( + "strconv" + + clientTypes "k8s.io/apimachinery/pkg/types" + + experimentTypes "github.com/litmuschaos/litmus-go/pkg/load/k6-loadgen/types" + "github.com/litmuschaos/litmus-go/pkg/types" +) + +// STEPS TO GETENV OF YOUR CHOICE HERE +// ADDED FOR FEW MANDATORY FIELD + +// GetENV fetches all the env variables from the runner pod +func GetENV(experimentDetails *experimentTypes.ExperimentDetails) { + experimentDetails.ExperimentName = types.Getenv("EXPERIMENT_NAME", "") + experimentDetails.ChaosNamespace = types.Getenv("CHAOS_NAMESPACE", "litmus") + experimentDetails.EngineName = types.Getenv("CHAOSENGINE", "") + experimentDetails.ChaosDuration, _ = strconv.Atoi(types.Getenv("TOTAL_CHAOS_DURATION", "30")) + experimentDetails.ChaosInterval, _ = strconv.Atoi(types.Getenv("CHAOS_INTERVAL", "10")) + experimentDetails.RampTime, _ = strconv.Atoi(types.Getenv("RAMP_TIME", "0")) + experimentDetails.AppNS = types.Getenv("APP_NAMESPACE", "") + experimentDetails.AppLabel = types.Getenv("APP_LABEL", "") + experimentDetails.AppKind = types.Getenv("APP_KIND", "") + experimentDetails.AuxiliaryAppInfo = types.Getenv("AUXILIARY_APPINFO", "") + experimentDetails.ChaosUID = clientTypes.UID(types.Getenv("CHAOS_UID", "")) + experimentDetails.InstanceID = types.Getenv("INSTANCE_ID", "") + experimentDetails.ChaosPodName = types.Getenv("POD_NAME", "") + experimentDetails.Delay, _ = strconv.Atoi(types.Getenv("STATUS_CHECK_DELAY", "2")) + experimentDetails.Timeout, _ = strconv.Atoi(types.Getenv("STATUS_CHECK_TIMEOUT", "180")) + experimentDetails.TargetContainer = types.Getenv("TARGET_CONTAINER", "") + experimentDetails.TargetPods = types.Getenv("TARGET_PODS", "") + experimentDetails.PodsAffectedPerc, _ = strconv.Atoi(types.Getenv("PODS_AFFECTED_PERC", "0")) + experimentDetails.LIBImagePullPolicy = types.Getenv("LIB_IMAGE_PULL_POLICY", "Always") + experimentDetails.LIBImage = types.Getenv("LIB_IMAGE", "ghcr.io/grafana/k6-operator:latest-runner") + experimentDetails.SetHelperData = types.Getenv("SET_HELPER_DATA", "true") + experimentDetails.ChaosServiceAccount = types.Getenv("CHAOS_SERVICE_ACCOUNT", "") + experimentDetails.ScriptPath = types.Getenv("SCRIPT_PATH", "/tmp/script.js") +} diff --git a/pkg/load/k6-loadgen/types/types.go b/pkg/load/k6-loadgen/types/types.go new file mode 100644 index 000000000..9fd5f13bd --- /dev/null +++ b/pkg/load/k6-loadgen/types/types.go @@ -0,0 +1,36 @@ +package types + +import ( + clientTypes "k8s.io/apimachinery/pkg/types" +) + +// ADD THE ATTRIBUTES OF YOUR CHOICE HERE +// FEW MANDATORY ATTRIBUTES ARE ADDED BY DEFAULT + +// ExperimentDetails is for collecting all the experiment-related details +type ExperimentDetails struct { + ExperimentName string + EngineName string + ChaosDuration int + ChaosInterval int + RampTime int + AppNS string + AppLabel string + AppKind string + AuxiliaryAppInfo string + ChaosUID clientTypes.UID + InstanceID string + ChaosNamespace string + ChaosPodName string + Timeout int + Delay int + TargetContainer string + PodsAffectedPerc int + TargetPods string + LIBImagePullPolicy string + LIBImage string + SetHelperData string + ChaosServiceAccount string + IsTargetContainerProvided bool + ScriptPath string +} diff --git a/pkg/utils/common/configmaps.go b/pkg/utils/common/configmaps.go new file mode 100644 index 000000000..435ce302a --- /dev/null +++ b/pkg/utils/common/configmaps.go @@ -0,0 +1,92 @@ +package common + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "unicode/utf8" + + "github.com/litmuschaos/litmus-go/pkg/cerrors" + "github.com/litmuschaos/litmus-go/pkg/clients" + "github.com/litmuschaos/litmus-go/pkg/types" + "github.com/palantir/stacktrace" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation" +) + +// DeleteConfigMap deletes the specified configmap +func DeleteConfigMap(configMapName, namespace string, clients clients.ClientSets) error { + if err := clients.KubeClient.CoreV1().ConfigMaps(namespace).Delete(context.Background(), configMapName, v1.DeleteOptions{}); err != nil { + return cerrors.Error{ErrorCode: cerrors.ErrorTypeGeneric, Target: fmt.Sprintf("{configMapName: %s, namespace: %s}", configMapName, namespace), Reason: fmt.Sprintf("failed to delete configmap: %s", err.Error())} + } + return nil +} + +// CreateConfigMapFromFile creates the configmap +func CreateConfigMapFromFile(namespace, filePath string, clients clients.ClientSets, chaosDetails *types.ChaosDetails) (string, error) { + scriptConfig := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + GenerateName: "litmus-go-cm-", + Namespace: namespace, + }, + Data: map[string]string{}, + } + + if err := addKeyFromFileToConfigMap(scriptConfig, filePath); err != nil { + return "", stacktrace.Propagate(err, "could not add script to configmap") + } + + cm, err := clients.KubeClient.CoreV1().ConfigMaps(namespace).Create(context.Background(), scriptConfig, v1.CreateOptions{}) + if err != nil { + return "", cerrors.Error{ErrorCode: cerrors.ErrorTypeGeneric, Reason: fmt.Sprintf("unable to create script configmap: %s", err.Error())} + } + + return cm.Name, nil +} + +func addKeyFromFileToConfigMap(configMap *corev1.ConfigMap, filePath string) error { + // Using filename as key + _, filename := filepath.Split(filePath) + + data, err := os.ReadFile(filePath) + if err != nil { + return err + } + if utf8.Valid(data) { + return addKeyFromLiteralToConfigMap(configMap, filename, string(data)) + } + err = validateNewConfigMap(configMap, filename) + if err != nil { + return err + } + configMap.BinaryData[filename] = data + + return nil +} + +func addKeyFromLiteralToConfigMap(configMap *corev1.ConfigMap, keyName, data string) error { + err := validateNewConfigMap(configMap, keyName) + if err != nil { + return err + } + configMap.Data[keyName] = data + + return nil +} + +func validateNewConfigMap(configMap *corev1.ConfigMap, keyName string) error { + if errs := validation.IsConfigMapKey(keyName); len(errs) > 0 { + return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ",")) + } + if _, exists := configMap.Data[keyName]; exists { + return fmt.Errorf("cannot add key %q, another key by that name already exists in Data for ConfigMap %q", keyName, configMap.Name) + } + if _, exists := configMap.BinaryData[keyName]; exists { + return fmt.Errorf("cannot add key %q, another key by that name already exists in BinaryData for ConfigMap %q", keyName, configMap.Name) + } + + return nil +}