-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathapi.go
178 lines (151 loc) · 5.09 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package main
import (
"encoding/json"
"io"
"math/rand"
"net/http"
"strings"
"sync/atomic"
"time"
log "github.com/Sirupsen/logrus"
marathon "github.com/gambol99/go-marathon"
)
// StatsResult JSON payload
type StatsResult struct {
KilledTasks uint64 `json:"gone"`
}
// RampageResult JSON payload
type RampageResult struct {
Success bool `json:"success"`
KilledTasks []string `json:"goners"`
}
// Handles /health API calls (GET only)
func getHealth(w http.ResponseWriter, r *http.Request) {
log.WithFields(log.Fields{"handle": "/health"}).Info("health check")
io.WriteString(w, "I am Groot")
}
// Handles /stats API calls (GET only)
func getStats(w http.ResponseWriter, r *http.Request) {
log.WithFields(log.Fields{"handle": "/stats"}).Info("Overall tasks killed: ", overallTasksKilled)
sr := &StatsResult{
KilledTasks: overallTasksKilled}
srJSON, _ := json.Marshal(sr)
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, string(srJSON))
}
// Handles /rampage API calls (POST only)
func postRampage(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
log.WithFields(log.Fields{"handle": "/rampage"}).Info("Starting rampage on destruction level ", destructionLevel)
switch destructionLevel {
case DLBASIC:
killTasks(w, r)
case DLADVANCED:
io.WriteString(w, "not yet implemented")
case DLALL:
io.WriteString(w, "not yet implemented")
default:
http.NotFound(w, r)
}
} else {
log.WithFields(log.Fields{"handle": "/rampage"}).Error("Only POST method supported")
http.NotFound(w, r)
}
}
// killTasks will identify tasks of any apps (but not framework services)
// and randomly kill off a few of them
func killTasks(w http.ResponseWriter, r *http.Request) {
if client, ok := getClient(); ok {
apps, err := client.Applications(nil)
if err != nil {
log.WithFields(log.Fields{"handle": "/rampage"}).Info("Failed to list apps")
http.Error(w, "Failed to list apps", 500)
return
}
log.WithFields(log.Fields{"handle": "/rampage"}).Info("Found overall ", len(apps.Apps), " applications running")
candidates := []string{}
for _, app := range apps.Apps {
log.WithFields(log.Fields{"handle": "/rampage"}).Debug("APP ", app.ID)
details, _ := client.Application(app.ID)
if !myself(details) && !isFramework(details) {
if details.Tasks != nil && len(details.Tasks) > 0 {
for _, task := range details.Tasks {
log.WithFields(log.Fields{"handle": "/rampage"}).Debug("TASK ", task.ID)
candidates = append(candidates, task.ID)
}
}
}
}
if len(candidates) > 0 {
log.WithFields(log.Fields{"handle": "/rampage"}).Info("Found ", len(candidates), " tasks to kill")
rampage(w, client, candidates)
}
} else {
http.Error(w, "Can't connect to Marathon", 500)
}
}
// getClient tries to get a connection to the DC/OS System Marathon
func getClient() (marathon.Marathon, bool) {
config := marathon.NewDefaultConfig()
config.URL = marathonURL
client, err := marathon.NewClient(config)
if err != nil {
log.WithFields(log.Fields{"handle": "/rampage"}).Error("Failed to create Marathon client due to ", err)
return nil, false
}
return client, true
}
// rampage kills random tasks from the candidates and returns a JSON result
func rampage(w http.ResponseWriter, c marathon.Marathon, candidates []string) {
var targets []int
// generates a list of random, non-repeating indices into the candidates:
if len(candidates) > numTargets {
targets = rand.Perm(len(candidates))[:numTargets]
} else {
targets = rand.Perm(len(candidates))
}
rr := &RampageResult{
Success: true,
KilledTasks: []string{}}
// kill the candidates
for _, t := range targets {
candidate := candidates[t]
killTask(c, candidate)
log.WithFields(log.Fields{"handle": "/rampage"}).Info("Killed tasks ", candidate)
rr.KilledTasks = append(rr.KilledTasks, candidate)
atomic.AddUint64(&overallTasksKilled, 1)
log.WithFields(log.Fields{"handle": "/rampage"}).Info("Counter: ", overallTasksKilled)
time.Sleep(time.Millisecond * time.Duration(sleepTime))
}
rrJSON, _ := json.Marshal(rr)
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, string(rrJSON))
}
// killTask kills a certain task and increments overall count if successful
func killTask(c marathon.Marathon, taskID string) bool {
_, err := c.KillTask(taskID, nil)
if err != nil {
log.WithFields(log.Fields{"handle": "/rampage"}).Debug("Not able to kill task ", taskID)
return false
}
log.WithFields(log.Fields{"handle": "/rampage"}).Debug("Killed task ", taskID)
return true
}
// myself returns true if it is applied to DRAX Marathon app itself
func myself(app *marathon.Application) bool {
if strings.Contains(app.ID, "drax") {
return true
}
return false
}
// isFramework returns true if the Marathon app is a service framework,
// and false otherwise (determined via the DCOS_PACKAGE_IS_FRAMEWORK label key)
func isFramework(app *marathon.Application) bool {
for k, v := range *app.Labels {
log.WithFields(log.Fields{"handle": "/rampage"}).Debug("LABEL ", k, ":", v)
if k == "DCOS_PACKAGE_IS_FRAMEWORK" && v == "true" {
return true
}
}
return false
}