Graceful exit, and slight restructuring

This commit is contained in:
Kegan Myers 2015-08-24 00:28:45 -05:00
parent 9ac6dec291
commit 0291404261
11 changed files with 173 additions and 97 deletions

View File

@ -12,32 +12,8 @@ and placed in whatever directory you will run CFHA from.
Example configuration file: Example configuration file:
```js (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`)
{
"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
}
```
Limitations Limitations
=========== ===========

View File

@ -8,14 +8,12 @@ import (
"fmt" "fmt"
) )
func NewHttpChecker(config core.CheckCreateConfig) *HttpChecker { func NewHttpChecker(config core.CheckCreateConfig) *core.GenericCheck {
checker := HttpChecker{ return core.NewGenericCheck(&HttpChecker{
&http.Client{}, &http.Client{},
config, config,
config.Host.Type + "://" + config.Host.Host + "/", config.Host.Type + "://" + config.Host.Host + "/",
} })
go checker.run()
return &checker
} }
type HttpChecker struct { type HttpChecker struct {
@ -24,16 +22,16 @@ type HttpChecker struct {
endpoint string endpoint string
} }
func (this *HttpChecker) run() { func (this *HttpChecker) Check() time.Duration {
interval := time.Duration(this.config.Interval) * time.Second this.config.Engine.Input<- core.Result{
log.Print(fmt.Sprintf("Starting: %v\n", this.config.Host)) this.config.Host.Host,
for true { this.check(),
this.config.Engine.Input<- core.Result{
this.config.Host.Host,
this.check(),
}
time.Sleep(interval)
} }
return this.config.Interval
}
func (this *HttpChecker) Stop() bool {
return true
} }
func (this *HttpChecker) check() core.Status { func (this *HttpChecker) check() core.Status {

View File

@ -9,3 +9,42 @@ type CheckCreateConfig struct {
Interval time.Duration Interval time.Duration
Host TargetConfig 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())
}
}
}

View File

@ -5,12 +5,12 @@ type Config struct {
} }
type CheckConfig struct { type CheckConfig struct {
Interval uint16 Targets []TargetConfig
Target TargetConfig
Reactions []ReactionConfig Reactions []ReactionConfig
} }
type TargetConfig struct { type TargetConfig struct {
Interval uint16
Type string Type string
Host string Host string
Options map[string]string Options map[string]string

View File

@ -8,15 +8,32 @@ import (
type Engine struct { type Engine struct {
Input chan Result Input chan Result
handlers []*GenericHandler handlers []*GenericHandler
checks []*GenericCheck
killswitch chan bool killswitch chan bool
killresponse chan bool
} }
func NewEngine() *Engine { func NewEngine() *Engine {
return &Engine{ engine := &Engine{
make(chan Result), make(chan Result),
make([]*GenericHandler, 0), 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) { func (this *Engine) AddHandler(handler *GenericHandler) {
@ -28,33 +45,50 @@ func (this *Engine) AddHandler(handler *GenericHandler) {
log.Print(fmt.Sprintf("%v", this.handlers)) log.Print(fmt.Sprintf("%v", this.handlers))
} }
func (this *Engine) Run() { func (this *Engine) Stop() bool {
go this.startProcessor() this.killswitch<- true
return <-this.killresponse
} }
func (this *Engine) startProcessor() { func (this *Engine) startProcessor() {
statuses := make(map[string]Status) statuses := make(map[string]Status)
for true { 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 //Create a record with to, from
if result.Status == statuses[result.RecordValue] { change := Transition{
continue 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
}

View File

@ -5,35 +5,42 @@ import (
type Handler interface{ type Handler interface{
Handle(transition Transition) Handle(transition Transition)
} Stop() bool
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)
}
}
} }
func NewGenericHandler(input chan Transition, proxy Handler) *GenericHandler { func NewGenericHandler(input chan Transition, proxy Handler) *GenericHandler {
handler := &GenericHandler{ handler := &GenericHandler{
input, input,
make(chan bool, 1), make(chan bool, 0),
make(chan bool, 0),
proxy,
} }
go handler.run(proxy) go handler.run()
return handler 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)
}
}
}

View File

@ -1,19 +1,24 @@
package engine package engine
import ( import (
"log"
"fmt"
"time" "time"
"../core" "../core"
"../checks" "../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{ config := core.CheckCreateConfig{
engine, engine,
time.Duration(int64(interval)) * time.Second, time.Duration(int64(host.Interval)) * time.Second,
host, host,
} }
switch host.Type { switch host.Type {
case "http", "https": case "http", "https":
checks.NewHttpChecker(config) return checks.NewHttpChecker(config)
} }
return nil
} }

View File

@ -11,7 +11,9 @@ func EngineFromConfig(config core.CheckConfig) *core.Engine {
engine.AddHandler(createHandler(reaction)) engine.AddHandler(createHandler(reaction))
} }
createCheck(config.Interval, engine, config.Target) for _, target := range config.Targets {
engine.AddCheck(createCheck(engine, target))
}
return engine return engine
} }

View File

@ -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 { func (this *cloudflareHandler) removeCloudflareRecord(recordValue string) bool {
records, err := this.client.RetrieveRecordsByName(this.config.Options["Domain"], this.config.Options["Name"]) records, err := this.client.RetrieveRecordsByName(this.config.Options["Domain"], this.config.Options["Name"])

View File

@ -18,3 +18,7 @@ func (this *logHandler) Handle(transition core.Transition) {
transition.RecordValue, transition.To, transition.RecordValue, transition.To,
transition.To.String())) transition.To.String()))
} }
func (this *logHandler) Stop() bool {
return true
}

15
main.go
View File

@ -3,7 +3,10 @@ package main
import ( import (
"log" "log"
"fmt" "fmt"
"os"
"syscall"
"io/ioutil" "io/ioutil"
"os/signal"
"encoding/json" "encoding/json"
"./core" "./core"
"./engine" "./engine"
@ -24,9 +27,13 @@ func main() {
engines = append(engines, engine.EngineFromConfig(check)) engines = append(engines, engine.EngineFromConfig(check))
} }
for _, engine := range engines { sigs := make(chan os.Signal, 1)
engine.Run() signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
} <-sigs
select{} log.Print(fmt.Sprintf("Stopping."))
for _, engine := range engines {
engine.Stop()
}
log.Print("Exiting.")
} }