From 19c14912543fc0e0566e354cd9c1bc3d518c53d2 Mon Sep 17 00:00:00 2001 From: gpickett Date: Mon, 29 Mar 2021 13:19:30 -0400 Subject: [PATCH] Update to REST functionality --- README.md | 6 ++- go/chaos.go | 52 +++++++++++++++++++--- go/monkey-ops.go | 5 +++ go/types.go | 2 + jenkinsfile | 69 ++++++++++++++++++++++++++++++ openshift/monkey-ops-template.yaml | 23 +++++++--- 6 files changed, 145 insertions(+), 12 deletions(-) create mode 100644 jenkinsfile diff --git a/README.md b/README.md index 2786d42..ba226fa 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Monkey-Ops is prepared to running into a docker image. Monkey-Ops also includes Monkey-Ops has two different modes of execution: background or rest. * **Background**: With the Background mode, the service is running nonstop until you stop the container. -* **Rest**: With the Rest mode, you consume an api rest that allows you login in Openshift, choose a project, and execute the chaos for a certain time. +* **Rest**: With the Rest mode, you consume an api rest that allows you login in Openshift, choose a project, and execute the chaos for a certain time. In addition, it will allow you to specify pod names that you want to attack, as well as the option to scale or not your deployments. The service accept parameters as flags or environment variables. These are the input flags required: @@ -29,6 +29,7 @@ The service accept parameters as flags or environment variables. These are the i --MODE string Execution mode: background or rest (by default "background") --PROJECT_NAME string Project to get crazy --TOKEN string Bearer token with edit grants to access to the Openshift project + --NAMES string Name of the pods you want to attack ### Usage with Docker @@ -127,3 +128,6 @@ Monkey-Ops Api Rest expose two endpoints: > "totalTime": Total Time of monkey-ops execution in seconds > } +### Using in Jenkins + +This code has also been extended to easily integrate in Jenkins. After creating a docker image from the Dockefile here (and modifying your yml to represent your images and project name), you can reference the added jenkinsfile to see how it can be intgrated. \ No newline at end of file diff --git a/go/chaos.go b/go/chaos.go index 3653d3d..c8f7489 100644 --- a/go/chaos.go +++ b/go/chaos.go @@ -15,7 +15,7 @@ import ( ) //Get all the pods running from a project -func GetPods(token string, project string, url string) []string { +func GetPods(token string, project string, url string, names string) []string { urlGetPods := url + "/api/v1/namespaces/" + project + "/pods" @@ -53,7 +53,11 @@ func GetPods(token string, project string, url string) []string { podsCustom := map[string]interface{}{} json.Unmarshal(pods, &podsCustom) - if podsCustom != nil && len(podsCustom)> 0 { + //Create Names Variables + targetNames := strings.Split(names, ", ") + + //Case to run against all pods + if podsCustom != nil && len(podsCustom)> 0 && string(targetNames[0]) == "names" { items := podsCustom["items"].([]interface{}) for _, item := range items { @@ -61,13 +65,48 @@ func GetPods(token string, project string, url string) []string { metadataMap := itemObject["metadata"].(map[string]interface{}) statusMap := itemObject["status"].(map[string]interface{}) status := statusMap["phase"].(string) + if status == "Running" { - podsName = append(podsName, metadataMap["name"].(string)) + log.Println("Adding pod") + podsName = append(podsName, metadataMap["name"].(string)) + log.Println(podsName) } - + return podsName } } + //Run Ordered Chaos against Specific Pods + if podsCustom != nil && len(podsCustom)> 0 { + items := podsCustom["items"].([]interface{}) + + for _, item := range items { + itemObject := item.(map[string]interface{}) + metadataMap := itemObject["metadata"].(map[string]interface{}) + statusMap := itemObject["status"].(map[string]interface{}) + status := statusMap["phase"].(string) + appName := metadataMap["name"] + + // If there was no passed, run "ordered" Chaos against listed pods + // No in-built Golang function to compare arrays, have to iterate over + // Iterate over []strings (ie targetNames), because it is not legal argument in strings.Contains + for _, name := range targetNames { + log.Println(name) + log.Println(appName) + if strings.Contains(metadataMap["name"].(string), name) { + if status == "Running" { + log.Println("Adding ", appName) + podsName = append(podsName, metadataMap["name"].(string)) + log.Println(podsName) + } else { + log.Println("Pod already stopping") + } + } else { + log.Println("Skipping pod ", appName) + } + } + } + } + log.Println("Pod List: ", podsName) return podsName } @@ -219,11 +258,12 @@ func ExecuteChaos(chaosInput *ChaosInput, mode string) { for doChaos := (mode == "background" || (time.Since(start).Seconds() < chaosInput.TotalTime)); doChaos; doChaos = (mode == "background" || (time.Since(start).Seconds() < chaosInput.TotalTime)) { //Randomly choice if delete pod or scale a DC - randComponent := random(1, 3) + // Changing to only delete pods + randComponent := random(1, 2) switch randComponent { case 1: - pods := GetPods(chaosInput.Token, chaosInput.Project, chaosInput.Url) + pods := GetPods(chaosInput.Token, chaosInput.Project, chaosInput.Url, chaosInput.Names) if pods != nil && len(pods) > 0 { randPod := random(0, len(pods)) log.Println(pods[randPod]) diff --git a/go/monkey-ops.go b/go/monkey-ops.go index 824b9c1..ad668b0 100644 --- a/go/monkey-ops.go +++ b/go/monkey-ops.go @@ -16,6 +16,7 @@ func main() { flag.String("TOKEN", "", "Bearer token with edit grants to access to the Openshift project") flag.Float64("INTERVAL", 30, "interval time in seconds") flag.String("MODE", "background", "Execution mode: background or rest") + flag.String("NAMES", "", "Names to hit with Chaos") //Binding flags and env vars viper.BindPFlag("API_SERVER", flag.Lookup("API_SERVER")) @@ -23,6 +24,7 @@ func main() { viper.BindPFlag("TOKEN", flag.Lookup("TOKEN")) viper.BindPFlag("INTERVAL", flag.Lookup("INTERVAL")) viper.BindPFlag("MODE", flag.Lookup("MODE")) + viper.BindPFlag("NAMES", flag.Lookup("NAMES")) viper.BindEnv("KUBERNETES_SERVICE_HOST") viper.BindEnv("KUBERNETES_SERVICE_PORT") @@ -31,6 +33,7 @@ func main() { viper.BindEnv("TOKEN") viper.BindEnv("INTERVAL") viper.BindEnv("MODE") + viper.BindEnv("NAMES") flag.Parse() @@ -46,6 +49,7 @@ func main() { token := viper.GetString("TOKEN") interval := viper.GetFloat64("INTERVAL") mode := viper.GetString("MODE") + names := viper.GetString("NAMES") if mode == "background" { // read the service account secret token file at once @@ -67,6 +71,7 @@ func main() { Token: token, Interval: interval, TotalTime: 0, + Names: names, } //Launh the chaos diff --git a/go/types.go b/go/types.go index 190668c..5185dcf 100644 --- a/go/types.go +++ b/go/types.go @@ -15,12 +15,14 @@ type LoginInput struct { Url string `json:"url"` } +//The third field sets a default value type ChaosInput struct { Url string `json:"url"` Project string `json:"project"` Token string `json:"token"` Interval float64 `json:"interval"` TotalTime float64 `json:"totalTime"` + Names string `json:"names"` } type ChaosOutput struct { diff --git a/jenkinsfile b/jenkinsfile new file mode 100644 index 0000000..04a8db2 --- /dev/null +++ b/jenkinsfile @@ -0,0 +1,69 @@ +#!groovy +pipeline { + + agent { + label 'docker-maven-slave' + } + triggers { + githubPush() + } + parameters { + string(name: 'names', defaultValue: 'ratings', description: 'The names of the pods we want to hit with Chaos', trim: true) + string(name: 'interval', defaultValue: '10', description: 'The time (in seconds) we want to have between each Chaos attack', trim: true) + string(name: 'totalTime', defaultValue: '300', description: 'The duration (in seconds) we want chaos', trim: true) + string(name: 'openshift_url', defaultValue: '', trim: true) + choice(name: 'scaling', choices: ['noscaling', 'scaling'], description: 'Whether or not to scale application') + string(name: 'tokenId', description: 'Your monkey-ops security token', trim: true) + string(name: 'endpoint', description: 'Your monkey-ops route', trim: true) + string(name: 'msid', description: 'Your msib', trim: true) + } + +// Trying environment variables + environment { + token = credentials("${tokenId}") + BUILD_VERSION = "$currentBuild.number" + BRANCH = "$BRANCH_NAME" + monkey_url = 'your-base-url' + projectBAK = 'chaos-demo' + + + OCP_PROJECT = 'chaos-demo' + } +// Trying options + options { + buildDiscarder(logRotator(numToKeepStr: '5')) + } +// Trying stages + stages { + stage('Calculate Default settings') { + steps { + echo "params = $params" + // calculate variables (based on msid) + script { + project = "otu-$msid" // Can be changed to specify your own project + jmeterJob = "otu-$msid-jmeter" + } + } + } + stage('Run Chaos Global Library') { + + // Hard coded Chaos tests + //stage ('Chaos Tests') { + steps { + + sh """ + curl -X POST \ + -H 'Content-Type: application/json' \ + -d '{"token":"${token}","url":"${openshift_url}","project":"${project}","interval":'${interval}',"totalTime":'${totalTime}',"names":"${names}","scaling":"${scaling}"}' \\ + ${endpoint}/chaos + """ + } + } + + stage('Starting Jmeter Job') { + steps { + build job: "${jmeterJob}" + } + } + } +} diff --git a/openshift/monkey-ops-template.yaml b/openshift/monkey-ops-template.yaml index 6c57a64..cd44898 100644 --- a/openshift/monkey-ops-template.yaml +++ b/openshift/monkey-ops-template.yaml @@ -7,6 +7,19 @@ metadata: tags: instant-app,agent name: monkey-ops objects: +- apiVersion: v1 + kind: Service + metadata: + name: ${APP_NAME} + labels: + app_name: ${APP_NAME} + spec: + selector: + app_name: ${APP_NAME} + type: ClusterIP + ports: + - name: web + port: 8080 - apiVersion: v1 kind: DeploymentConfig metadata: @@ -43,10 +56,10 @@ objects: - name: INTERVAL value: ${INTERVAL} - name: MODE - value: ${MODE} + value: ${MODE} - name: TZ value: ${TZ} - image: dockerpfurmanek/monkey-ops:1 + image: produban/monkey-ops:latest imagePullPolicy: IfNotPresent name: ${APP_NAME} ports: @@ -54,9 +67,9 @@ objects: protocol: TCP resources: limits: - memory: 20M + memory: 64Mi request: - memory: 20M + memory: 64Mi securityContext: capabilities: {} privileged: false @@ -104,7 +117,7 @@ parameters: displayName: Execution Mode, It must be "background", i.e., it runs nonstop, or "rest", i.e., it executes via API Rest. name: MODE required: true - value: background + value: rest - description: TimeZone for the running containers. displayName: TimeZone name: TZ