From 02914042616d478ed772a63d9620c19f730dcd19 Mon Sep 17 00:00:00 2001 From: Kegan Myers Date: Mon, 24 Aug 2015 00:28:45 -0500 Subject: [PATCH] Graceful exit, and slight restructuring --- README.md | 28 ++------------- checks/http.go | 26 +++++++------- core/check.go | 39 ++++++++++++++++++++ core/config.go | 4 +-- core/engine.go | 80 ++++++++++++++++++++++++++++++------------ core/handler.go | 55 ++++++++++++++++------------- engine/check.go | 11 ++++-- engine/engine.go | 4 ++- handlers/cloudflare.go | 4 +++ handlers/log.go | 4 +++ main.go | 15 +++++--- 11 files changed, 173 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 691a68f..fdc6050 100644 --- a/README.md +++ b/README.md @@ -12,32 +12,8 @@ and placed in whatever directory you will run CFHA from. Example configuration file: -```js -{ - "hosts": [ //an array of hosts to check - { - "host": "192.168.1.1", //the IP address of the host - "type": "http", //the type of check to run, can be either http or https - "options": {} //optional additional parameters to the check module - }, - { - "host": "192.168.1.2", - "type": "https", - "options": { - "hostname": "lb-check-hostname" //this is the only currently supported parameter, and will be sent as the "host" header of an http(s) request - } - } - ], - "cloudflare": { //cloudflare configuration - "email": "cfemail@example.com", - "apiKey": "CF_API_KEY", //get this from your cloudflare profile - "domain": "example.com", //the domain in cloudflare you will be operating on - "name": "lb.example.com", //the full dns record you wish to edit - "ttl": "1" //1 is automatic, otherwise see the cloudflare documentation on TTL - }, - "interval": 1 //how often (in seconds) to ping the server, note that this is a number, not a string -} -``` +(the configuration format is currently in flux, the example will be updated when it is stable. for now just look at `core/config` and build a json object to match `Config`) + Limitations =========== diff --git a/checks/http.go b/checks/http.go index 696d7f5..cf7a00e 100644 --- a/checks/http.go +++ b/checks/http.go @@ -8,14 +8,12 @@ import ( "fmt" ) -func NewHttpChecker(config core.CheckCreateConfig) *HttpChecker { - checker := HttpChecker{ +func NewHttpChecker(config core.CheckCreateConfig) *core.GenericCheck { + return core.NewGenericCheck(&HttpChecker{ &http.Client{}, config, config.Host.Type + "://" + config.Host.Host + "/", - } - go checker.run() - return &checker + }) } type HttpChecker struct { @@ -24,16 +22,16 @@ type HttpChecker struct { endpoint string } -func (this *HttpChecker) run() { - interval := time.Duration(this.config.Interval) * time.Second - log.Print(fmt.Sprintf("Starting: %v\n", this.config.Host)) - for true { - this.config.Engine.Input<- core.Result{ - this.config.Host.Host, - this.check(), - } - time.Sleep(interval) +func (this *HttpChecker) Check() time.Duration { + this.config.Engine.Input<- core.Result{ + this.config.Host.Host, + this.check(), } + return this.config.Interval +} + +func (this *HttpChecker) Stop() bool { + return true } func (this *HttpChecker) check() core.Status { diff --git a/core/check.go b/core/check.go index de01a0f..a4d4bb7 100644 --- a/core/check.go +++ b/core/check.go @@ -9,3 +9,42 @@ type CheckCreateConfig struct { Interval time.Duration Host TargetConfig } + +type Check interface{ + Check() time.Duration + Stop() bool +} + +func NewGenericCheck(proxy Check) *GenericCheck { + check := &GenericCheck{ + make(chan bool, 0), + make(chan bool, 0), + proxy, + } + go check.run() + return check +} + +type GenericCheck struct { + killswitch chan bool + killresponse chan bool + proxy Check +} + +func (this *GenericCheck) Stop() bool { + this.killswitch<- true + return <-this.killresponse +} + +func (this *GenericCheck) run() { + timeout := time.NewTimer(0) + for true { + select { + case <-this.killswitch: + this.killresponse<- this.proxy.Stop() + return + case <-timeout.C: + timeout = time.NewTimer(this.proxy.Check()) + } + } +} diff --git a/core/config.go b/core/config.go index 0c002de..4150f51 100644 --- a/core/config.go +++ b/core/config.go @@ -5,12 +5,12 @@ type Config struct { } type CheckConfig struct { - Interval uint16 - Target TargetConfig + Targets []TargetConfig Reactions []ReactionConfig } type TargetConfig struct { + Interval uint16 Type string Host string Options map[string]string diff --git a/core/engine.go b/core/engine.go index 5d6c42a..ac00dad 100644 --- a/core/engine.go +++ b/core/engine.go @@ -8,15 +8,32 @@ import ( type Engine struct { Input chan Result handlers []*GenericHandler + checks []*GenericCheck killswitch chan bool + killresponse chan bool } func NewEngine() *Engine { - return &Engine{ + engine := &Engine{ make(chan Result), make([]*GenericHandler, 0), - make(chan bool, 1), + make([]*GenericCheck, 0), + make(chan bool, 0), + make(chan bool, 0), } + + go engine.startProcessor() + + return engine +} + +func (this *Engine) AddCheck(check *GenericCheck) { + if check == nil { + return + } + + this.checks = append(this.checks, check) + log.Print(fmt.Sprintf("%v", this.checks)) } func (this *Engine) AddHandler(handler *GenericHandler) { @@ -28,33 +45,50 @@ func (this *Engine) AddHandler(handler *GenericHandler) { log.Print(fmt.Sprintf("%v", this.handlers)) } -func (this *Engine) Run() { - go this.startProcessor() +func (this *Engine) Stop() bool { + this.killswitch<- true + return <-this.killresponse } func (this *Engine) startProcessor() { statuses := make(map[string]Status) for true { - result := <-this.Input + select { + case result := <-this.Input: + //No transition if we don't exist + if result.Status == statuses[result.RecordValue] { + continue + } - //No transition if we don't exist - if result.Status == statuses[result.RecordValue] { - continue + //Create a record with to, from + change := Transition{ + result.Status, + statuses[result.RecordValue], + result.RecordValue, + } + + //Send the record to everyone who cares + for _, relay := range this.handlers { + relay.Channel<- change + } + + //And set our new status + statuses[result.RecordValue] = result.Status + case <-this.killswitch: + this.killresponse<- this.stop() + return } - - //Create a record with to, from - change := Transition{ - result.Status, - statuses[result.RecordValue], - result.RecordValue, - } - - //Send the record to everyone who cares - for _, relay := range this.handlers { - relay.Channel<- change - } - - //And set our new status - statuses[result.RecordValue] = result.Status } } + +func (this *Engine) stop() bool { + exitStatus := true + for _, handler := range this.handlers { + exitStatus = exitStatus && handler.Stop() + } + for _, check := range this.checks { + exitStatus = exitStatus && check.Stop() + } + return exitStatus +} + diff --git a/core/handler.go b/core/handler.go index b887b5a..8e5655b 100644 --- a/core/handler.go +++ b/core/handler.go @@ -5,35 +5,42 @@ import ( type Handler interface{ Handle(transition Transition) -} - -type GenericHandler struct { - Channel chan Transition - killswitch chan bool -} - -func (this *GenericHandler) Stop() { - this.killswitch <- true -} - -func (this *GenericHandler) run(proxy Handler) { - for true { - var transition Transition - select { - case <-this.killswitch: - return - case transition = <-this.Channel: - proxy.Handle(transition) - } - - } + Stop() bool } func NewGenericHandler(input chan Transition, proxy Handler) *GenericHandler { handler := &GenericHandler{ input, - make(chan bool, 1), + make(chan bool, 0), + make(chan bool, 0), + proxy, } - go handler.run(proxy) + go handler.run() return handler } + +type GenericHandler struct { + Channel chan Transition + killswitch chan bool + killresponse chan bool + proxy Handler +} + +func (this *GenericHandler) Stop() bool { + this.killswitch<- true + return <-this.killresponse +} + +func (this *GenericHandler) run() { + for true { + var transition Transition + select { + case <-this.killswitch: + this.killresponse<- this.proxy.Stop() + return + case transition = <-this.Channel: + this.proxy.Handle(transition) + } + + } +} diff --git a/engine/check.go b/engine/check.go index 8cd4c9a..66298ca 100644 --- a/engine/check.go +++ b/engine/check.go @@ -1,19 +1,24 @@ package engine import ( + "log" + "fmt" "time" "../core" "../checks" ) -func createCheck(interval uint16, engine *core.Engine, host core.TargetConfig) { +func createCheck(engine *core.Engine, host core.TargetConfig) *core.GenericCheck { + log.Print("createCheck called") + log.Print(fmt.Sprintf("creating a `%s`", host.Type)) config := core.CheckCreateConfig{ engine, - time.Duration(int64(interval)) * time.Second, + time.Duration(int64(host.Interval)) * time.Second, host, } switch host.Type { case "http", "https": - checks.NewHttpChecker(config) + return checks.NewHttpChecker(config) } + return nil } diff --git a/engine/engine.go b/engine/engine.go index 582b318..3f967ef 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -11,7 +11,9 @@ func EngineFromConfig(config core.CheckConfig) *core.Engine { engine.AddHandler(createHandler(reaction)) } - createCheck(config.Interval, engine, config.Target) + for _, target := range config.Targets { + engine.AddCheck(createCheck(engine, target)) + } return engine } diff --git a/handlers/cloudflare.go b/handlers/cloudflare.go index a6de925..322d330 100644 --- a/handlers/cloudflare.go +++ b/handlers/cloudflare.go @@ -43,6 +43,10 @@ func (this *cloudflareHandler) Handle(transition core.Transition) { } } +func (this *cloudflareHandler) Stop() bool { + return true +} + func (this *cloudflareHandler) removeCloudflareRecord(recordValue string) bool { records, err := this.client.RetrieveRecordsByName(this.config.Options["Domain"], this.config.Options["Name"]) diff --git a/handlers/log.go b/handlers/log.go index 39d43de..a30e950 100644 --- a/handlers/log.go +++ b/handlers/log.go @@ -18,3 +18,7 @@ func (this *logHandler) Handle(transition core.Transition) { transition.RecordValue, transition.To, transition.To.String())) } + +func (this *logHandler) Stop() bool { + return true +} diff --git a/main.go b/main.go index 0ab9124..d6dd95b 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,10 @@ package main import ( "log" "fmt" + "os" + "syscall" "io/ioutil" + "os/signal" "encoding/json" "./core" "./engine" @@ -24,9 +27,13 @@ func main() { engines = append(engines, engine.EngineFromConfig(check)) } - for _, engine := range engines { - engine.Run() - } + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs - select{} + log.Print(fmt.Sprintf("Stopping.")) + for _, engine := range engines { + engine.Stop() + } + log.Print("Exiting.") }