[TASK] handle domain_code (with filters) (#119)

This commit is contained in:
Martin Geno 2018-01-17 20:20:35 +01:00 committed by Martin/Geno
parent fed895312c
commit 35d6933d31
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
29 changed files with 493 additions and 114 deletions

View File

@ -20,6 +20,10 @@ func TestReadConfig(t *testing.T) {
assert.Equal(time.Hour*24*7, config.Nodes.PruneAfter.Duration) assert.Equal(time.Hour*24*7, config.Nodes.PruneAfter.Duration)
assert.Equal(time.Hour*24*7, config.Database.DeleteAfter.Duration) assert.Equal(time.Hour*24*7, config.Database.DeleteAfter.Duration)
assert.Len(config.Respondd.Sites, 1)
assert.Contains(config.Respondd.Sites, "ffhb")
assert.Contains(config.Respondd.Sites["ffhb"].Domains, "city")
// Test output plugins // Test output plugins
assert.Len(config.Nodes.Output, 3) assert.Len(config.Nodes.Output, 3)
outputs := config.Nodes.Output["meshviewer"].([]interface{}) outputs := config.Nodes.Output["meshviewer"].([]interface{})

View File

@ -11,13 +11,14 @@ import (
// importCmd represents the import command // importCmd represents the import command
var importCmd = &cobra.Command{ var importCmd = &cobra.Command{
Use: "import <file.rrd> <site>", Use: "import <file.rrd> <site> <domain>",
Short: "Imports global statistics from the given RRD files, requires InfluxDB", Short: "Imports global statistics from the given RRD files, requires InfluxDB",
Example: "yanic import --config /etc/yanic.toml olddata.rrd global", Example: "yanic import --config /etc/yanic.toml olddata.rrd global global",
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(3),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
path := args[0] path := args[0]
site := args[1] site := args[1]
domain := args[2]
config := loadConfig() config := loadConfig()
err := allDatabase.Start(config.Database) err := allDatabase.Start(config.Database)
@ -36,6 +37,7 @@ var importCmd = &cobra.Command{
}, },
ds.Time, ds.Time,
site, site,
domain,
) )
} }
}, },

View File

@ -27,7 +27,9 @@ var queryCmd = &cobra.Command{
nodes := runtime.NewNodes(&runtime.NodesConfig{}) nodes := runtime.NewNodes(&runtime.NodesConfig{})
collector := respond.NewCollector(nil, nodes, []string{}, []string{iface}, 0) sitesDomains := make(map[string][]string)
collector := respond.NewCollector(nil, nodes, sitesDomains, []string{iface}, 0)
defer collector.Close() defer collector.Close()
collector.SendPacket(dstAddress) collector.SendPacket(dstAddress)

View File

@ -54,7 +54,7 @@ var serveCmd = &cobra.Command{
time.Sleep(delay) time.Sleep(delay)
} }
collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.Sites, config.Respondd.Interfaces, config.Respondd.Port) collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.SitesDomains(), config.Respondd.Interfaces, config.Respondd.Port)
collector.Start(config.Respondd.CollectInterval.Duration) collector.Start(config.Respondd.CollectInterval.Duration)
defer collector.Close() defer collector.Close()
} }

View File

@ -11,12 +11,17 @@ synchronize = "1m"
collect_interval = "1m" collect_interval = "1m"
# interface that has an IP in your mesh network # interface that has an IP in your mesh network
interfaces = ["br-ffhb"] interfaces = ["br-ffhb"]
# list of sites to save stats for (empty for global only)
sites = []
# define a port to listen # define a port to listen
# if not set or set to 0 the kernel will use a random free port at its own # if not set or set to 0 the kernel will use a random free port at its own
#port = 10001 #port = 10001
# table of a site to save stats for (not exists for global only)
#[respondd.sites.example]
## list of domains on this site to save stats for (empty for global only)
#domains = []
## example
[respondd.sites.ffhb]
domains = ["city"]
# A little build-in webserver, which statically serves a directory. # A little build-in webserver, which statically serves a directory.
# This is useful for testing purposes or for a little standalone installation. # This is useful for testing purposes or for a little standalone installation.
@ -56,6 +61,14 @@ offline_after = "10m"
# List of site_codes of nodes that should be included in the output # List of site_codes of nodes that should be included in the output
#sites = ["ffhb"] #sites = ["ffhb"]
# #
# replace the site_code with the domain_code in this output
# e.g. site_code='ffhb',domain_code='city' => site_code='city', domain_code=''
#domain_as_site = true
#
# append on the site_code the domain_code with a '.' in this output
# e.g. site_code='ffhb',domain_code='city' => site_code='ffhb.city', domain_code=''
#domain_append_site = true
#
# set has_location to true if you want to include only nodes that have geo-coordinates set # set has_location to true if you want to include only nodes that have geo-coordinates set
# (setting this to false has no sensible effect, unless you'd want to hide nodes that have coordinates) # (setting this to false has no sensible effect, unless you'd want to hide nodes that have coordinates)
#has_location = true #has_location = true

View File

@ -43,7 +43,8 @@ type Owner struct {
// System struct // System struct
type System struct { type System struct {
SiteCode string `json:"site_code,omitempty"` SiteCode string `json:"site_code,omitempty"`
DomainCode string `json:"domain_code,omitempty"`
} }
// Location struct // Location struct

View File

@ -60,9 +60,9 @@ func (conn *Connection) InsertLink(link *runtime.Link, time time.Time) {
} }
} }
func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) {
for _, item := range conn.list { for _, item := range conn.list {
item.InsertGlobals(stats, time, site) item.InsertGlobals(stats, time, site, domain)
} }
} }

View File

@ -15,7 +15,7 @@ type Connection interface {
InsertLink(*runtime.Link, time.Time) InsertLink(*runtime.Link, time.Time)
// InsertGlobals stores global statistics // InsertGlobals stores global statistics
InsertGlobals(*runtime.GlobalStats, time.Time, string) InsertGlobals(*runtime.GlobalStats, time.Time, string, string)
// PruneNodes prunes historical per-node data // PruneNodes prunes historical per-node data
PruneNodes(deleteAfter time.Duration) PruneNodes(deleteAfter time.Duration)

View File

@ -7,7 +7,7 @@ import (
"github.com/fgrosse/graphigo" "github.com/fgrosse/graphigo"
) )
func (c *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { func (c *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) {
measurementGlobal := MeasurementGlobal measurementGlobal := MeasurementGlobal
counterMeasurementModel := CounterMeasurementModel counterMeasurementModel := CounterMeasurementModel
counterMeasurementFirmware := CounterMeasurementFirmware counterMeasurementFirmware := CounterMeasurementFirmware
@ -20,6 +20,13 @@ func (c *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, s
counterMeasurementAutoupdater += "_" + site counterMeasurementAutoupdater += "_" + site
} }
if domain != runtime.GLOBAL_DOMAIN {
measurementGlobal += "_" + domain
counterMeasurementModel += "_" + domain
counterMeasurementFirmware += "_" + domain
counterMeasurementAutoupdater += "_" + domain
}
c.addPoint(GlobalStatsFields(measurementGlobal, stats)) c.addPoint(GlobalStatsFields(measurementGlobal, stats))
c.addCounterMap(counterMeasurementModel, stats.Models, time) c.addCounterMap(counterMeasurementModel, stats.Models, time)
c.addCounterMap(counterMeasurementFirmware, stats.Firmwares, time) c.addCounterMap(counterMeasurementFirmware, stats.Firmwares, time)

View File

@ -8,8 +8,8 @@ import (
) )
// InsertGlobals implementation of database // InsertGlobals implementation of database
func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) {
var tags models.Tags tags := models.Tags{}
measurementGlobal := MeasurementGlobal measurementGlobal := MeasurementGlobal
counterMeasurementModel := CounterMeasurementModel counterMeasurementModel := CounterMeasurementModel
@ -17,20 +17,26 @@ func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time
counterMeasurementAutoupdater := CounterMeasurementAutoupdater counterMeasurementAutoupdater := CounterMeasurementAutoupdater
if site != runtime.GLOBAL_SITE { if site != runtime.GLOBAL_SITE {
tags = models.Tags{ tags.Set([]byte("site"), []byte(site))
models.Tag{Key: []byte("site"), Value: []byte(site)},
}
measurementGlobal += "_site" measurementGlobal += "_site"
counterMeasurementModel += "_site" counterMeasurementModel += "_site"
counterMeasurementFirmware += "_site" counterMeasurementFirmware += "_site"
counterMeasurementAutoupdater += "_site" counterMeasurementAutoupdater += "_site"
} }
if domain != runtime.GLOBAL_DOMAIN {
tags.Set([]byte("domain"), []byte(domain))
measurementGlobal += "_domain"
counterMeasurementModel += "_domain"
counterMeasurementFirmware += "_domain"
counterMeasurementAutoupdater += "_domain"
}
conn.addPoint(measurementGlobal, tags, GlobalStatsFields(stats), time) conn.addPoint(measurementGlobal, tags, GlobalStatsFields(stats), time)
conn.addCounterMap(counterMeasurementModel, stats.Models, time, site) conn.addCounterMap(counterMeasurementModel, stats.Models, time, site, domain)
conn.addCounterMap(counterMeasurementFirmware, stats.Firmwares, time, site) conn.addCounterMap(counterMeasurementFirmware, stats.Firmwares, time, site, domain)
conn.addCounterMap(counterMeasurementAutoupdater, stats.Autoupdater, time, site) conn.addCounterMap(counterMeasurementAutoupdater, stats.Autoupdater, time, site, domain)
} }
// GlobalStatsFields returns fields for InfluxDB // GlobalStatsFields returns fields for InfluxDB
@ -48,13 +54,14 @@ func GlobalStatsFields(stats *runtime.GlobalStats) map[string]interface{} {
// Saves the values of a CounterMap in the database. // Saves the values of a CounterMap in the database.
// The key are used as 'value' tag. // The key are used as 'value' tag.
// The value is used as 'counter' field. // The value is used as 'counter' field.
func (conn *Connection) addCounterMap(name string, m runtime.CounterMap, t time.Time, site string) { func (conn *Connection) addCounterMap(name string, m runtime.CounterMap, t time.Time, site string, domain string) {
for key, count := range m { for key, count := range m {
conn.addPoint( conn.addPoint(
name, name,
models.Tags{ models.Tags{
models.Tag{Key: []byte("value"), Value: []byte(key)}, models.Tag{Key: []byte("value"), Value: []byte(key)},
models.Tag{Key: []byte("site"), Value: []byte(site)}, models.Tag{Key: []byte("site"), Value: []byte(site)},
models.Tag{Key: []byte("domain"), Value: []byte(domain)},
}, },
models.Fields{"count": count}, models.Fields{"count": count},
t, t,

View File

@ -12,18 +12,24 @@ import (
"github.com/FreifunkBremen/yanic/runtime" "github.com/FreifunkBremen/yanic/runtime"
) )
const TEST_SITE = "ffxx" const (
TEST_SITE = "ffhb"
TEST_DOMAIN = "city"
)
func TestGlobalStats(t *testing.T) { func TestGlobalStats(t *testing.T) {
stats := runtime.NewGlobalStats(createTestNodes(), []string{TEST_SITE}) stats := runtime.NewGlobalStats(createTestNodes(), map[string][]string{TEST_SITE: {TEST_DOMAIN}})
assert := assert.New(t) assert := assert.New(t)
// check SITE_GLOBAL fields // check SITE_GLOBAL fields
fields := GlobalStatsFields(stats[runtime.GLOBAL_SITE]) fields := GlobalStatsFields(stats[runtime.GLOBAL_SITE][runtime.GLOBAL_DOMAIN])
assert.EqualValues(3, fields["nodes"]) assert.EqualValues(3, fields["nodes"])
fields = GlobalStatsFields(stats[TEST_SITE]) fields = GlobalStatsFields(stats[TEST_SITE][runtime.GLOBAL_DOMAIN])
assert.EqualValues(2, fields["nodes"])
fields = GlobalStatsFields(stats[TEST_SITE][TEST_DOMAIN])
assert.EqualValues(1, fields["nodes"]) assert.EqualValues(1, fields["nodes"])
conn := &Connection{ conn := &Connection{
@ -32,59 +38,80 @@ func TestGlobalStats(t *testing.T) {
global := 0 global := 0
globalSite := 0 globalSite := 0
globalDomain := 0
model := 0 model := 0
modelSite := 0 modelSite := 0
modelDomain := 0
firmware := 0 firmware := 0
firmwareSite := 0 firmwareSite := 0
firmwareDomain := 0
autoupdater := 0 autoupdater := 0
autoupdaterSite := 0 autoupdaterSite := 0
autoupdaterDomain := 0
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(9) wg.Add(15)
go func() { go func() {
for p := range conn.points { for p := range conn.points {
switch p.Name() { switch p.Name() {
case MeasurementGlobal: case MeasurementGlobal:
global++ global++
break
case "global_site": case "global_site":
globalSite++ globalSite++
break case "global_site_domain":
globalDomain++
case CounterMeasurementModel: case CounterMeasurementModel:
model++ model++
break
case "model_site": case "model_site":
modelSite++ modelSite++
break case "model_site_domain":
modelDomain++
case CounterMeasurementFirmware: case CounterMeasurementFirmware:
firmware++ firmware++
break
case "firmware_site": case "firmware_site":
firmwareSite++ firmwareSite++
break case "firmware_site_domain":
firmwareDomain++
case CounterMeasurementAutoupdater: case CounterMeasurementAutoupdater:
autoupdater++ autoupdater++
break
case "autoupdater_site": case "autoupdater_site":
autoupdaterSite++ autoupdaterSite++
break case "autoupdater_site_domain":
autoupdaterDomain++
default: default:
assert.Equal("invalid p.Name found", p.Name()) assert.Equal("invalid p.Name found", p.Name())
} }
wg.Done() wg.Done()
} }
}() }()
for site, stat := range stats { for site, domains := range stats {
conn.InsertGlobals(stat, time.Now(), site) for domain, stat := range domains {
conn.InsertGlobals(stat, time.Now(), site, domain)
}
} }
wg.Wait() wg.Wait()
assert.Equal(1, global) assert.Equal(1, global)
assert.Equal(1, globalSite) assert.Equal(1, globalSite)
assert.Equal(1, globalDomain)
assert.Equal(2, model) assert.Equal(2, model)
assert.Equal(1, modelSite) assert.Equal(2, modelSite)
assert.Equal(1, modelDomain)
assert.Equal(1, firmware) assert.Equal(1, firmware)
assert.Equal(0, firmwareSite) assert.Equal(1, firmwareSite)
assert.Equal(0, firmwareDomain)
assert.Equal(2, autoupdater) assert.Equal(2, autoupdater)
assert.Equal(1, autoupdaterSite) assert.Equal(2, autoupdaterSite)
assert.Equal(1, autoupdaterDomain)
} }
func createTestNodes() *runtime.Nodes { func createTestNodes() *runtime.Nodes {
@ -102,7 +129,9 @@ func createTestNodes() *runtime.Nodes {
Hardware: data.Hardware{ Hardware: data.Hardware{
Model: "TP-Link 841", Model: "TP-Link 841",
}, },
System: data.System{}, System: data.System{
SiteCode: TEST_SITE,
},
}, },
} }
nodeData.Nodeinfo.Software.Firmware.Release = "2016.1.6+entenhausen1" nodeData.Nodeinfo.Software.Firmware.Release = "2016.1.6+entenhausen1"
@ -134,7 +163,8 @@ func createTestNodes() *runtime.Nodes {
Model: "Xeon Multi-Core", Model: "Xeon Multi-Core",
}, },
System: data.System{ System: data.System{
SiteCode: TEST_SITE, SiteCode: TEST_SITE,
DomainCode: TEST_DOMAIN,
}, },
}, },
}) })

View File

@ -52,6 +52,9 @@ func (conn *Connection) InsertNode(node *runtime.Node) {
if nodeinfo.System.SiteCode != "" { if nodeinfo.System.SiteCode != "" {
tags.SetString("site", nodeinfo.System.SiteCode) tags.SetString("site", nodeinfo.System.SiteCode)
} }
if nodeinfo.System.DomainCode != "" {
tags.SetString("domain", nodeinfo.System.DomainCode)
}
if owner := nodeinfo.Owner; owner != nil { if owner := nodeinfo.Owner; owner != nil {
tags.SetString("owner", owner.Contact) tags.SetString("owner", owner.Contact)
} }

View File

@ -52,7 +52,8 @@ func TestToInflux(t *testing.T) {
Contact: "nobody", Contact: "nobody",
}, },
System: data.System{ System: data.System{
SiteCode: "ffxx", SiteCode: "ffhb",
DomainCode: "city",
}, },
Wireless: &data.Wireless{ Wireless: &data.Wireless{
TxPower24: 3, TxPower24: 3,
@ -133,7 +134,8 @@ func TestToInflux(t *testing.T) {
assert.EqualValues("deadbeef", tags["nodeid"]) assert.EqualValues("deadbeef", tags["nodeid"])
assert.EqualValues("nobody", tags["owner"]) assert.EqualValues("nobody", tags["owner"])
assert.EqualValues("testing", tags["autoupdater"]) assert.EqualValues("testing", tags["autoupdater"])
assert.EqualValues("ffxx", tags["site"]) assert.EqualValues("ffhb", tags["site"])
assert.EqualValues("city", tags["domain"])
assert.EqualValues(0.5, fields["load"]) assert.EqualValues(0.5, fields["load"])
assert.EqualValues(0, fields["neighbours.lldp"]) assert.EqualValues(0, fields["neighbours.lldp"])
assert.EqualValues(1, fields["neighbours.batadv"]) assert.EqualValues(1, fields["neighbours.batadv"])

View File

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

View File

@ -43,7 +43,7 @@ func TestStart(t *testing.T) {
assert.Contains(string(dat), "InsertLink") assert.Contains(string(dat), "InsertLink")
assert.NotContains(string(dat), "InsertGlobals") assert.NotContains(string(dat), "InsertGlobals")
conn.InsertGlobals(&runtime.GlobalStats{}, time.Now(), runtime.GLOBAL_SITE) conn.InsertGlobals(&runtime.GlobalStats{}, time.Now(), runtime.GLOBAL_SITE, runtime.GLOBAL_DOMAIN)
dat, _ = ioutil.ReadFile(path) dat, _ = ioutil.ReadFile(path)
assert.Contains(string(dat), "InsertGlobals") assert.Contains(string(dat), "InsertGlobals")

View File

@ -85,7 +85,7 @@ func (conn *Connection) InsertNode(node *runtime.Node) {
func (conn *Connection) InsertLink(link *runtime.Link, time time.Time) { func (conn *Connection) InsertLink(link *runtime.Link, time time.Time) {
} }
func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) {
} }
func (conn *Connection) PruneNodes(deleteAfter time.Duration) { func (conn *Connection) PruneNodes(deleteAfter time.Duration) {

View File

@ -15,8 +15,9 @@ enable = true
# synchronize = "1m" # synchronize = "1m"
collect_interval = "1m" collect_interval = "1m"
interfaces = ["br-ffhb"] interfaces = ["br-ffhb"]
sites = ["ffhb"]
#port = 10001 #port = 10001
#[respondd.sites.example]
#domains = ["city"]
``` ```
{% endmethod %} {% endmethod %}
@ -64,16 +65,6 @@ interfaces = ["br-ffhb"]
{% endmethod %} {% endmethod %}
### sites
{% method %}
List of sites to save stats for (empty for global only)
{% sample lang="toml" %}
```toml
sites = ["ffhb"]
```
{% endmethod %}
### port ### port
{% method %} {% method %}
Define a port to listen and send the respondd packages. Define a port to listen and send the respondd packages.
@ -84,6 +75,25 @@ port = 10001
``` ```
{% endmethod %} {% endmethod %}
### [respondd.sites.example]
{% method %}
Tables of sites to save stats for (not exists for global only).
Here is the site _ffhb_.
{% sample lang="toml" %}
```toml
[respondd.sites.ffhb]
domains = ["city"]
```
{% endmethod %}
#### domains
{% method %}
list of domains on this site to save stats for (empty for global only)
{% sample lang="toml" %}
```toml
domains = ["city"]
```
{% endmethod %}
## [webserver] ## [webserver]
@ -197,6 +207,8 @@ enable = true
no_owner = true no_owner = true
blacklist = ["00112233445566", "1337f0badead"] blacklist = ["00112233445566", "1337f0badead"]
sites = ["ffhb"] sites = ["ffhb"]
domain_as_site = true
domain_append_site = true
has_location = true has_location = true
[nodes.output.example.filter.in_area] [nodes.output.example.filter.in_area]
latitude_min = 34.30 latitude_min = 34.30
@ -258,6 +270,35 @@ blacklist = ["00112233445566", "1337f0badead"]
{% endmethod %} {% endmethod %}
### sites
{% method %}
List of site_codes of nodes that should be included in output
{% sample lang="toml" %}
```toml
sites = ["ffhb"]
```
{% endmethod %}
### domain_as_site
{% method %}
Replace the `site_code` with the `domain_code` in this output.
e.g. `site_code='ffhb',domain_code='city'` becomes `site_code='city', domain_code=''`
{% sample lang="toml" %}
```toml
domain_as_site = true
```
{% endmethod %}
### domain_append_site
{% method %}
Append on the `site_code` the `domain_code` with a `.` in this output.
e.g. `site_code='ffhb',domain_code='city'` becomes `site_code='ffhb.city', domain_code=''`
{% sample lang="toml" %}
```toml
domain_append_site = true
```
{% endmethod %}
### sites ### sites
{% method %} {% method %}
List of site_codes of nodes that should be included in output List of site_codes of nodes that should be included in output

View File

@ -2,6 +2,8 @@ package all
import ( import (
_ "github.com/FreifunkBremen/yanic/output/filter/blacklist" _ "github.com/FreifunkBremen/yanic/output/filter/blacklist"
_ "github.com/FreifunkBremen/yanic/output/filter/domainappendsite"
_ "github.com/FreifunkBremen/yanic/output/filter/domainassite"
_ "github.com/FreifunkBremen/yanic/output/filter/haslocation" _ "github.com/FreifunkBremen/yanic/output/filter/haslocation"
_ "github.com/FreifunkBremen/yanic/output/filter/inarea" _ "github.com/FreifunkBremen/yanic/output/filter/inarea"
_ "github.com/FreifunkBremen/yanic/output/filter/noowner" _ "github.com/FreifunkBremen/yanic/output/filter/noowner"

View File

@ -0,0 +1,50 @@
package domainappendsite
import (
"errors"
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/output/filter"
"github.com/FreifunkBremen/yanic/runtime"
)
type domainAppendSite struct{ set bool }
func init() {
filter.Register("domain_append_site", build)
}
func build(config interface{}) (filter.Filter, error) {
if value, ok := config.(bool); ok {
return &domainAppendSite{set: value}, nil
}
return nil, errors.New("invalid configuration, boolean expected")
}
func (config *domainAppendSite) Apply(node *runtime.Node) *runtime.Node {
if nodeinfo := node.Nodeinfo; nodeinfo != nil && config.set && nodeinfo.System.DomainCode != "" {
node = &runtime.Node{
Address: node.Address,
Firstseen: node.Firstseen,
Lastseen: node.Lastseen,
Online: node.Online,
Statistics: node.Statistics,
Nodeinfo: &data.NodeInfo{
NodeID: nodeinfo.NodeID,
Network: nodeinfo.Network,
System: data.System{
SiteCode: nodeinfo.System.SiteCode + "." + nodeinfo.System.DomainCode,
},
Owner: nodeinfo.Owner,
Hostname: nodeinfo.Hostname,
Location: nodeinfo.Location,
Software: nodeinfo.Software,
Hardware: nodeinfo.Hardware,
VPN: nodeinfo.VPN,
Wireless: nodeinfo.Wireless,
},
Neighbours: node.Neighbours,
}
}
return node
}

View File

@ -0,0 +1,43 @@
package domainappendsite
import (
"testing"
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/stretchr/testify/assert"
)
func TestFilter(t *testing.T) {
assert := assert.New(t)
// invalid config
filter, err := build("nope")
assert.Error(err)
// delete owner by configuration
filter, _ = build(true)
n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
System: data.System{
SiteCode: "ffhb",
DomainCode: "city",
},
}})
assert.NotNil(n)
assert.Equal("ffhb.city", n.Nodeinfo.System.SiteCode)
assert.Equal("", n.Nodeinfo.System.DomainCode)
// keep owner configuration
filter, _ = build(false)
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
System: data.System{
SiteCode: "ffhb",
DomainCode: "city",
},
}})
assert.NotNil(n)
assert.Equal("ffhb", n.Nodeinfo.System.SiteCode)
assert.Equal("city", n.Nodeinfo.System.DomainCode)
}

View File

@ -0,0 +1,50 @@
package domainassite
import (
"errors"
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/output/filter"
"github.com/FreifunkBremen/yanic/runtime"
)
type domainAsSite struct{ set bool }
func init() {
filter.Register("domain_as_site", build)
}
func build(config interface{}) (filter.Filter, error) {
if value, ok := config.(bool); ok {
return &domainAsSite{set: value}, nil
}
return nil, errors.New("invalid configuration, boolean expected")
}
func (config *domainAsSite) Apply(node *runtime.Node) *runtime.Node {
if nodeinfo := node.Nodeinfo; nodeinfo != nil && config.set && nodeinfo.System.DomainCode != "" {
node = &runtime.Node{
Address: node.Address,
Firstseen: node.Firstseen,
Lastseen: node.Lastseen,
Online: node.Online,
Statistics: node.Statistics,
Nodeinfo: &data.NodeInfo{
NodeID: nodeinfo.NodeID,
Network: nodeinfo.Network,
System: data.System{
SiteCode: nodeinfo.System.DomainCode,
},
Owner: nodeinfo.Owner,
Hostname: nodeinfo.Hostname,
Location: nodeinfo.Location,
Software: nodeinfo.Software,
Hardware: nodeinfo.Hardware,
VPN: nodeinfo.VPN,
Wireless: nodeinfo.Wireless,
},
Neighbours: node.Neighbours,
}
}
return node
}

View File

@ -0,0 +1,43 @@
package domainassite
import (
"testing"
"github.com/FreifunkBremen/yanic/data"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/stretchr/testify/assert"
)
func TestFilter(t *testing.T) {
assert := assert.New(t)
// invalid config
filter, err := build("nope")
assert.Error(err)
// delete owner by configuration
filter, _ = build(true)
n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
System: data.System{
SiteCode: "ffhb",
DomainCode: "city",
},
}})
assert.NotNil(n)
assert.Equal("city", n.Nodeinfo.System.SiteCode)
assert.Equal("", n.Nodeinfo.System.DomainCode)
// keep owner configuration
filter, _ = build(false)
n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{
System: data.System{
SiteCode: "ffhb",
DomainCode: "city",
},
}})
assert.NotNil(n)
assert.Equal("ffhb", n.Nodeinfo.System.SiteCode)
assert.Equal("city", n.Nodeinfo.System.DomainCode)
}

View File

@ -33,6 +33,7 @@ type Node struct {
MAC string `json:"mac"` MAC string `json:"mac"`
Addresses []string `json:"addresses"` Addresses []string `json:"addresses"`
SiteCode string `json:"site_code,omitempty"` SiteCode string `json:"site_code,omitempty"`
DomainCode string `json:"-"`
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
Owner string `json:"owner,omitempty"` Owner string `json:"owner,omitempty"`
Location *Location `json:"location,omitempty"` Location *Location `json:"location,omitempty"`
@ -85,6 +86,7 @@ func NewNode(nodes *runtime.Nodes, n *runtime.Node) *Node {
node.MAC = nodeinfo.Network.Mac node.MAC = nodeinfo.Network.Mac
node.Addresses = nodeinfo.Network.Addresses node.Addresses = nodeinfo.Network.Addresses
node.SiteCode = nodeinfo.System.SiteCode node.SiteCode = nodeinfo.System.SiteCode
node.DomainCode = nodeinfo.System.DomainCode
node.Hostname = nodeinfo.Hostname node.Hostname = nodeinfo.Hostname
if owner := nodeinfo.Owner; owner != nil { if owner := nodeinfo.Owner; owner != nil {
node.Owner = owner.Contact node.Owner = owner.Contact

View File

@ -21,25 +21,25 @@ type Collector struct {
ifaceToConn map[string]*net.UDPConn // map from interface name to UDP socket ifaceToConn map[string]*net.UDPConn // map from interface name to UDP socket
port int port int
queue chan *Response // received responses queue chan *Response // received responses
db database.Connection db database.Connection
nodes *runtime.Nodes nodes *runtime.Nodes
sites []string sitesDomains map[string][]string
interval time.Duration // Interval for multicast packets interval time.Duration // Interval for multicast packets
stop chan interface{} stop chan interface{}
} }
// NewCollector creates a Collector struct // NewCollector creates a Collector struct
func NewCollector(db database.Connection, nodes *runtime.Nodes, sites []string, ifaces []string, port int) *Collector { func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map[string][]string, ifaces []string, port int) *Collector {
coll := &Collector{ coll := &Collector{
db: db, db: db,
nodes: nodes, nodes: nodes,
sites: sites, sitesDomains: sitesDomains,
port: port, port: port,
queue: make(chan *Response, 400), queue: make(chan *Response, 400),
stop: make(chan interface{}), stop: make(chan interface{}),
ifaceToConn: make(map[string]*net.UDPConn), ifaceToConn: make(map[string]*net.UDPConn),
} }
for _, iface := range ifaces { for _, iface := range ifaces {
@ -302,9 +302,11 @@ func (coll *Collector) globalStatsWorker() {
// saves global statistics // saves global statistics
func (coll *Collector) saveGlobalStats() { func (coll *Collector) saveGlobalStats() {
stats := runtime.NewGlobalStats(coll.nodes, coll.sites) stats := runtime.NewGlobalStats(coll.nodes, coll.sitesDomains)
for site, stat := range stats { for site, domains := range stats {
coll.db.InsertGlobals(stat, time.Now(), site) for domain, stat := range domains {
coll.db.InsertGlobals(stat, time.Now(), site, domain)
}
} }
} }

View File

@ -9,12 +9,15 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const SITE_TEST = "ffxx" const (
SITE_TEST = "ffhb"
DOMAIN_TEST = "city"
)
func TestCollector(t *testing.T) { func TestCollector(t *testing.T) {
nodes := runtime.NewNodes(&runtime.NodesConfig{}) nodes := runtime.NewNodes(&runtime.NodesConfig{})
collector := NewCollector(nil, nodes, []string{SITE_TEST}, []string{}, 10001) collector := NewCollector(nil, nodes, map[string][]string{SITE_TEST: {DOMAIN_TEST}}, []string{}, 10001)
collector.Start(time.Millisecond) collector.Start(time.Millisecond)
time.Sleep(time.Millisecond * 10) time.Sleep(time.Millisecond * 10)
collector.Close() collector.Close()

View File

@ -3,10 +3,22 @@ package respond
import "github.com/FreifunkBremen/yanic/lib/duration" import "github.com/FreifunkBremen/yanic/lib/duration"
type Config struct { type Config struct {
Enable bool `toml:"enable"` Enable bool `toml:"enable"`
Synchronize duration.Duration `toml:"synchronize"` Synchronize duration.Duration `toml:"synchronize"`
Interfaces []string `toml:"interfaces"` Interfaces []string `toml:"interfaces"`
Sites []string `toml:"sites"` Sites map[string]SiteConfig `toml:"sites"`
Port int `toml:"port"` Port int `toml:"port"`
CollectInterval duration.Duration `toml:"collect_interval"` CollectInterval duration.Duration `toml:"collect_interval"`
}
func (c *Config) SitesDomains() (result map[string][]string) {
result = make(map[string][]string)
for site, siteConfig := range c.Sites {
result[site] = siteConfig.Domains
}
return
}
type SiteConfig struct {
Domains []string `toml:"domains"`
} }

24
respond/config_test.go Normal file
View File

@ -0,0 +1,24 @@
package respond
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSitesDomainsConfigTransform(t *testing.T) {
assert := assert.New(t)
c := Config{
Sites: map[string]SiteConfig{
"ffhb": {Domains: []string{"city"}},
},
}
result := c.SitesDomains()
assert.Len(result, 1)
assert.Contains(result, "ffhb")
domains := result["ffhb"]
assert.Len(domains, 1)
assert.Equal("city", domains[0])
}

View File

@ -3,6 +3,7 @@ package runtime
const ( const (
DISABLED_AUTOUPDATER = "disabled" DISABLED_AUTOUPDATER = "disabled"
GLOBAL_SITE = "global" GLOBAL_SITE = "global"
GLOBAL_DOMAIN = "global"
) )
// CounterMap to manage multiple values // CounterMap to manage multiple values
@ -23,32 +24,45 @@ type GlobalStats struct {
} }
//NewGlobalStats returns global statistics for InfluxDB //NewGlobalStats returns global statistics for InfluxDB
func NewGlobalStats(nodes *Nodes, sites []string) (result map[string]*GlobalStats) { func NewGlobalStats(nodes *Nodes, sitesDomains map[string][]string) (result map[string]map[string]*GlobalStats) {
result = make(map[string]*GlobalStats) result = make(map[string]map[string]*GlobalStats)
result[GLOBAL_SITE] = &GlobalStats{ result[GLOBAL_SITE] = make(map[string]*GlobalStats)
result[GLOBAL_SITE][GLOBAL_DOMAIN] = &GlobalStats{
Firmwares: make(CounterMap), Firmwares: make(CounterMap),
Models: make(CounterMap), Models: make(CounterMap),
Autoupdater: make(CounterMap), Autoupdater: make(CounterMap),
} }
for _, site := range sites { for site, domains := range sitesDomains {
result[site] = &GlobalStats{ result[site] = make(map[string]*GlobalStats)
result[site][GLOBAL_DOMAIN] = &GlobalStats{
Firmwares: make(CounterMap), Firmwares: make(CounterMap),
Models: make(CounterMap), Models: make(CounterMap),
Autoupdater: make(CounterMap), Autoupdater: make(CounterMap),
} }
for _, domain := range domains {
result[site][domain] = &GlobalStats{
Firmwares: make(CounterMap),
Models: make(CounterMap),
Autoupdater: make(CounterMap),
}
}
} }
nodes.RLock() nodes.RLock()
for _, node := range nodes.List { for _, node := range nodes.List {
if node.Online { if node.Online {
result[GLOBAL_SITE].Add(node) result[GLOBAL_SITE][GLOBAL_DOMAIN].Add(node)
if info := node.Nodeinfo; info != nil { if info := node.Nodeinfo; info != nil {
site := info.System.SiteCode site := info.System.SiteCode
if _, exist := result[site]; exist { domain := info.System.DomainCode
result[site].Add(node) if _, ok := result[site]; ok {
result[site][GLOBAL_DOMAIN].Add(node)
if _, ok := result[site][domain]; ok {
result[site][domain].Add(node)
}
} }
} }
} }

View File

@ -8,49 +8,70 @@ import (
"github.com/FreifunkBremen/yanic/data" "github.com/FreifunkBremen/yanic/data"
) )
const TEST_SITE = "ffxx" const (
TEST_SITE = "ffhb"
TEST_DOMAIN = "city"
)
func TestGlobalStats(t *testing.T) { func TestGlobalStats(t *testing.T) {
stats := NewGlobalStats(createTestNodes(), []string{TEST_SITE}) stats := NewGlobalStats(createTestNodes(), map[string][]string{TEST_SITE: {TEST_DOMAIN}})
assert := assert.New(t) assert := assert.New(t)
assert.Len(stats, 2) assert.Len(stats, 2)
//check GLOBAL_SITE stats //check GLOBAL_SITE stats
assert.EqualValues(1, stats[GLOBAL_SITE].Gateways) assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Gateways)
assert.EqualValues(3, stats[GLOBAL_SITE].Nodes) assert.EqualValues(3, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Nodes)
assert.EqualValues(25, stats[GLOBAL_SITE].Clients) assert.EqualValues(25, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Clients)
// check models // check models
assert.Len(stats[GLOBAL_SITE].Models, 2) assert.Len(stats[GLOBAL_SITE][GLOBAL_DOMAIN].Models, 2)
assert.EqualValues(2, stats[GLOBAL_SITE].Models["TP-Link 841"]) assert.EqualValues(2, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Models["TP-Link 841"])
assert.EqualValues(1, stats[GLOBAL_SITE].Models["Xeon Multi-Core"]) assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Models["Xeon Multi-Core"])
// check firmwares // check firmwares
assert.Len(stats[GLOBAL_SITE].Firmwares, 1) assert.Len(stats[GLOBAL_SITE][GLOBAL_DOMAIN].Firmwares, 1)
assert.EqualValues(1, stats[GLOBAL_SITE].Firmwares["2016.1.6+entenhausen1"]) assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Firmwares["2016.1.6+entenhausen1"])
// check autoupdater // check autoupdater
assert.Len(stats[GLOBAL_SITE].Autoupdater, 2) assert.Len(stats[GLOBAL_SITE][GLOBAL_DOMAIN].Autoupdater, 2)
assert.EqualValues(1, stats[GLOBAL_SITE].Autoupdater["stable"]) assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Autoupdater["stable"])
// check TEST_SITE stats // check TEST_SITE stats
assert.EqualValues(1, stats[TEST_SITE].Gateways) assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Gateways)
assert.EqualValues(2, stats[TEST_SITE].Nodes) assert.EqualValues(2, stats[TEST_SITE][GLOBAL_DOMAIN].Nodes)
assert.EqualValues(23, stats[TEST_SITE].Clients) assert.EqualValues(23, stats[TEST_SITE][GLOBAL_DOMAIN].Clients)
// check models // check models
assert.Len(stats[TEST_SITE].Models, 2) assert.Len(stats[TEST_SITE][GLOBAL_DOMAIN].Models, 2)
assert.EqualValues(1, stats[TEST_SITE].Models["TP-Link 841"]) assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Models["TP-Link 841"])
assert.EqualValues(1, stats[TEST_SITE].Models["Xeon Multi-Core"]) assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Models["Xeon Multi-Core"])
// check firmwares // check firmwares
assert.Len(stats[TEST_SITE].Firmwares, 1) assert.Len(stats[TEST_SITE][GLOBAL_DOMAIN].Firmwares, 1)
assert.EqualValues(1, stats[TEST_SITE].Firmwares["2016.1.6+entenhausen1"]) assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Firmwares["2016.1.6+entenhausen1"])
// check autoupdater // check autoupdater
assert.Len(stats[TEST_SITE].Autoupdater, 1) assert.Len(stats[TEST_SITE][GLOBAL_DOMAIN].Autoupdater, 1)
assert.EqualValues(0, stats[TEST_SITE].Autoupdater["stable"]) assert.EqualValues(0, stats[TEST_SITE][GLOBAL_DOMAIN].Autoupdater["stable"])
// check TEST_DOMAIN stats
assert.EqualValues(1, stats[TEST_SITE][TEST_DOMAIN].Gateways)
assert.EqualValues(1, stats[TEST_SITE][TEST_DOMAIN].Nodes)
assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Clients)
// check models
assert.Len(stats[TEST_SITE][TEST_DOMAIN].Models, 1)
assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Models["TP-Link 841"])
assert.EqualValues(1, stats[TEST_SITE][TEST_DOMAIN].Models["Xeon Multi-Core"])
// check firmwares
assert.Len(stats[TEST_SITE][TEST_DOMAIN].Firmwares, 0)
assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Firmwares["2016.1.6+entenhausen1"])
// check autoupdater
assert.Len(stats[TEST_SITE][TEST_DOMAIN].Autoupdater, 1)
assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Autoupdater["stable"])
} }
func createTestNodes() *Nodes { func createTestNodes() *Nodes {
@ -109,7 +130,8 @@ func createTestNodes() *Nodes {
Model: "Xeon Multi-Core", Model: "Xeon Multi-Core",
}, },
System: data.System{ System: data.System{
SiteCode: TEST_SITE, SiteCode: TEST_SITE,
DomainCode: TEST_DOMAIN,
}, },
}, },
}) })