Skip to content
This repository has been archived by the owner on Nov 9, 2022. It is now read-only.

Commit

Permalink
Merge pull request #2 from nicolevanderhoeven/main
Browse files Browse the repository at this point in the history
add xk6-chaos summary
  • Loading branch information
nicolevanderhoeven authored Nov 12, 2021
2 parents 09838a5 + 9506abb commit 16267d6
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 18 deletions.
8 changes: 6 additions & 2 deletions chaos.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package chaos

import (
"fmt"

_ "github.com/simskij/xk6-chaos/internal/experiments" // Register the experiments module as well
_ "github.com/simskij/xk6-chaos/internal/k8s" // Register the k8s module as well
"go.k6.io/k6/js/modules"
_ "github.com/simskij/xk6-chaos/internal/k8s" // Register the k8s module as well
)

const version = "v0.0.1"
const version = "v0.0.2"

func init() {
modules.Register("k6/x/chaos", &Chaos{
Version: version,
})
fmt.Println("Running k6io/xk6-chaos@$" + version)
}

// Chaos is the main export of the chaos engineering extension
Expand Down
40 changes: 25 additions & 15 deletions examples/test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import chaos from 'k6/x/chaos';
import { Pods } from 'k6/x/chaos/k8s';
import { Podkillers } from 'k6/x/chaos/experiments';
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.1/index.js';

export default function () {
console.log(`Running simskij/xk6-chaos@${chaos.version}.`);
const p = new Pods();
console.log(
`There are currently ${p.list().length} pods in the default namespace.`
);
killPod(p);
console.log(
`There are now ${p.list().length} pods in the default namespace.`
);
killPod();
}

function killPod(p) {
const victim = p.list()[0];
console.log(`Killing pod ${victim}`);
p.killByName('media', victim);
// The killPod function terminates a pod within a Kubernetes cluster according to specifications provided.
export function killPod() {

// Instantiate a new Podkiller object
const podkiller = new Podkillers();

// The line below terminates a random pod in the specified namespace.
// podkiller.killRandomPod('default');

// The line below kills a pod within the namespace whose name exactly matches "web-7d55cf8588-7bxpv".
// podkiller.killPod('default', 'web-7d55cf8588-7bxpv');

// The line below terminates a pod within the namespace whose name contains "web"
podkiller.killPodLike('default', 'web');

}

// The handleSummary function creates a summary of the chaos experiments after the standard k6 summary.
export function handleSummary(data) {
return {
'stdout': textSummary(data, { indent: ' ', enableColors: true}) + new Podkillers().generateSummary(),
};
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ go 1.15
require (
github.com/googleapis/gnostic v0.5.3 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/k6io/xk6 v0.4.1 // indirect
github.com/onsi/ginkgo v1.14.0
github.com/onsi/gomega v1.10.1
go.k6.io/k6 v0.32.0
go.uber.org/multierr v1.1.0
k8s.io/api v0.21.0
k8s.io/apimachinery v0.21.0
k8s.io/client-go v0.21.0
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da h1:UVU3a9pRUyLdnBtn60WjRl0s4SEyJc2ChCY56OAR6wI=
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.3.0/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
Expand Down Expand Up @@ -247,6 +249,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.1.1-0.20180222160526-d18983907793/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k6io/xk6 v0.4.1 h1:O0O1raTV6XrkD+WvLj/qXrstEG+wOpu3vpalzuJOGZI=
github.com/k6io/xk6 v0.4.1/go.mod h1:QS8YtgA0CTJId5eYwHE41n25y4cnhXbxHXTwWQC8Heg=
github.com/k6io/xk6-output-kafka v0.1.1/go.mod h1:FEnBddwavknsQ3/tGFW0CP9ABAVMyimlJdLgyT1BDPE=
github.com/k6io/xk6-output-kafka v0.1.2-0.20210510135110-a159d7c8c171/go.mod h1:fgsOfxm/erON/jKuOBL8/TceTnAZPQ1OS8A7cPhrso8=
github.com/k6io/xk6-output-kafka v0.2.0/go.mod h1:yoiecpPwfa3F/UVl8k2scJ0xkZqRYu6Ht1XqohXnlXA=
Expand Down Expand Up @@ -433,7 +437,9 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
Expand Down
32 changes: 32 additions & 0 deletions internal/experiments/experiments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package experiments

import (
"context"

"github.com/simskij/xk6-chaos/internal/experiments/podkillers"
"go.k6.io/k6/js/common"
"go.k6.io/k6/js/modules"
)


// Register the extension on module initialization, available to
// import from JS as "k6/x/chaos/experiments".
func init() {
modules.Register("k6/x/chaos/experiments", &Experiments{
// Podkiller: podkillers.New(),
})
// i++
}

// This exposes experiment metadata for use in displaying results.
type Experiments struct {
Podkiller *podkillers.Podkillers
Summary string
}

// XPodkillers serves as a constructor of the Podkillers js class
func (*Experiments) XPodkillers(ctx *context.Context) (interface{}, error) {
rt := common.GetRuntime(*ctx)
p := podkillers.New()
return common.Bind(rt, p, ctx), nil
}
117 changes: 117 additions & 0 deletions internal/experiments/podkillers/podkillers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package podkillers

import (
"context"
"fmt"
"time"

"github.com/simskij/xk6-chaos/internal/experiments/summary"
"github.com/simskij/xk6-chaos/internal/k8s/pods"
"github.com/simskij/xk6-chaos/pkg/k8s/client"
)

// This exposes podkiller metadata for use in displaying results.
type Podkillers struct {
Id int
ExperimentType string
NumOfPodsBefore int
NumOfPodsAfter int
Victims string
pod *pods.Pods
}

var ExperimentNumber = 1

// New creates a new podkiller
func New() *Podkillers {
experimentNum := ExperimentNumber
ExperimentNumber++
c, err := client.New()
p := pods.New(c)
if err != nil {
return nil
}
return &Podkillers{experimentNum, "Pod termination", 0, 0, "", p}
}

// AddResults saves the names of pods selected to be terminated.
func (p *Podkillers) AddResults(namespace string, victim string) {
p.GetNumOfPods(namespace)

sum := summary.GetSummary()
sum.AddResult(summary.PodkillerResult{
Victim: victim,
Timestamp: p.Timestamp(),
PodCount: summary.PodCount{
Before: p.NumOfPodsBefore,
After: p.NumOfPodsAfter,
},
})
}

// SetStartingPods saves the number of pods at the beginning of a test as the "before" state.
func (p *Podkillers) SetStartingPods(number int) {
p.NumOfPodsBefore = number
}

// GetStartingPods returns and saves the number of pods at the beginning of the test for reporting purposes.
func (p *Podkillers) GetStartingPods(namespace string) int {
var podsAlive, _ = p.pod.List(context.Background(), namespace)
p.NumOfPodsBefore = len(podsAlive)
return p.NumOfPodsBefore
}

// GetNumOfPods returns and saves the current number of pods.
func (p *Podkillers) GetNumOfPods(namespace string) int {
time.Sleep(5 * time.Second)
var podsAlive, _ = p.pod.List(context.Background(), namespace)
p.NumOfPodsAfter = len(podsAlive)
return p.NumOfPodsAfter
}

// KillPod terminates a k8s pod identified by name.
func (p *Podkillers) KillPod(namespace string, podName string) error {
p.GetStartingPods(namespace)
err := p.pod.KillByName(context.Background(), namespace, podName)
p.AddResults(namespace, podName)
return err
}

// KillPodLike terminates a k8s pod whose name contains string.
func (p *Podkillers) KillPodLike(namespace string, keyword string) error {
p.GetStartingPods(namespace)
podName, err := p.pod.KillByKeyword(context.Background(), namespace, keyword)
p.AddResults(namespace, podName)
return err
}

// KillRandomPod terminates a pod at random.
func (p *Podkillers) KillRandomPod(namespace string) error {
p.GetStartingPods(namespace)
podName, err := p.pod.KillRandom(context.Background(), namespace)
p.AddResults(namespace, podName)
return err
}

// Timestamp constructs the format of a timestamp for logging purposes
func (p *Podkillers) Timestamp() string {
dt := time.Now()
tsMsg := dt.Format("2006-Jan-02 15:04:05")
return tsMsg
}

func (p *Podkillers) GenerateSummary() string {
sum := summary.GetSummary()
output := "\n\nxk6-chaos\n===\n\nPODKILLERS:\n"
for i, result := range sum.Results {
output += fmt.Sprintf(
" Victim #%d: %s terminated at %s\n Pods before: %d; Pods 5s after termination: %d\n",
i,
result.Victim,
result.Timestamp,
result.PodCount.Before,
result.PodCount.After,
)
}
return output + "\n"
}
33 changes: 33 additions & 0 deletions internal/experiments/summary/summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package summary

var summary *Summary

func GetSummary() *Summary {
if summary == nil {
summary = &Summary{}
}
return summary
}

func (s *Summary) AddResult(result PodkillerResult) {
s.Results = append(s.Results, result)
}

func (s *Summary) GetResults() []PodkillerResult {
return s.Results
}

type Summary struct {
Results []PodkillerResult
}

type PodkillerResult struct {
Victim string
PodCount PodCount
Timestamp string
}

type PodCount struct {
Before int
After int
}
28 changes: 27 additions & 1 deletion internal/k8s/pods/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package pods

import (
"context"
"math/rand"
"strings"

coreV1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -40,10 +42,34 @@ func (pods *Pods) List(ctx context.Context, namespace string) ([]string, error)
func (pods *Pods) KillByName(ctx context.Context, namespace string, podName string) error {
podsInNamespace := pods.client.CoreV1().Pods(namespace)
err := podsInNamespace.Delete(ctx, podName, v1.DeleteOptions{})

return err
}

// KillByKeyword kills the first pod with a name that contains the specified keyword
func (pods *Pods) KillByKeyword(ctx context.Context, namespace string, podKeyword string) (string, error) {
var podsList, err = pods.List(ctx, namespace)
var podToCheck = ""
for i := 0; i < len(podsList); i++ {
podToCheck = podsList[i]
if strings.Contains(podToCheck, podKeyword) {
break
}
}
var podName = podToCheck
err = pods.KillByName(ctx, namespace, podName)
return podName, err
}

// KillRandom kills a random pod within a namespace
func (pods *Pods) KillRandom(ctx context.Context, namespace string) (string, error) {
var podsList, err = pods.List(ctx, namespace)
var randomNum = rand.Intn(len(podsList) - 1)
var randomPod = podsList[randomNum]

err = pods.KillByName(ctx, namespace, randomPod)
return randomPod, err
}

// Status of a pod with a specific name in a specific namespace
func (pods *Pods) Status(ctx context.Context, namespace string, podName string) (coreV1.PodStatus, error) {
pod, err := pods.client.CoreV1().Pods(namespace).Get(ctx, podName, v1.GetOptions{})
Expand Down

0 comments on commit 16267d6

Please sign in to comment.