feta/instance.go

333 lines
6.7 KiB
Go
Raw Normal View History

2019-10-24 10:38:16 +00:00
package main
2019-11-02 06:56:17 +00:00
import "encoding/json"
import "fmt"
import "io/ioutil"
2019-11-02 06:56:17 +00:00
import "net/http"
import "strings"
2019-11-03 10:56:50 +00:00
import "sync"
2019-11-02 06:56:17 +00:00
import "time"
2019-11-03 18:00:01 +00:00
import "errors"
2019-11-02 06:56:17 +00:00
2019-11-04 17:07:04 +00:00
import "github.com/gin-gonic/gin"
2019-11-02 06:56:17 +00:00
import "github.com/rs/zerolog/log"
2019-10-24 10:38:16 +00:00
const NodeInfoSchemaVersionTwoName = "http://nodeinfo.diaspora.software/ns/schema/2.0"
const INSTANCE_HTTP_TIMEOUT = time.Second * 60
const INSTANCE_SPIDER_INTERVAL = time.Second * 60
const INSTANCE_ERROR_INTERVAL = time.Second * 60 * 30
type InstanceImplementation int
const (
Unknown InstanceImplementation = iota
Mastodon
Pleroma
)
type InstanceStatus int
2019-10-24 11:56:44 +00:00
const (
InstanceStatusNone InstanceStatus = iota
InstanceStatusUnknown
InstanceStatusAlive
2019-11-03 18:00:01 +00:00
InstanceStatusIdentified
InstanceStatusFailure
2019-10-24 11:56:44 +00:00
)
type Instance struct {
2019-11-03 18:00:01 +00:00
sync.RWMutex
errorCount uint
successCount uint
highestId int
2019-11-03 18:00:01 +00:00
hostname string
2019-11-03 11:09:04 +00:00
identified bool
2019-11-03 18:00:01 +00:00
fetching bool
impl InstanceImplementation
2019-11-03 13:17:00 +00:00
backend *InstanceBackend
status InstanceStatus
2019-11-03 18:00:01 +00:00
nextFetch time.Time
nodeInfoUrl string
serverVersion string
2019-10-24 10:38:16 +00:00
}
2019-11-03 18:00:01 +00:00
func NewInstance(hostname InstanceHostname) *Instance {
2019-11-04 17:07:04 +00:00
self := new(Instance)
self.hostname = string(hostname)
self.status = InstanceStatusUnknown
self.nextFetch = time.Now().Add(-1 * time.Second)
return self
2019-10-24 11:56:44 +00:00
}
2019-11-04 17:07:04 +00:00
func (self *Instance) bumpFetch() {
self.Lock()
defer self.Unlock()
self.nextFetch = time.Now().Add(10 * time.Second)
2019-11-03 10:56:50 +00:00
}
2019-11-04 17:07:04 +00:00
func (self *Instance) setNextFetchAfter(d time.Duration) {
2019-11-03 18:00:01 +00:00
self.Lock()
defer self.Unlock()
2019-11-04 17:07:04 +00:00
self.nextFetch = time.Now().Add(d)
2019-11-03 10:56:50 +00:00
}
2019-11-03 18:00:01 +00:00
func (self *Instance) Fetch() {
err := self.detectNodeTypeIfNecessary()
if err != nil {
self.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
log.Debug().
Str("hostname", self.hostname).
Err(err).
Msg("unable to fetch instance metadata")
2019-10-24 11:56:44 +00:00
return
}
2019-11-03 18:00:01 +00:00
//self.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
log.Info().Msgf("i (%s) should check for toots", self.hostname)
}
func (self *Instance) dueForFetch() bool {
2019-11-04 17:07:04 +00:00
self.Lock()
defer self.Unlock()
nf := self.nextFetch
return nf.Before(time.Now())
}
func (self *Instance) nodeIdentified() bool {
2019-11-03 18:00:01 +00:00
self.RLock()
defer self.RUnlock()
2019-11-04 17:07:04 +00:00
if self.impl > Unknown {
return true
2019-11-03 18:00:01 +00:00
}
2019-11-04 17:07:04 +00:00
return false
2019-11-03 18:00:01 +00:00
}
2019-11-04 17:07:04 +00:00
func (self *Instance) detectNodeTypeIfNecessary() error {
if !self.nodeIdentified() {
return self.fetchNodeInfo()
} else {
2019-11-03 18:00:01 +00:00
return nil
}
2019-10-24 11:56:44 +00:00
}
2019-11-04 17:07:04 +00:00
func (self *Instance) registerError() {
self.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
self.Lock()
defer self.Unlock()
self.errorCount++
}
2019-11-04 17:07:04 +00:00
func (self *Instance) registerSuccess() {
self.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
self.Lock()
defer self.Unlock()
self.successCount++
}
func (self *Instance) ApiReport() *gin.H {
r := gin.H{}
return &r
}
2019-11-03 18:00:01 +00:00
func (i *Instance) Up() bool {
i.Lock()
defer i.Unlock()
return i.successCount > 0
}
func (i *Instance) fetchNodeInfoURL() error {
url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostname)
2019-10-24 11:56:44 +00:00
var c = &http.Client{
Timeout: INSTANCE_HTTP_TIMEOUT,
2019-10-24 11:56:44 +00:00
}
log.Debug().
Str("url", url).
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Msg("fetching nodeinfo reference URL")
resp, err := c.Get(url)
2019-10-24 11:56:44 +00:00
if err != nil {
log.Debug().
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Err(err).
Msg("unable to fetch nodeinfo, node is down?")
i.registerError()
2019-11-03 18:00:01 +00:00
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Debug().
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Err(err).
Msg("unable to read nodeinfo")
i.registerError()
2019-11-03 18:00:01 +00:00
return err
}
nir := new(NodeInfoWellKnownResponse)
err = json.Unmarshal(body, &nir)
if err != nil {
2019-11-03 18:00:01 +00:00
log.Debug().
Str("hostname", i.hostname).
Err(err).
Msg("unable to parse nodeinfo, node is weird")
i.registerError()
2019-11-03 18:00:01 +00:00
return err
}
for _, item := range nir.Links {
if item.Rel == NodeInfoSchemaVersionTwoName {
2019-11-03 18:00:01 +00:00
log.Debug().
Str("hostname", i.hostname).
Str("nodeinfourl", item.Href).
Msg("success fetching url for nodeinfo")
i.Lock()
i.nodeInfoUrl = item.Href
i.Unlock()
i.registerSuccess()
2019-11-03 18:00:01 +00:00
return nil
}
2019-11-03 18:00:01 +00:00
log.Debug().
Str("hostname", i.hostname).
Str("item-rel", item.Rel).
Str("item-href", item.Href).
Msg("found key in nodeinfo")
}
log.Error().
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Msg("incomplete nodeinfo")
i.registerError()
2019-11-03 18:00:01 +00:00
return errors.New("incomplete nodeinfo")
}
2019-11-03 18:00:01 +00:00
func (i *Instance) fetchNodeInfo() error {
err := i.fetchNodeInfoURL()
2019-11-03 10:56:50 +00:00
2019-11-03 18:00:01 +00:00
if err != nil {
return err
2019-10-24 11:56:44 +00:00
}
var c = &http.Client{
Timeout: INSTANCE_HTTP_TIMEOUT,
}
//FIXME make sure the nodeinfourl is on the same domain as the instance
//hostname
2019-11-03 18:00:01 +00:00
i.RLock()
2019-11-03 10:56:50 +00:00
url := i.nodeInfoUrl
2019-11-03 18:00:01 +00:00
i.RUnlock()
2019-11-03 10:56:50 +00:00
resp, err := c.Get(url)
if err != nil {
2019-11-03 18:00:01 +00:00
log.Debug().
Str("hostname", i.hostname).
Err(err).
Msgf("unable to fetch nodeinfo data")
i.registerError()
2019-11-03 18:00:01 +00:00
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Error().
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Err(err).
Msgf("unable to read nodeinfo data")
i.registerError()
2019-11-03 18:00:01 +00:00
return err
}
ni := new(NodeInfoVersionTwoSchema)
err = json.Unmarshal(body, &ni)
if err != nil {
log.Error().
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Err(err).
Msgf("unable to parse nodeinfo")
i.registerError()
2019-11-03 18:00:01 +00:00
return err
}
2019-11-03 18:00:01 +00:00
log.Debug().
Str("serverVersion", ni.Software.Version).
Str("software", ni.Software.Name).
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Str("nodeInfoUrl", i.nodeInfoUrl).
Msg("received nodeinfo from instance")
i.Lock()
defer i.Unlock()
i.serverVersion = ni.Software.Version
ni.Software.Name = strings.ToLower(ni.Software.Name)
if ni.Software.Name == "pleroma" {
2019-11-03 18:00:01 +00:00
log.Debug().
Str("hostname", i.hostname).
Str("software", ni.Software.Name).
Msg("detected server software")
i.registerSuccess()
i.identified = true
i.impl = Pleroma
2019-11-03 18:00:01 +00:00
i.status = InstanceStatusIdentified
return nil
} else if ni.Software.Name == "mastodon" {
i.registerSuccess()
i.identified = true
i.impl = Mastodon
2019-11-03 18:00:01 +00:00
i.status = InstanceStatusIdentified
return nil
} else {
log.Error().
2019-11-03 18:00:01 +00:00
Str("hostname", i.hostname).
Str("software", ni.Software.Name).
2019-11-03 18:00:01 +00:00
Msg("FIXME unknown server implementation")
i.registerError()
2019-11-03 18:00:01 +00:00
return errors.New("FIXME unknown server implementation")
}
2019-10-24 11:56:44 +00:00
}
2019-11-03 13:17:00 +00:00
/*
2019-10-24 11:56:44 +00:00
func (i *Instance) fetchRecentToots() ([]byte, error) {
2019-11-03 10:56:50 +00:00
i.Lock()
impl := i.impl
i.Unlock()
if impl == Mastodon {
2019-10-24 11:56:44 +00:00
return i.fetchRecentTootsJsonFromMastodon()
} else if impl == Pleroma {
return i.fetchRecentTootsJsonFromPleroma()
2019-10-24 11:56:44 +00:00
} else {
2019-11-03 10:56:50 +00:00
panic("unimplemented")
2019-10-24 11:56:44 +00:00
}
}
2019-11-03 13:17:00 +00:00
*/
2019-10-24 11:56:44 +00:00
2019-11-03 13:17:00 +00:00
/*
func (self *PleromaBackend) fetchRecentToots() ([]byte, error) {
2019-11-03 18:00:01 +00:00
//url :=
//fmt.Sprintf("https://%s/api/statuses/public_and_external_timeline.json?count=100",
//i.hostname)
2019-10-24 11:56:44 +00:00
return nil, nil
}
2019-10-24 10:38:16 +00:00
2019-11-03 13:17:00 +00:00
func (self *MastodonBackend) fetchRecentTootsJsonFromMastodon() ([]byte, error) {
2019-11-03 18:00:01 +00:00
//url :=
//fmt.Sprintf("https://%s/api/v1/timelines/public?limit=40&local=true",
//i.hostname)
2019-10-24 11:56:44 +00:00
return nil, nil
}
2019-11-03 13:17:00 +00:00
*/