diff --git a/api/v2.go b/api/v2.go index 3c2ca75..70be2d8 100644 --- a/api/v2.go +++ b/api/v2.go @@ -8,6 +8,7 @@ import ( gotcha "github.com/ian-kent/gotcha/app" "github.com/ian-kent/gotcha/http" "github.com/mailhog/MailHog-Server/config" + "github.com/mailhog/MailHog-Server/monkey" "github.com/mailhog/data" "github.com/ian-kent/goose" @@ -38,6 +39,12 @@ func CreateAPIv2(conf *config.Config, app *gotcha.App) *APIv2 { r.Get("/api/v2/search/?", apiv2.search) r.Options("/api/v2/search/?", apiv2.defaultOptions) + r.Get("/api/v2/jim/?", apiv2.jim) + r.Post("/api/v2/jim/?", apiv2.createJim) + r.Put("/api/v2/jim/?", apiv2.updateJim) + r.Delete("/api/v2/jim/?", apiv2.deleteJim) + r.Options("/api/v2/jim/?", apiv2.defaultOptions) + return apiv2 } @@ -125,7 +132,89 @@ func (apiv2 *APIv2) search(session *http.Session) { res.Items = []data.Message(*messages) res.Total = total - bytes, _ := json.Marshal(res) - session.Response.Headers.Add("Content-Type", "text/json") - session.Response.Write(bytes) + b, _ := json.Marshal(res) + session.Response.Headers.Add("Content-Type", "application/json") + session.Response.Write(b) +} + +func (apiv2 *APIv2) jim(session *http.Session) { + log.Println("[APIv2] GET /jim") + + apiv2.defaultOptions(session) + + if apiv2.config.Monkey == nil { + session.Response.Status = 404 + return + } + + b, _ := json.Marshal(apiv2.config.Monkey) + session.Response.Headers.Add("Content-Type", "application/json") + session.Response.Write(b) +} + +func (apiv2 *APIv2) deleteJim(session *http.Session) { + log.Println("[APIv2] DELETE /jim") + + apiv2.defaultOptions(session) + + if apiv2.config.Monkey == nil { + session.Response.Status = 404 + return + } + + apiv2.config.Monkey = nil +} + +func (apiv2 *APIv2) createJim(session *http.Session) { + log.Println("[APIv2] POST /jim") + + apiv2.defaultOptions(session) + + if apiv2.config.Monkey != nil { + session.Response.Status = 400 + return + } + + apiv2.config.Monkey = config.Jim + + // Try, but ignore errors + // Could be better (e.g., ok if no json, error if badly formed json) + // but this works for now + apiv2.newJimFromBody(session) + + session.Response.Status = 201 +} + +func (apiv2 *APIv2) newJimFromBody(session *http.Session) error { + var jim monkey.Jim + + dec := json.NewDecoder(session.Request.Body()) + err := dec.Decode(&jim) + + if err != nil { + return err + } + + jim.ConfigureFrom(config.Jim) + + config.Jim = &jim + apiv2.config.Monkey = &jim + + return nil +} + +func (apiv2 *APIv2) updateJim(session *http.Session) { + log.Println("[APIv2] PUT /jim") + + apiv2.defaultOptions(session) + + if apiv2.config.Monkey == nil { + session.Response.Status = 404 + return + } + + err := apiv2.newJimFromBody(session) + if err != nil { + session.Response.Status = 400 + } } diff --git a/config/config.go b/config/config.go index 504b472..8639aee 100644 --- a/config/config.go +++ b/config/config.go @@ -41,7 +41,7 @@ type Config struct { } var cfg = DefaultConfig() -var jim = &monkey.Jim{} +var Jim = &monkey.Jim{} func Configure() *Config { switch cfg.StorageType { @@ -63,10 +63,10 @@ func Configure() *Config { } if cfg.InviteJim { - jim.Configure(func(message string, args ...interface{}) { + Jim.Configure(func(message string, args ...interface{}) { log.Printf(message, args...) }) - cfg.Monkey = jim + cfg.Monkey = Jim } return cfg @@ -82,5 +82,5 @@ func RegisterFlags() { flag.StringVar(&cfg.MongoColl, "mongo-coll", envconf.FromEnvP("MH_MONGO_COLLECTION", "messages").(string), "MongoDB collection, e.g. messages") flag.StringVar(&cfg.CORSOrigin, "cors-origin", envconf.FromEnvP("MH_CORS_ORIGIN", "").(string), "CORS Access-Control-Allow-Origin header for API endpoints") flag.BoolVar(&cfg.InviteJim, "invite-jim", envconf.FromEnvP("MH_INVITE_JIM", false).(bool), "Decide whether to invite Jim (beware, he causes trouble)") - jim.RegisterFlags() + Jim.RegisterFlags() } diff --git a/monkey/jim.go b/monkey/jim.go index afe6186..9ebf089 100644 --- a/monkey/jim.go +++ b/monkey/jim.go @@ -11,27 +11,27 @@ import ( // Jim is a chaos monkey type Jim struct { - disconnectChance float64 - acceptChance float64 - linkSpeedAffect float64 - linkSpeedMin float64 - linkSpeedMax float64 - rejectSenderChance float64 - rejectRecipientChance float64 - rejectAuthChance float64 + DisconnectChance float64 + AcceptChance float64 + LinkSpeedAffect float64 + LinkSpeedMin float64 + LinkSpeedMax float64 + RejectSenderChance float64 + RejectRecipientChance float64 + RejectAuthChance float64 logf func(message string, args ...interface{}) } // RegisterFlags implements ChaosMonkey.RegisterFlags func (j *Jim) RegisterFlags() { - flag.Float64Var(&j.disconnectChance, "jim-disconnect", 0.005, "Chance of disconnect") - flag.Float64Var(&j.acceptChance, "jim-accept", 0.99, "Chance of accept") - flag.Float64Var(&j.linkSpeedAffect, "jim-linkspeed-affect", 0.1, "Chance of affecting link speed") - flag.Float64Var(&j.linkSpeedMin, "jim-linkspeed-min", 1024, "Minimum link speed (in bytes per second)") - flag.Float64Var(&j.linkSpeedMax, "jim-linkspeed-max", 10240, "Maximum link speed (in bytes per second)") - flag.Float64Var(&j.rejectSenderChance, "jim-reject-sender", 0.05, "Chance of rejecting a sender (MAIL FROM)") - flag.Float64Var(&j.rejectRecipientChance, "jim-reject-recipient", 0.05, "Chance of rejecting a recipient (RCPT TO)") - flag.Float64Var(&j.rejectAuthChance, "jim-reject-auth", 0.05, "Chance of rejecting authentication (AUTH)") + flag.Float64Var(&j.DisconnectChance, "jim-disconnect", 0.005, "Chance of disconnect") + flag.Float64Var(&j.AcceptChance, "jim-accept", 0.99, "Chance of accept") + flag.Float64Var(&j.LinkSpeedAffect, "jim-linkspeed-affect", 0.1, "Chance of affecting link speed") + flag.Float64Var(&j.LinkSpeedMin, "jim-linkspeed-min", 1024, "Minimum link speed (in bytes per second)") + flag.Float64Var(&j.LinkSpeedMax, "jim-linkspeed-max", 10240, "Maximum link speed (in bytes per second)") + flag.Float64Var(&j.RejectSenderChance, "jim-reject-sender", 0.05, "Chance of rejecting a sender (MAIL FROM)") + flag.Float64Var(&j.RejectRecipientChance, "jim-reject-recipient", 0.05, "Chance of rejecting a recipient (RCPT TO)") + flag.Float64Var(&j.RejectAuthChance, "jim-reject-auth", 0.05, "Chance of rejecting authentication (AUTH)") } // Configure implements ChaosMonkey.Configure @@ -40,9 +40,15 @@ func (j *Jim) Configure(logf func(string, ...interface{})) { rand.Seed(time.Now().Unix()) } +// ConfigureFrom lets us configure a new Jim from an old one without +// having to expose logf (and any other future private vars) +func (j *Jim) ConfigureFrom(j2 *Jim) { + j.Configure(j2.logf) +} + // Accept implements ChaosMonkey.Accept func (j *Jim) Accept(conn net.Conn) bool { - if rand.Float64() > j.acceptChance { + if rand.Float64() > j.AcceptChance { j.logf("Jim: Rejecting connection\n") return false } @@ -53,9 +59,9 @@ func (j *Jim) Accept(conn net.Conn) bool { // LinkSpeed implements ChaosMonkey.LinkSpeed func (j *Jim) LinkSpeed() *linkio.Throughput { rand.Seed(time.Now().Unix()) - if rand.Float64() < j.linkSpeedAffect { - lsDiff := j.linkSpeedMax - j.linkSpeedMin - lsAffect := j.linkSpeedMin + (lsDiff * rand.Float64()) + if rand.Float64() < j.LinkSpeedAffect { + lsDiff := j.LinkSpeedMax - j.LinkSpeedMin + lsAffect := j.LinkSpeedMin + (lsDiff * rand.Float64()) f := linkio.Throughput(lsAffect) * linkio.BytePerSecond j.logf("Jim: Restricting throughput to %s\n", f) return &f @@ -66,7 +72,7 @@ func (j *Jim) LinkSpeed() *linkio.Throughput { // ValidRCPT implements ChaosMonkey.ValidRCPT func (j *Jim) ValidRCPT(rcpt string) bool { - if rand.Float64() < j.rejectRecipientChance { + if rand.Float64() < j.RejectRecipientChance { j.logf("Jim: Rejecting recipient %s\n", rcpt) return false } @@ -76,7 +82,7 @@ func (j *Jim) ValidRCPT(rcpt string) bool { // ValidMAIL implements ChaosMonkey.ValidMAIL func (j *Jim) ValidMAIL(mail string) bool { - if rand.Float64() < j.rejectSenderChance { + if rand.Float64() < j.RejectSenderChance { j.logf("Jim: Rejecting sender %s\n", mail) return false } @@ -86,7 +92,7 @@ func (j *Jim) ValidMAIL(mail string) bool { // ValidAUTH implements ChaosMonkey.ValidAUTH func (j *Jim) ValidAUTH(mechanism string, args ...string) bool { - if rand.Float64() < j.rejectAuthChance { + if rand.Float64() < j.RejectAuthChance { j.logf("Jim: Rejecting authentication %s: %s\n", mechanism, args) return false } @@ -96,7 +102,7 @@ func (j *Jim) ValidAUTH(mechanism string, args ...string) bool { // Disconnect implements ChaosMonkey.Disconnect func (j *Jim) Disconnect() bool { - if rand.Float64() < j.disconnectChance { + if rand.Float64() < j.DisconnectChance { j.logf("Jim: Being nasty, kicking them off\n") return true }