Compare commits

...

3 Commits

Author SHA1 Message Date
Martin/Geno
9b6e9eb300
change lib 2019-01-20 02:12:47 +01:00
Martin/Geno
8b75fb1b04
add fallback for addresses 2019-01-20 01:00:50 +01:00
Martin/Geno
521af9429a
[TASK] add no-respondd 2019-01-19 21:37:27 +01:00
14 changed files with 173 additions and 16 deletions

View File

@ -25,6 +25,12 @@ As root:
go get -v -u github.com/FreifunkBremen/yanic go get -v -u github.com/FreifunkBremen/yanic
``` ```
### allow to ping
only needed if config has `nodes.ping_count` > 0
```sh
sudo setcap cap_net_raw=+ep /opt/go/bin/yanic
```
#### Work with other databases #### Work with other databases
If you like to use another database solution than influxdb, Pull Requests are If you like to use another database solution than influxdb, Pull Requests are
welcome. Just fork this project and create another subpackage within the folder welcome. Just fork this project and create another subpackage within the folder

View File

@ -55,6 +55,12 @@ save_interval = "5s"
# Set node to offline if not seen within this period # Set node to offline if not seen within this period
offline_after = "10m" offline_after = "10m"
## Verify if node is really down by ping last seen address of node
# send x pings to verify if node is offline (for disable set count < 1)
ping_count = 3
# timeout of sending ping to a node
ping_timeout = "1s"
## [[nodes.output.example]] ## [[nodes.output.example]]
# Each output format has its own config block and needs to be enabled by adding: # Each output format has its own config block and needs to be enabled by adding:

View File

@ -36,6 +36,7 @@ func (c *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, s
func GlobalStatsFields(name string, stats *runtime.GlobalStats) []graphigo.Metric { func GlobalStatsFields(name string, stats *runtime.GlobalStats) []graphigo.Metric {
return []graphigo.Metric{ return []graphigo.Metric{
{Name: name + ".nodes", Value: stats.Nodes}, {Name: name + ".nodes", Value: stats.Nodes},
{Name: name + ".nodes.no_respondd", Value: stats.NodesNoRespondd},
{Name: name + ".gateways", Value: stats.Gateways}, {Name: name + ".gateways", Value: stats.Gateways},
{Name: name + ".clients.total", Value: stats.Clients}, {Name: name + ".clients.total", Value: stats.Clients},
{Name: name + ".clients.wifi", Value: stats.ClientsWifi}, {Name: name + ".clients.wifi", Value: stats.ClientsWifi},

View File

@ -43,6 +43,7 @@ func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time
func GlobalStatsFields(stats *runtime.GlobalStats) map[string]interface{} { func GlobalStatsFields(stats *runtime.GlobalStats) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"nodes": stats.Nodes, "nodes": stats.Nodes,
"nodes.no_respondd": stats.NodesNoRespondd,
"gateways": stats.Gateways, "gateways": stats.Gateways,
"clients.total": stats.Clients, "clients.total": stats.Clients,
"clients.wifi": stats.ClientsWifi, "clients.wifi": stats.ClientsWifi,

View File

@ -50,7 +50,7 @@ func (conn *Connection) InsertLink(link *runtime.Link, time time.Time) {
} }
func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) { func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) {
conn.log("InsertGlobals: [", time.String(), "] site: ", site, " domain: ", domain, ", nodes: ", stats.Nodes, ", clients: ", stats.Clients, " models: ", len(stats.Models)) conn.log("InsertGlobals: [", time.String(), "] site: ", site, " domain: ", domain, ", nodes: ", stats.Nodes, " (no respondd: ", stats.NodesNoRespondd, "), clients: ", stats.Clients, " models: ", len(stats.Models))
} }
func (conn *Connection) PruneNodes(deleteAfter time.Duration) { func (conn *Connection) PruneNodes(deleteAfter time.Duration) {

View File

@ -203,6 +203,8 @@ state_path = "/var/lib/yanic/state.json"
prune_after = "7d" prune_after = "7d"
save_interval = "5s" save_interval = "5s"
offline_after = "10m" offline_after = "10m"
ping_count = 3
ping_timeout = "1s"
``` ```
{% endmethod %} {% endmethod %}
@ -246,6 +248,26 @@ offline_after = "10m"
``` ```
{% endmethod %} {% endmethod %}
### ping_count
{% method %}
Verify if node is really down by ping last seen address of node
send x pings to verify if node is offline (for disable set count < 1)
{% sample lang="toml" %}
```toml
ping_count = 3
```
{% endmethod %}
### ping_timeout
{% method %}
Timeout of sending ping to a node
{% sample lang="toml" %}
```toml
ping_timeout = "1s"
```
{% endmethod %}
## [[nodes.output.example]] ## [[nodes.output.example]]
{% method %} {% method %}

View File

@ -26,6 +26,12 @@ As root:
go get -v -u github.com/FreifunkBremen/yanic go get -v -u github.com/FreifunkBremen/yanic
``` ```
### allow to ping
only needed if config has `nodes.ping_count` > 0
```sh
sudo setcap cap_net_raw=+ep /opt/go/bin/yanic
```
### Install ### Install
```sh ```sh

View File

@ -13,6 +13,7 @@ type Node struct {
Firstseen jsontime.Time `json:"firstseen"` Firstseen jsontime.Time `json:"firstseen"`
Lastseen jsontime.Time `json:"lastseen"` Lastseen jsontime.Time `json:"lastseen"`
Online bool `json:"online"` Online bool `json:"online"`
NoRespondd bool `json:"no_respondd"`
Statistics *data.Statistics `json:"statistics"` Statistics *data.Statistics `json:"statistics"`
Nodeinfo *data.NodeInfo `json:"nodeinfo"` Nodeinfo *data.NodeInfo `json:"nodeinfo"`
Neighbours *data.Neighbours `json:"-"` Neighbours *data.Neighbours `json:"-"`

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/bdlm/log" "github.com/bdlm/log"
ping "github.com/digineo/go-ping"
"github.com/FreifunkBremen/yanic/data" "github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/lib/jsontime" "github.com/FreifunkBremen/yanic/lib/jsontime"
@ -17,6 +18,7 @@ type Nodes struct {
List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID List map[string]*Node `json:"nodes"` // the current nodemap, indexed by node ID
ifaceToNodeID map[string]string // mapping from MAC address to NodeID ifaceToNodeID map[string]string // mapping from MAC address to NodeID
config *NodesConfig config *NodesConfig
pinger *ping.Pinger
sync.RWMutex sync.RWMutex
} }
@ -27,6 +29,11 @@ func NewNodes(config *NodesConfig) *Nodes {
ifaceToNodeID: make(map[string]string), ifaceToNodeID: make(map[string]string),
config: config, config: config,
} }
p, err := ping.New("", "::")
if err != nil {
log.Warnf("ping bind failed: %s", err)
}
nodes.pinger = p
if config.StatePath != "" { if config.StatePath != "" {
nodes.load() nodes.load()
@ -80,9 +87,10 @@ func (nodes *Nodes) Update(nodeID string, res *data.ResponseData) *Node {
// Update fields // Update fields
node.Lastseen = now node.Lastseen = now
node.Online = true node.Online = true
node.Neighbours = res.Neighbours
node.Nodeinfo = res.NodeInfo node.Nodeinfo = res.NodeInfo
node.Statistics = res.Statistics node.Statistics = res.Statistics
node.Neighbours = res.Neighbours
node.NoRespondd = false
return node return node
} }
@ -170,15 +178,29 @@ func (nodes *Nodes) expire() {
nodes.Lock() nodes.Lock()
defer nodes.Unlock() defer nodes.Unlock()
wg := sync.WaitGroup{}
for id, node := range nodes.List { for id, node := range nodes.List {
if node.Lastseen.Before(pruneAfter) { if node.Lastseen.Before(pruneAfter) {
// expire // expire
delete(nodes.List, id) delete(nodes.List, id)
} else if node.Lastseen.Before(offlineAfter) { } else if node.Lastseen.Before(offlineAfter) {
// set to offline // set to offline
wg.Add(1)
go func(node *Node) {
defer wg.Done()
if nodes.config.PingCount > 0 && nodes.ping(node) {
node.Online = true
node.NoRespondd = true
} else {
node.Online = false node.Online = false
node.NoRespondd = false
}
}(node)
} }
} }
wg.Wait()
log.WithField("nodes", "expire").Info("end")
} }
// adds the nodes interface addresses to the internal map // adds the nodes interface addresses to the internal map
@ -240,6 +262,7 @@ func (nodes *Nodes) save() {
// serialize nodes // serialize nodes
SaveJSON(nodes, nodes.config.StatePath) SaveJSON(nodes, nodes.config.StatePath)
log.WithField("nodes", "save").Info("end")
} }
// SaveJSON to path // SaveJSON to path

View File

@ -7,5 +7,7 @@ type NodesConfig struct {
SaveInterval duration.Duration `toml:"save_interval"` // Save nodes periodically SaveInterval duration.Duration `toml:"save_interval"` // Save nodes periodically
OfflineAfter duration.Duration `toml:"offline_after"` // Set node to offline if not seen within this period OfflineAfter duration.Duration `toml:"offline_after"` // Set node to offline if not seen within this period
PruneAfter duration.Duration `toml:"prune_after"` // Remove nodes after n days of inactivity PruneAfter duration.Duration `toml:"prune_after"` // Remove nodes after n days of inactivity
PingCount int `toml:"ping_count"` // send x pings to verify if node is offline (for disable count < 1)
PingTimeout duration.Duration `toml:"ping_timeout"` // timeout of sending ping to a node
Output map[string]interface{} Output map[string]interface{}
} }

39
runtime/nodes_ping.go Normal file
View File

@ -0,0 +1,39 @@
package runtime
import (
"net"
"github.com/bdlm/log"
)
func (nodes *Nodes) ping(node *Node) bool {
logNode := log.WithField("node_id", "unknown")
if node.Nodeinfo != nil {
logNode = logNode.WithField("node_id", node.Nodeinfo.NodeID)
}
var addr *net.IPAddr
if node.Address != nil {
addr = &net.IPAddr{IP:node.Address.IP, Zone: node.Address.Zone}
} else {
logNode.Debug("error no address found")
if node.Nodeinfo != nil {
for _, addrMaybeString := range node.Nodeinfo.Network.Addresses {
if len(addrMaybeString) >= 5 && addrMaybeString[:5] != "fe80:" {
addrMaybe, err := net.ResolveIPAddr("ip6", addrMaybeString)
if err == nil {
addr = addrMaybe
}
}
}
}
}
logAddr := logNode.WithField("addr", addr.String())
_, err := nodes.pinger.PingAttempts(addr, nodes.config.PingTimeout.Duration, nodes.config.PingCount)
logAddr.WithFields(map[string]interface{}{
"success": err == nil,
}).Debug("pong")
return err == nil
}

View File

@ -0,0 +1,44 @@
package runtime
import (
"net"
"testing"
"time"
"github.com/bdlm/log"
"github.com/stretchr/testify/assert"
"github.com/FreifunkBremen/yanic/data"
)
func TestPing(t *testing.T) {
log.SetLevel(log.DebugLevel)
assert := assert.New(t)
config := &NodesConfig{
PingCount: 1,
}
config.OfflineAfter.Duration = time.Minute * 10
// to get default (100%) path of testing
// config.PruneAfter.Duration = time.Hour * 24 * 6
nodes := &Nodes{
config: config,
List: make(map[string]*Node),
ifaceToNodeID: make(map[string]string),
}
node := nodes.Update("expire", &data.ResponseData{NodeInfo: &data.NodeInfo{
NodeID: "nodeID-Lola",
Network: data.Network{Addresses: []string{"fe80::1", "fd2f::1"}},
}})
// get fallback
assert.False(nodes.ping(node))
node.Address = &net.UDPAddr{Zone: "bat0"}
// error during ping
assert.False(nodes.ping(node))
node.Address.IP = net.ParseIP("fe80::1")
// error during ping
assert.False(nodes.ping(node))
}

View File

@ -17,6 +17,7 @@ type GlobalStats struct {
ClientsWifi5 uint32 ClientsWifi5 uint32
Gateways uint32 Gateways uint32
Nodes uint32 Nodes uint32
NodesNoRespondd uint32
Firmwares CounterMap Firmwares CounterMap
Models CounterMap Models CounterMap
@ -81,6 +82,9 @@ func (s *GlobalStats) Add(node *Node) {
s.ClientsWifi5 += stats.Clients.Wifi5 s.ClientsWifi5 += stats.Clients.Wifi5
s.ClientsWifi += stats.Clients.Wifi s.ClientsWifi += stats.Clients.Wifi
} }
if node.NoRespondd {
s.NodesNoRespondd++
}
if node.IsGateway() { if node.IsGateway() {
s.Gateways++ s.Gateways++
} }

View File

@ -22,6 +22,7 @@ func TestGlobalStats(t *testing.T) {
//check GLOBAL_SITE stats //check GLOBAL_SITE stats
assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Gateways) assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Gateways)
assert.EqualValues(3, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Nodes) assert.EqualValues(3, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Nodes)
assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].NodesNoRespondd)
assert.EqualValues(25, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Clients) assert.EqualValues(25, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Clients)
// check models // check models
@ -99,6 +100,7 @@ func createTestNodes() *Nodes {
nodes.AddNode(&Node{ nodes.AddNode(&Node{
Online: true, Online: true,
NoRespondd: true,
Statistics: &data.Statistics{ Statistics: &data.Statistics{
Clients: data.Clients{ Clients: data.Clients{
Total: 2, Total: 2,