works really great now
This commit is contained in:
parent
5023155190
commit
a854a48b2b
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
feta
|
feta
|
||||||
output/
|
output/
|
||||||
|
feta.sqlite
|
||||||
|
@ -4,6 +4,8 @@ import "time"
|
|||||||
import "net/http"
|
import "net/http"
|
||||||
import "encoding/json"
|
import "encoding/json"
|
||||||
import "runtime"
|
import "runtime"
|
||||||
|
import "fmt"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ func (a *FetaAPIServer) instances() []hash {
|
|||||||
i["successCount"] = v.successCount
|
i["successCount"] = v.successCount
|
||||||
i["errorCount"] = v.errorCount
|
i["errorCount"] = v.errorCount
|
||||||
i["identified"] = v.identified
|
i["identified"] = v.identified
|
||||||
i["status"] = v.status
|
i["status"] = v.Status()
|
||||||
i["software"] = "unknown"
|
i["software"] = "unknown"
|
||||||
i["version"] = "unknown"
|
i["version"] = "unknown"
|
||||||
if v.identified {
|
if v.identified {
|
||||||
@ -35,11 +37,42 @@ func (a *FetaAPIServer) instances() []hash {
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *FetaAPIServer) instanceSummary() map[string]int {
|
||||||
|
resp := make(map[string]int)
|
||||||
|
for _, v := range a.feta.manager.listInstances() {
|
||||||
|
v.Lock()
|
||||||
|
resp[fmt.Sprintf("STATUS_%s", v.Status())]++
|
||||||
|
if v.serverImplementationString != "" {
|
||||||
|
//FIXME(sneak) sanitize this to a-z0-9, it is server-provided
|
||||||
|
resp[fmt.Sprintf("SOFTWARE_%s", strings.ToUpper(v.serverImplementationString))]++
|
||||||
|
}
|
||||||
|
v.Unlock()
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FetaAPIServer) getInstanceListHandler() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
result := &gin.H{
|
||||||
|
"instances": a.instances(),
|
||||||
|
}
|
||||||
|
|
||||||
|
json, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(json)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *FetaAPIServer) getIndexHandler() http.HandlerFunc {
|
func (a *FetaAPIServer) getIndexHandler() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
index := &gin.H{
|
index := &gin.H{
|
||||||
"instances": a.instances(),
|
|
||||||
"server": &gin.H{
|
"server": &gin.H{
|
||||||
"now": time.Now().UTC().Format(time.RFC3339),
|
"now": time.Now().UTC().Format(time.RFC3339),
|
||||||
"uptime": a.feta.Uptime().String(),
|
"uptime": a.feta.Uptime().String(),
|
||||||
@ -50,6 +83,7 @@ func (a *FetaAPIServer) getIndexHandler() http.HandlerFunc {
|
|||||||
"buildarch": a.feta.buildarch,
|
"buildarch": a.feta.buildarch,
|
||||||
"builduser": a.feta.builduser,
|
"builduser": a.feta.builduser,
|
||||||
},
|
},
|
||||||
|
"instanceSummary": a.instanceSummary(),
|
||||||
}
|
}
|
||||||
|
|
||||||
json, err := json.Marshal(index)
|
json, err := json.Marshal(index)
|
||||||
|
@ -44,7 +44,9 @@ func (a *FetaAPIServer) getRouter() *gin.Engine {
|
|||||||
r.Use(ginzerolog.Logger("gin"))
|
r.Use(ginzerolog.Logger("gin"))
|
||||||
|
|
||||||
r.GET("/.well-known/healthcheck.json", gin.WrapF(a.getHealthCheckHandler()))
|
r.GET("/.well-known/healthcheck.json", gin.WrapF(a.getHealthCheckHandler()))
|
||||||
r.GET("/", gin.WrapF(a.getIndexHandler()))
|
r.GET("/", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "/feta") })
|
||||||
|
r.GET("/feta", gin.WrapF(a.getIndexHandler()))
|
||||||
|
r.GET("/feta/list/instances", gin.WrapF(a.getInstanceListHandler()))
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
14
dbmodel.go
Normal file
14
dbmodel.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package feta
|
||||||
|
|
||||||
|
import "github.com/jinzhu/gorm"
|
||||||
|
import _ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||||
|
|
||||||
|
type SavedInstance struct {
|
||||||
|
gorm.Model
|
||||||
|
hostname string
|
||||||
|
software string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FetaProcess) databaseMigrations() {
|
||||||
|
f.db.AutoMigrate(&SavedInstance{})
|
||||||
|
}
|
27
feta.go
27
feta.go
@ -32,7 +32,7 @@ type FetaProcess struct {
|
|||||||
manager *InstanceManager
|
manager *InstanceManager
|
||||||
api *FetaAPIServer
|
api *FetaAPIServer
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
startup *time.Time
|
startup time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FetaProcess) identify() {
|
func (f *FetaProcess) identify() {
|
||||||
@ -72,26 +72,31 @@ func (f *FetaProcess) setupLogging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FetaProcess) Uptime() time.Duration {
|
func (f *FetaProcess) Uptime() time.Duration {
|
||||||
return time.Since(*f.startup)
|
return time.Since(f.startup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FetaProcess) setupDatabase() {
|
||||||
|
var err error
|
||||||
|
f.db, err = gorm.Open("sqlite3", "feta.sqlite")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.databaseMigrations()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FetaProcess) RunForever() int {
|
func (f *FetaProcess) RunForever() int {
|
||||||
t := time.Now()
|
f.startup = time.Now()
|
||||||
f.startup = &t
|
|
||||||
|
|
||||||
var err error
|
f.setupDatabase()
|
||||||
f.db, err = gorm.Open("sqlite3", "/feta.sqlite")
|
|
||||||
defer f.db.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Panic().Err(err)
|
|
||||||
}
|
|
||||||
newInstanceHostnameNotifications := make(chan InstanceHostname)
|
newInstanceHostnameNotifications := make(chan InstanceHostname)
|
||||||
|
|
||||||
f.locator = NewInstanceLocator()
|
f.locator = NewInstanceLocator()
|
||||||
f.manager = NewInstanceManager()
|
f.manager = NewInstanceManager()
|
||||||
f.api = new(FetaAPIServer)
|
f.api = new(FetaAPIServer)
|
||||||
f.api.feta = f // api needs to get to us
|
f.api.feta = f // api needs to get to us to access data
|
||||||
|
|
||||||
f.locator.AddInstanceNotificationChannel(newInstanceHostnameNotifications)
|
f.locator.AddInstanceNotificationChannel(newInstanceHostnameNotifications)
|
||||||
f.manager.AddInstanceNotificationChannel(newInstanceHostnameNotifications)
|
f.manager.AddInstanceNotificationChannel(newInstanceHostnameNotifications)
|
||||||
|
216
instance.go
216
instance.go
@ -9,38 +9,31 @@ import "sync"
|
|||||||
import "time"
|
import "time"
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
//import "github.com/gin-gonic/gin"
|
||||||
|
import "github.com/looplab/fsm"
|
||||||
import "github.com/rs/zerolog/log"
|
import "github.com/rs/zerolog/log"
|
||||||
|
|
||||||
const NodeInfoSchemaVersionTwoName = "http://nodeinfo.diaspora.software/ns/schema/2.0"
|
const NodeInfoSchemaVersionTwoName = "http://nodeinfo.diaspora.software/ns/schema/2.0"
|
||||||
|
|
||||||
const INSTANCE_NODEINFO_TIMEOUT = time.Second * 5
|
const INSTANCE_NODEINFO_TIMEOUT = time.Second * 50
|
||||||
|
|
||||||
const INSTANCE_HTTP_TIMEOUT = time.Second * 60
|
const INSTANCE_HTTP_TIMEOUT = time.Second * 50
|
||||||
|
|
||||||
const INSTANCE_SPIDER_INTERVAL = time.Second * 60
|
const INSTANCE_SPIDER_INTERVAL = time.Second * 60
|
||||||
|
|
||||||
const INSTANCE_ERROR_INTERVAL = time.Second * 60 * 30
|
const INSTANCE_ERROR_INTERVAL = time.Second * 60 * 30
|
||||||
|
|
||||||
type InstanceImplementation int
|
type instanceImplementation int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Unknown InstanceImplementation = iota
|
Unknown instanceImplementation = iota
|
||||||
Mastodon
|
Mastodon
|
||||||
Pleroma
|
Pleroma
|
||||||
)
|
)
|
||||||
|
|
||||||
type InstanceStatus int
|
type instanceStatus int
|
||||||
|
|
||||||
const (
|
type instance struct {
|
||||||
InstanceStatusNone InstanceStatus = iota
|
|
||||||
InstanceStatusUnknown
|
|
||||||
InstanceStatusAlive
|
|
||||||
InstanceStatusIdentified
|
|
||||||
InstanceStatusFailure
|
|
||||||
)
|
|
||||||
|
|
||||||
type Instance struct {
|
|
||||||
structLock sync.Mutex
|
structLock sync.Mutex
|
||||||
errorCount uint
|
errorCount uint
|
||||||
successCount uint
|
successCount uint
|
||||||
@ -48,114 +41,162 @@ type Instance struct {
|
|||||||
hostname string
|
hostname string
|
||||||
identified bool
|
identified bool
|
||||||
fetching bool
|
fetching bool
|
||||||
implementation InstanceImplementation
|
implementation instanceImplementation
|
||||||
backend *InstanceBackend
|
backend *InstanceBackend
|
||||||
status InstanceStatus
|
status instanceStatus
|
||||||
nextFetch time.Time
|
nextFetch time.Time
|
||||||
nodeInfoUrl string
|
nodeInfoUrl string
|
||||||
serverVersionString string
|
serverVersionString string
|
||||||
serverImplementationString string
|
serverImplementationString string
|
||||||
fetchingLock sync.Mutex
|
fetchingLock sync.Mutex
|
||||||
|
fsm *fsm.FSM
|
||||||
|
fsmLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInstance(hostname InstanceHostname) *Instance {
|
func NewInstance(options ...func(i *instance)) *instance {
|
||||||
self := new(Instance)
|
i := new(instance)
|
||||||
self.hostname = string(hostname)
|
i.setNextFetchAfter(1 * time.Second)
|
||||||
self.status = InstanceStatusUnknown
|
|
||||||
self.setNextFetchAfter(86400 * time.Second)
|
i.fsm = fsm.NewFSM(
|
||||||
return self
|
"STATUS_UNKNOWN",
|
||||||
|
fsm.Events{
|
||||||
|
{Name: "BEGIN_NODEINFO_URL_FETCH", Src: []string{"STATUS_UNKNOWN"}, Dst: "FETCHING_NODEINFO_URL"},
|
||||||
|
{Name: "GOT_NODEINFO_URL", Src: []string{"FETCHING_NODEINFO_URL"}, Dst: "PRE_NODEINFO_FETCH"},
|
||||||
|
{Name: "BEGIN_NODEINFO_FETCH", Src: []string{"PRE_NODEINFO_FETCH"}, Dst: "FETCHING_NODEINFO"},
|
||||||
|
{Name: "GOT_NODEINFO", Src: []string{"FETCHING_NODEINFO"}, Dst: "READY_FOR_TOOTFETCH"},
|
||||||
|
{Name: "FETCH_TIME_REACHED", Src: []string{"READY_FOR_TOOTFETCH"}, Dst: "READY_AND_DUE_FETCH"},
|
||||||
|
{Name: "WEIRD_NODE_RESPONSE", Src: []string{"FETCHING_NODEINFO_URL", "PRE_NODEINFO_FETCH", "FETCHING_NODEINFO"}, Dst: "WEIRD_NODE"},
|
||||||
|
{Name: "EARLY_FETCH_ERROR", Src: []string{"FETCHING_NODEINFO_URL", "PRE_NODEINFO_FETCH", "FETCHING_NODEINFO"}, Dst: "EARLY_ERROR"},
|
||||||
|
{Name: "TOOT_FETCH_ERROR", Src: []string{"READY_FOR_TOOTFETCH"}, Dst: "TOOT_FETCH_ERROR"},
|
||||||
|
},
|
||||||
|
fsm.Callbacks{
|
||||||
|
"enter_state": func(e *fsm.Event) { i.fsmEnterState(e) },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(i)
|
||||||
|
}
|
||||||
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) Lock() {
|
func (i *instance) Status() string {
|
||||||
self.structLock.Lock()
|
i.fsmLock.Lock()
|
||||||
|
defer i.fsmLock.Unlock()
|
||||||
|
return i.fsm.Current()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) Unlock() {
|
func (i *instance) Event(eventname string) {
|
||||||
self.structLock.Unlock()
|
i.fsmLock.Lock()
|
||||||
|
defer i.fsmLock.Unlock()
|
||||||
|
i.fsm.Event(eventname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) bumpFetch() {
|
func (i *instance) fsmEnterState(e *fsm.Event) {
|
||||||
self.Lock()
|
log.Debug().
|
||||||
defer self.Unlock()
|
Str("hostname", i.hostname).
|
||||||
self.nextFetch = time.Now().Add(100 * time.Second)
|
Str("state", e.Dst).
|
||||||
|
Msg("instance changed state")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) setNextFetchAfter(d time.Duration) {
|
func (i *instance) Lock() {
|
||||||
self.Lock()
|
i.structLock.Lock()
|
||||||
defer self.Unlock()
|
|
||||||
self.nextFetch = time.Now().Add(d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) Fetch() {
|
func (i *instance) Unlock() {
|
||||||
self.fetchingLock.Lock()
|
i.structLock.Unlock()
|
||||||
defer self.fetchingLock.Unlock()
|
}
|
||||||
|
|
||||||
self.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
|
func (i *instance) bumpFetch() {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
i.nextFetch = time.Now().Add(100 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
err := self.detectNodeTypeIfNecessary()
|
func (i *instance) setNextFetchAfter(d time.Duration) {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
i.nextFetch = time.Now().Add(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *instance) Fetch() {
|
||||||
|
i.fetchingLock.Lock()
|
||||||
|
defer i.fetchingLock.Unlock()
|
||||||
|
|
||||||
|
i.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
|
||||||
|
|
||||||
|
err := i.detectNodeTypeIfNecessary()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("hostname", self.hostname).
|
Str("hostname", i.hostname).
|
||||||
Err(err).
|
Err(err).
|
||||||
Msg("unable to fetch instance metadata")
|
Msg("unable to fetch instance metadata")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
|
i.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
|
||||||
log.Info().Msgf("i (%s) IS NOW READY FOR FETCH", self.hostname)
|
log.Info().Msgf("i (%s) IS NOW READY FOR FETCH", i.hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) dueForFetch() bool {
|
func (i *instance) dueForFetch() bool {
|
||||||
self.Lock()
|
// this just checks FSM state, the ticker must update it and do time
|
||||||
defer self.Unlock()
|
// calcs
|
||||||
if !self.identified {
|
if i.Status() == "READY_AND_DUE_FETCH" {
|
||||||
return false
|
|
||||||
}
|
|
||||||
nf := self.nextFetch
|
|
||||||
return nf.Before(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Instance) nodeIdentified() bool {
|
|
||||||
self.Lock()
|
|
||||||
defer self.Unlock()
|
|
||||||
if self.implementation > Unknown {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) detectNodeTypeIfNecessary() error {
|
func (i *instance) isNowPastFetchTime() bool {
|
||||||
if !self.nodeIdentified() {
|
return time.Now().After(i.nextFetch)
|
||||||
return self.fetchNodeInfo()
|
}
|
||||||
|
|
||||||
|
func (i *instance) Tick() {
|
||||||
|
if i.Status() == "READY_FOR_TOOTFETCH" {
|
||||||
|
if i.isNowPastFetchTime() {
|
||||||
|
i.Event("FETCH_TIME_REACHED")
|
||||||
|
}
|
||||||
|
} else if i.Status() == "STATUS_UNKNOWN" {
|
||||||
|
i.Fetch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *instance) nodeIdentified() bool {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
if i.implementation > Unknown {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *instance) detectNodeTypeIfNecessary() error {
|
||||||
|
if !i.nodeIdentified() {
|
||||||
|
return i.fetchNodeInfo()
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) registerError() {
|
func (i *instance) registerError() {
|
||||||
self.Lock()
|
i.Lock()
|
||||||
defer self.Unlock()
|
defer i.Unlock()
|
||||||
self.errorCount++
|
i.errorCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) registerSuccess() {
|
func (i *instance) registerSuccess() {
|
||||||
self.Lock()
|
i.Lock()
|
||||||
defer self.Unlock()
|
defer i.Unlock()
|
||||||
self.successCount++
|
i.successCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Instance) APIReport() *gin.H {
|
func (i *instance) Up() bool {
|
||||||
r := gin.H{}
|
|
||||||
return &r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) Up() bool {
|
|
||||||
i.Lock()
|
i.Lock()
|
||||||
defer i.Unlock()
|
defer i.Unlock()
|
||||||
return i.successCount > 0
|
return i.successCount > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) fetchNodeInfoURL() error {
|
func (i *instance) fetchNodeInfoURL() error {
|
||||||
url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostname)
|
url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostname)
|
||||||
var c = &http.Client{
|
var c = &http.Client{
|
||||||
Timeout: INSTANCE_NODEINFO_TIMEOUT,
|
Timeout: INSTANCE_NODEINFO_TIMEOUT,
|
||||||
@ -166,6 +207,7 @@ func (i *Instance) fetchNodeInfoURL() error {
|
|||||||
Str("hostname", i.hostname).
|
Str("hostname", i.hostname).
|
||||||
Msg("fetching nodeinfo reference URL")
|
Msg("fetching nodeinfo reference URL")
|
||||||
|
|
||||||
|
i.Event("BEGIN_NODEINFO_URL_FETCH")
|
||||||
resp, err := c.Get(url)
|
resp, err := c.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
@ -173,6 +215,7 @@ func (i *Instance) fetchNodeInfoURL() error {
|
|||||||
Err(err).
|
Err(err).
|
||||||
Msg("unable to fetch nodeinfo, node is down?")
|
Msg("unable to fetch nodeinfo, node is down?")
|
||||||
i.registerError()
|
i.registerError()
|
||||||
|
i.Event("EARLY_FETCH_ERROR")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,6 +228,7 @@ func (i *Instance) fetchNodeInfoURL() error {
|
|||||||
Err(err).
|
Err(err).
|
||||||
Msg("unable to read nodeinfo")
|
Msg("unable to read nodeinfo")
|
||||||
i.registerError()
|
i.registerError()
|
||||||
|
i.Event("EARLY_FETCH_ERROR")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +240,7 @@ func (i *Instance) fetchNodeInfoURL() error {
|
|||||||
Err(err).
|
Err(err).
|
||||||
Msg("unable to parse nodeinfo, node is weird")
|
Msg("unable to parse nodeinfo, node is weird")
|
||||||
i.registerError()
|
i.registerError()
|
||||||
|
i.Event("WEIRD_NODE_RESPONSE")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,23 +255,25 @@ func (i *Instance) fetchNodeInfoURL() error {
|
|||||||
i.nodeInfoUrl = item.Href
|
i.nodeInfoUrl = item.Href
|
||||||
i.Unlock()
|
i.Unlock()
|
||||||
i.registerSuccess()
|
i.registerSuccess()
|
||||||
|
i.Event("GOT_NODEINFO_URL")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("hostname", i.hostname).
|
Str("hostname", i.hostname).
|
||||||
Str("item-rel", item.Rel).
|
Str("item-rel", item.Rel).
|
||||||
Str("item-href", item.Href).
|
Str("item-href", item.Href).
|
||||||
Msg("found key in nodeinfo")
|
Msg("nodeinfo entry")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Error().
|
log.Error().
|
||||||
Str("hostname", i.hostname).
|
Str("hostname", i.hostname).
|
||||||
Msg("incomplete nodeinfo")
|
Msg("incomplete nodeinfo")
|
||||||
i.registerError()
|
i.registerError()
|
||||||
|
i.Event("WEIRD_NODE_RESPONSE")
|
||||||
return errors.New("incomplete nodeinfo")
|
return errors.New("incomplete nodeinfo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) fetchNodeInfo() error {
|
func (i *instance) fetchNodeInfo() error {
|
||||||
err := i.fetchNodeInfoURL()
|
err := i.fetchNodeInfoURL()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -243,6 +290,7 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
url := i.nodeInfoUrl
|
url := i.nodeInfoUrl
|
||||||
i.Unlock()
|
i.Unlock()
|
||||||
|
|
||||||
|
i.Event("BEGIN_NODEINFO_FETCH")
|
||||||
resp, err := c.Get(url)
|
resp, err := c.Get(url)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -251,6 +299,7 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Err(err).
|
Err(err).
|
||||||
Msgf("unable to fetch nodeinfo data")
|
Msgf("unable to fetch nodeinfo data")
|
||||||
i.registerError()
|
i.registerError()
|
||||||
|
i.Event("EARLY_FETCH_ERROR")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,6 +312,7 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Err(err).
|
Err(err).
|
||||||
Msgf("unable to read nodeinfo data")
|
Msgf("unable to read nodeinfo data")
|
||||||
i.registerError()
|
i.registerError()
|
||||||
|
i.Event("EARLY_FETCH_ERROR")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,6 +324,7 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Err(err).
|
Err(err).
|
||||||
Msgf("unable to parse nodeinfo")
|
Msgf("unable to parse nodeinfo")
|
||||||
i.registerError()
|
i.registerError()
|
||||||
|
i.Event("WEIRD_NODE_RESPONSE")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,9 +347,9 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Msg("detected server software")
|
Msg("detected server software")
|
||||||
i.identified = true
|
i.identified = true
|
||||||
i.implementation = Pleroma
|
i.implementation = Pleroma
|
||||||
i.status = InstanceStatusIdentified
|
|
||||||
i.Unlock()
|
i.Unlock()
|
||||||
i.registerSuccess()
|
i.registerSuccess()
|
||||||
|
i.Event("GOT_NODEINFO")
|
||||||
return nil
|
return nil
|
||||||
} else if ni.Software.Name == "mastodon" {
|
} else if ni.Software.Name == "mastodon" {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
@ -307,9 +358,9 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Msg("detected server software")
|
Msg("detected server software")
|
||||||
i.identified = true
|
i.identified = true
|
||||||
i.implementation = Mastodon
|
i.implementation = Mastodon
|
||||||
i.status = InstanceStatusIdentified
|
|
||||||
i.Unlock()
|
i.Unlock()
|
||||||
i.registerSuccess()
|
i.registerSuccess()
|
||||||
|
i.Event("GOT_NODEINFO")
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
log.Error().
|
log.Error().
|
||||||
@ -318,7 +369,8 @@ func (i *Instance) fetchNodeInfo() error {
|
|||||||
Msg("FIXME unknown server implementation")
|
Msg("FIXME unknown server implementation")
|
||||||
i.Unlock()
|
i.Unlock()
|
||||||
i.registerError()
|
i.registerError()
|
||||||
return errors.New("FIXME unknown server implementation")
|
i.Event("WEIRD_NODE_RESPONSE")
|
||||||
|
return errors.New("unknown server implementation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,14 +391,14 @@ func (i *Instance) fetchRecentToots() ([]byte, error) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func (self *PleromaBackend) fetchRecentToots() ([]byte, error) {
|
func (i *PleromaBackend) fetchRecentToots() ([]byte, error) {
|
||||||
//url :=
|
//url :=
|
||||||
//fmt.Sprintf("https://%s/api/statuses/public_and_external_timeline.json?count=100",
|
//fmt.Sprintf("https://%s/api/statuses/public_and_external_timeline.json?count=100",
|
||||||
//i.hostname)
|
//i.hostname)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *MastodonBackend) fetchRecentTootsJsonFromMastodon() ([]byte, error) {
|
func (i *MastodonBackend) fetchRecentTootsJsonFromMastodon() ([]byte, error) {
|
||||||
//url :=
|
//url :=
|
||||||
//fmt.Sprintf("https://%s/api/v1/timelines/public?limit=40&local=true",
|
//fmt.Sprintf("https://%s/api/v1/timelines/public?limit=40&local=true",
|
||||||
//i.hostname)
|
//i.hostname)
|
||||||
|
30
locator.go
30
locator.go
@ -23,7 +23,7 @@ var INDEX_ERROR_INTERVAL = time.Second * 60 * 10
|
|||||||
|
|
||||||
// LOG_REPORT_INTERVAL defines how long between logging internal
|
// LOG_REPORT_INTERVAL defines how long between logging internal
|
||||||
// stats/reporting for user supervision
|
// stats/reporting for user supervision
|
||||||
var LOG_REPORT_INTERVAL = time.Second * 60
|
var LOG_REPORT_INTERVAL = time.Second * 10
|
||||||
|
|
||||||
const mastodonIndexUrl = "https://instances.social/list.json?q%5Busers%5D=&q%5Bsearch%5D=&strict=false"
|
const mastodonIndexUrl = "https://instances.social/list.json?q%5Busers%5D=&q%5Bsearch%5D=&strict=false"
|
||||||
const pleromaIndexUrl = "https://distsn.org/cgi-bin/distsn-pleroma-instances-api.cgi"
|
const pleromaIndexUrl = "https://distsn.org/cgi-bin/distsn-pleroma-instances-api.cgi"
|
||||||
@ -55,6 +55,22 @@ func (self *InstanceLocator) addInstance(hostname InstanceHostname) {
|
|||||||
self.reportInstanceVia <- hostname
|
self.reportInstanceVia <- hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *InstanceLocator) mastodonIndexRefreshDue() bool {
|
||||||
|
return self.mastodonIndexNextRefresh.Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InstanceLocator) durationUntilNextMastodonIndexRefresh() time.Duration {
|
||||||
|
return (time.Duration(-1) * time.Now().Sub(*self.mastodonIndexNextRefresh))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InstanceLocator) pleromaIndexRefreshDue() bool {
|
||||||
|
return self.pleromaIndexNextRefresh.Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *InstanceLocator) durationUntilNextPleromaIndexRefresh() time.Duration {
|
||||||
|
return (time.Duration(-1) * time.Now().Sub(*self.pleromaIndexNextRefresh))
|
||||||
|
}
|
||||||
|
|
||||||
func (self *InstanceLocator) Locate() {
|
func (self *InstanceLocator) Locate() {
|
||||||
log.Info().Msg("InstanceLocator starting")
|
log.Info().Msg("InstanceLocator starting")
|
||||||
x := time.Now()
|
x := time.Now()
|
||||||
@ -66,7 +82,7 @@ func (self *InstanceLocator) Locate() {
|
|||||||
log.Info().Msg("InstanceLocator tick")
|
log.Info().Msg("InstanceLocator tick")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if self.pleromaIndexNextRefresh.Before(time.Now()) {
|
if self.pleromaIndexRefreshDue() {
|
||||||
if !pleromaSemaphore.TryAcquire(1) {
|
if !pleromaSemaphore.TryAcquire(1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -76,7 +92,7 @@ func (self *InstanceLocator) Locate() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if self.mastodonIndexNextRefresh.Before(time.Now()) {
|
if self.mastodonIndexRefreshDue() {
|
||||||
if !mastodonSemaphore.TryAcquire(1) {
|
if !mastodonSemaphore.TryAcquire(1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -90,11 +106,11 @@ func (self *InstanceLocator) Locate() {
|
|||||||
if time.Now().After(x.Add(LOG_REPORT_INTERVAL)) {
|
if time.Now().After(x.Add(LOG_REPORT_INTERVAL)) {
|
||||||
x = time.Now()
|
x = time.Now()
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("nextMastodonIndexFetch", time.Now().Sub(*self.mastodonIndexNextRefresh).String()).
|
Str("nextMastodonIndexRefresh", self.durationUntilNextMastodonIndexRefresh().String()).
|
||||||
Send()
|
Msg("refresh countdown")
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("nextMastodonIndexFetch", time.Now().Sub(*self.pleromaIndexNextRefresh).String()).
|
Str("nextPleromaIndexRefresh", self.durationUntilNextPleromaIndexRefresh().String()).
|
||||||
Send()
|
Msg("refresh countdown")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
manager.go
85
manager.go
@ -2,13 +2,12 @@ package feta
|
|||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
import "time"
|
import "time"
|
||||||
import "fmt"
|
|
||||||
import "runtime"
|
import "runtime"
|
||||||
|
|
||||||
//import "github.com/gin-gonic/gin"
|
//import "github.com/gin-gonic/gin"
|
||||||
import "github.com/rs/zerolog/log"
|
import "github.com/rs/zerolog/log"
|
||||||
|
|
||||||
const HOST_DISCOVERY_PARALLELISM = 10
|
const HOST_DISCOVERY_PARALLELISM = 1
|
||||||
|
|
||||||
type InstanceBackend interface {
|
type InstanceBackend interface {
|
||||||
//FIXME
|
//FIXME
|
||||||
@ -16,7 +15,7 @@ type InstanceBackend interface {
|
|||||||
|
|
||||||
type InstanceManager struct {
|
type InstanceManager struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
instances map[InstanceHostname]*Instance
|
instances map[InstanceHostname]*instance
|
||||||
newInstanceNotifications chan InstanceHostname
|
newInstanceNotifications chan InstanceHostname
|
||||||
startup time.Time
|
startup time.Time
|
||||||
hostAdderSemaphore chan bool
|
hostAdderSemaphore chan bool
|
||||||
@ -25,7 +24,7 @@ type InstanceManager struct {
|
|||||||
func NewInstanceManager() *InstanceManager {
|
func NewInstanceManager() *InstanceManager {
|
||||||
i := new(InstanceManager)
|
i := new(InstanceManager)
|
||||||
i.hostAdderSemaphore = make(chan bool, HOST_DISCOVERY_PARALLELISM)
|
i.hostAdderSemaphore = make(chan bool, HOST_DISCOVERY_PARALLELISM)
|
||||||
i.instances = make(map[InstanceHostname]*Instance)
|
i.instances = make(map[InstanceHostname]*instance)
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,31 +75,33 @@ func (self *InstanceManager) Manage() {
|
|||||||
self.receiveNewInstanceHostnames()
|
self.receiveNewInstanceHostnames()
|
||||||
}()
|
}()
|
||||||
self.startup = time.Now()
|
self.startup = time.Now()
|
||||||
|
x := self.startup
|
||||||
for {
|
for {
|
||||||
log.Info().Msg("InstanceManager tick")
|
log.Info().Msg("InstanceManager tick")
|
||||||
self.managerLoop()
|
self.managerLoop()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
if time.Now().After(x.Add(LOG_REPORT_INTERVAL)) {
|
||||||
|
x = time.Now()
|
||||||
|
self.logInstanceReport()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) managerLoop() {
|
func (self *InstanceManager) managerLoop() {
|
||||||
self.Lock()
|
self.Lock()
|
||||||
il := make([]*Instance, 0)
|
il := make([]*instance, 0)
|
||||||
for _, v := range self.instances {
|
for _, v := range self.instances {
|
||||||
il = append(il, v)
|
il = append(il, v)
|
||||||
}
|
}
|
||||||
self.Unlock()
|
self.Unlock()
|
||||||
|
|
||||||
for _, v := range il {
|
for _, v := range il {
|
||||||
if v.dueForFetch() {
|
go func(i *instance) {
|
||||||
go func(i *Instance) {
|
i.Tick()
|
||||||
i.Fetch()
|
|
||||||
}(v)
|
}(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *InstanceManager) hostnameExists(newhn InstanceHostname) bool {
|
func (self *InstanceManager) hostnameExists(newhn InstanceHostname) bool {
|
||||||
self.Lock()
|
self.Lock()
|
||||||
defer self.Unlock()
|
defer self.Unlock()
|
||||||
@ -120,7 +121,9 @@ func (self *InstanceManager) addInstanceByHostname(newhn InstanceHostname) {
|
|||||||
// this blocks on the channel size, limiting concurrency
|
// this blocks on the channel size, limiting concurrency
|
||||||
self.hostAdderSemaphore <- true
|
self.hostAdderSemaphore <- true
|
||||||
|
|
||||||
i := NewInstance(newhn)
|
i := NewInstance(func(x *instance) {
|
||||||
|
x.hostname = string(newhn)
|
||||||
|
})
|
||||||
// we do node detection under the addLock to avoid thundering
|
// we do node detection under the addLock to avoid thundering
|
||||||
// on startup
|
// on startup
|
||||||
i.detectNodeTypeIfNecessary()
|
i.detectNodeTypeIfNecessary()
|
||||||
@ -147,37 +150,19 @@ func (self *InstanceManager) receiveNewInstanceHostnames() {
|
|||||||
|
|
||||||
func (self *InstanceManager) logInstanceReport() {
|
func (self *InstanceManager) logInstanceReport() {
|
||||||
r := self.instanceSummaryReport()
|
r := self.instanceSummaryReport()
|
||||||
log.Info().
|
|
||||||
Uint("up", r.up).
|
sublogger := log.With().Logger()
|
||||||
Uint("total", r.total).
|
|
||||||
Uint("identified", r.identified).
|
for k, v := range r {
|
||||||
|
sublogger = sublogger.With().Uint(k, v).Logger()
|
||||||
|
}
|
||||||
|
|
||||||
|
sublogger.Info().
|
||||||
Msg("instance report")
|
Msg("instance report")
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstanceSummaryReport struct {
|
func (self *InstanceManager) listInstances() []*instance {
|
||||||
up uint
|
var out []*instance
|
||||||
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()
|
self.Lock()
|
||||||
defer self.Unlock()
|
defer self.Unlock()
|
||||||
for _, v := range self.instances {
|
for _, v := range self.instances {
|
||||||
@ -186,22 +171,12 @@ func (self *InstanceManager) listInstances() []*Instance {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *InstanceManager) instanceSummaryReport() *InstanceSummaryReport {
|
func (im *InstanceManager) instanceSummaryReport() map[string]uint {
|
||||||
self.Lock()
|
r := make(map[string]uint)
|
||||||
defer self.Unlock()
|
for _, v := range im.listInstances() {
|
||||||
r := new(InstanceSummaryReport)
|
v.Lock()
|
||||||
|
r[v.Status()]++
|
||||||
r.total = uint(len(self.instances))
|
v.Unlock()
|
||||||
|
|
||||||
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
|
return r
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user