package main import "sync" import "time" import "fmt" import "runtime" import "github.com/gin-gonic/gin" import "github.com/rs/zerolog/log" type InstanceBackend interface { //FIXME } type InstanceManager struct { mu sync.Mutex instances map[InstanceHostname]*Instance newInstanceNotifications chan InstanceHostname startup time.Time } func NewInstanceManager() *InstanceManager { i := new(InstanceManager) i.instances = make(map[InstanceHostname]*Instance) return i } func (self *InstanceManager) logCaller(msg string) { fpcs := make([]uintptr, 1) // Skip 2 levels to get the caller n := runtime.Callers(3, fpcs) if n == 0 { log.Debug().Msg("MSG: NO CALLER") } caller := runtime.FuncForPC(fpcs[0] - 1) if caller == nil { log.Debug().Msg("MSG CALLER WAS NIL") } // Print the file name and line number filename, line := caller.FileLine(fpcs[0] - 1) function := caller.Name() log.Debug(). Str("filename", filename). Int("linenum", line). Str("function", function). Msg(msg) } func (self *InstanceManager) Lock() { self.logCaller("instancemanager attempting to lock") self.mu.Lock() self.logCaller("instancemanager locked") } func (self *InstanceManager) Unlock() { self.mu.Unlock() self.logCaller("instancemanager unlocked") } func (self *InstanceManager) AddInstanceNotificationChannel(via chan InstanceHostname) { self.Lock() defer self.Unlock() self.newInstanceNotifications = via } func (self *InstanceManager) Manage() { self.managerInfiniteLoop() } func (self *InstanceManager) managerInfiniteLoop() { log.Info().Msg("InstanceManager starting") go self.receiveNewInstanceHostnames() self.startup = time.Now() for { log.Info().Msg("InstanceManager tick") self.managerLoop() time.Sleep(1 * time.Second) } } func (self *InstanceManager) managerLoop() { self.Lock() defer self.Unlock() for _, v := range self.instances { // wrap in a new goroutine because this needs to iterate // fast and unlock fast go func() { if v.dueForFetch() { go v.Fetch() } }() } } func (self *InstanceManager) hostnameExists(newhn InstanceHostname) bool { self.Lock() defer self.Unlock() for k, _ := range self.instances { if newhn == k { return true } } return false } func (self *InstanceManager) addInstanceByHostname(newhn InstanceHostname) { // only add it if we haven't seen the hostname before if self.hostnameExists(newhn) { return } i := NewInstance(newhn) self.Lock() defer self.Unlock() self.instances[newhn] = i } func (self *InstanceManager) receiveNewInstanceHostnames() { var newhn InstanceHostname for { newhn = <-self.newInstanceNotifications self.addInstanceByHostname(newhn) } } func (self *InstanceManager) logInstanceReport() { r := self.instanceSummaryReport() log.Info(). Uint("up", r.up). Uint("total", r.total). Uint("identified", r.identified). Msg("instance report") } type InstanceSummaryReport struct { up uint identified uint total uint } func (r *InstanceSummaryReport) String() string { return fmt.Sprintf("up=%d identified=%d total=%d", r.up, r.identified, r.total) } func (self *InstanceManager) NumInstances() uint { return self.instanceSummaryReport().total } type InstanceListReport []*InstanceDetail type InstanceDetail struct { hostname string up bool nextFetch string } func (self *InstanceManager) listInstances() []*Instance { var out []*Instance self.Lock() defer self.Unlock() for _, v := range self.instances { out = append(out, v) } return out } func (self *InstanceManager) instanceListForApi() []*gin.H { var output []*gin.H l := self.listInstances() for _, v := range l { id := &gin.H{ "hostname": v.hostname, "up": v.Up(), "nextFetch": string(time.Now().Sub(v.nextFetch)), } output = append(output, id) } return output } func (self *InstanceManager) instanceSummaryReport() *InstanceSummaryReport { self.Lock() defer self.Unlock() r := new(InstanceSummaryReport) r.total = uint(len(self.instances)) for _, elem := range self.instances { if elem.identified == true { r.identified = r.identified + 1 } if elem.status == InstanceStatusAlive { r.up = r.up + 1 } } return r }