[TASK] add respond(d) daemon - WIP
This commit is contained in:
parent
9d836f87c2
commit
d12e62ee7b
|
@ -24,5 +24,6 @@ _testmain.go
|
|||
*.prof
|
||||
webroot
|
||||
/config.toml
|
||||
/config-respondd.toml
|
||||
/vendor
|
||||
/bin
|
|
@ -1,54 +1,31 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/naoina/toml"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/database"
|
||||
"github.com/FreifunkBremen/yanic/respond"
|
||||
"github.com/FreifunkBremen/yanic/runtime"
|
||||
"github.com/FreifunkBremen/yanic/webserver"
|
||||
)
|
||||
|
||||
// Config represents the whole configuration
|
||||
type Config struct {
|
||||
Respondd respond.Config
|
||||
Webserver webserver.Config
|
||||
Nodes runtime.NodesConfig
|
||||
Database database.Config
|
||||
}
|
||||
|
||||
var (
|
||||
configPath string
|
||||
collector *respond.Collector
|
||||
nodes *runtime.Nodes
|
||||
)
|
||||
|
||||
func loadConfig() *Config {
|
||||
config, err := ReadConfigFile(configPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "unable to load config file:", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// ReadConfigFile reads a config model from path of a yml file
|
||||
func ReadConfigFile(path string) (config *Config, err error) {
|
||||
config = &Config{}
|
||||
|
||||
func ReadConfigFile(path string, config interface{}) error {
|
||||
file, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
err = toml.Unmarshal(file, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ import (
|
|||
func TestReadConfig(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
config, err := ReadConfigFile("../config_example.toml")
|
||||
config := &ServeConfig{}
|
||||
err := ReadConfigFile("../config_example.toml", config)
|
||||
|
||||
assert.NoError(err)
|
||||
assert.NotNil(config)
|
||||
|
||||
|
@ -40,11 +42,11 @@ func TestReadConfig(t *testing.T) {
|
|||
},
|
||||
}, meshviewer)
|
||||
|
||||
_, err = ReadConfigFile("testdata/config_invalid.toml")
|
||||
err = ReadConfigFile("testdata/config_invalid.toml", config)
|
||||
assert.Error(err, "not unmarshalable")
|
||||
assert.Contains(err.Error(), "invalid TOML syntax")
|
||||
|
||||
_, err = ReadConfigFile("testdata/adsa.toml")
|
||||
err = ReadConfigFile("testdata/adsa.toml", config)
|
||||
assert.Error(err, "not found able")
|
||||
assert.Contains(err.Error(), "no such file or directory")
|
||||
}
|
||||
|
|
|
@ -19,7 +19,10 @@ var importCmd = &cobra.Command{
|
|||
path := args[0]
|
||||
site := args[1]
|
||||
domain := args[2]
|
||||
config := loadConfig()
|
||||
config := &ServeConfig{}
|
||||
if err := ReadConfigFile(configPath, config); err != nil {
|
||||
log.Panicf("unable to load config file: %s", err)
|
||||
}
|
||||
|
||||
err := allDatabase.Start(config.Database)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/bdlm/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/respond/daemon"
|
||||
)
|
||||
|
||||
// serveCmd represents the serve command
|
||||
var responddCMD = &cobra.Command{
|
||||
Use: "respondd",
|
||||
Short: "Runs a respond daemon",
|
||||
Example: "yanic respondd --config /etc/respondd.toml",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
daemon := &respondd.Daemon{}
|
||||
if err := ReadConfigFile(configPath, daemon); err != nil {
|
||||
log.Panicf("unable to load config file: %s", err)
|
||||
}
|
||||
|
||||
go daemon.Start()
|
||||
|
||||
log.Info("respondd daemon started")
|
||||
// Wait for INT/TERM
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
sig := <-sigs
|
||||
log.Infof("received %s", sig)
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(responddCMD)
|
||||
responddCMD.Flags().StringVarP(&configPath, "config", "c", "config-respondd.toml", "Path to configuration file")
|
||||
}
|
20
cmd/serve.go
20
cmd/serve.go
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/bdlm/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/database"
|
||||
allDatabase "github.com/FreifunkBremen/yanic/database/all"
|
||||
allOutput "github.com/FreifunkBremen/yanic/output/all"
|
||||
"github.com/FreifunkBremen/yanic/respond"
|
||||
|
@ -16,16 +17,26 @@ import (
|
|||
"github.com/FreifunkBremen/yanic/webserver"
|
||||
)
|
||||
|
||||
// ServeConfig represents the whole configuration
|
||||
type ServeConfig struct {
|
||||
Respondd respond.Config
|
||||
Webserver webserver.Config
|
||||
Nodes runtime.NodesConfig
|
||||
Database database.Config
|
||||
}
|
||||
|
||||
// serveCmd represents the serve command
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Runs the yanic server",
|
||||
Example: "yanic serve --config /etc/yanic.toml",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config := loadConfig()
|
||||
config := &ServeConfig{}
|
||||
if err := ReadConfigFile(configPath, config); err != nil {
|
||||
log.Panicf("unable to load config file: %s", err)
|
||||
}
|
||||
|
||||
err := allDatabase.Start(config.Database)
|
||||
if err != nil {
|
||||
if err := allDatabase.Start(config.Database); err != nil {
|
||||
log.Panicf("could not connect to database: %s", err)
|
||||
}
|
||||
defer allDatabase.Close()
|
||||
|
@ -33,8 +44,7 @@ var serveCmd = &cobra.Command{
|
|||
nodes = runtime.NewNodes(&config.Nodes)
|
||||
nodes.Start()
|
||||
|
||||
err = allOutput.Start(nodes, config.Nodes)
|
||||
if err != nil {
|
||||
if err := allOutput.Start(nodes, config.Nodes); err != nil {
|
||||
log.Panicf("error on init outputs: %s", err)
|
||||
}
|
||||
defer allOutput.Close()
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# how ofter the cache respond of a respondd request is calculated
|
||||
data_interval = "3m"
|
||||
|
||||
# if set true, respond will contain data from batman interface
|
||||
multi_instance = false
|
||||
|
||||
[[listen]]
|
||||
address = "ff02::2:1001"
|
||||
interface = ""
|
||||
port = 1001
|
||||
|
||||
# manuelle data for respond
|
||||
[data.nodeinfo.location]
|
||||
latitude = 53.112446246
|
||||
longitude = 8.734087944
|
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=yanic
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=yanic
|
||||
ExecStart=/opt/go/bin/yanic respondd --config /etc/respondd.conf
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
Environment=PATH=/usr/bin:/usr/local/bin
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -2,8 +2,8 @@ package data
|
|||
|
||||
// ResponseData struct
|
||||
type ResponseData struct {
|
||||
Neighbours *Neighbours `json:"neighbours"`
|
||||
Nodeinfo *Nodeinfo `json:"nodeinfo"`
|
||||
Statistics *Statistics `json:"statistics"`
|
||||
Nodeinfo *Nodeinfo `json:"nodeinfo" toml:"nodeinfo"`
|
||||
Statistics *Statistics `json:"statistics" toml:"statistics"`
|
||||
Neighbours *Neighbours `json:"neighbours" toml:"neighbours"`
|
||||
CustomFields map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package respondd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
)
|
||||
|
||||
func trim(s string) string {
|
||||
return strings.TrimSpace(strings.Trim(s, "\n"))
|
||||
}
|
||||
|
||||
func (d *Daemon) updateData() {
|
||||
nodeID := ""
|
||||
// Nodeinfo
|
||||
if d.Data.Nodeinfo == nil {
|
||||
d.Data.Nodeinfo = &data.Nodeinfo{}
|
||||
} else {
|
||||
nodeID = d.Data.Nodeinfo.NodeID
|
||||
}
|
||||
if d.Data.Nodeinfo.Hostname == "" {
|
||||
d.Data.Nodeinfo.Hostname, _ = os.Hostname()
|
||||
}
|
||||
|
||||
// Statistics
|
||||
if d.Data.Statistics == nil {
|
||||
d.Data.Statistics = &data.Statistics{}
|
||||
} else if nodeID == "" {
|
||||
nodeID = d.Data.Statistics.NodeID
|
||||
}
|
||||
|
||||
// Neighbours
|
||||
if d.Data.Neighbours == nil {
|
||||
d.Data.Neighbours = &data.Neighbours{}
|
||||
} else if nodeID == "" {
|
||||
nodeID = d.Data.Neighbours.NodeID
|
||||
}
|
||||
|
||||
if nodeID == "" && !d.MultiInstance {
|
||||
if v, err := ioutil.ReadFile("/etc/machine-id"); err == nil {
|
||||
nodeID = trim(string(v))[:12]
|
||||
}
|
||||
}
|
||||
d.Data.Nodeinfo.NodeID = nodeID
|
||||
d.Data.Statistics.NodeID = nodeID
|
||||
d.Data.Neighbours.NodeID = nodeID
|
||||
|
||||
for _, data := range d.dataByInterface {
|
||||
data.Nodeinfo = d.Data.Nodeinfo
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) getData(iface string) *data.ResponseData {
|
||||
if !d.MultiInstance {
|
||||
return d.Data
|
||||
}
|
||||
if data, ok := d.dataByInterface[iface]; ok {
|
||||
return data
|
||||
}
|
||||
d.dataByInterface[iface] = &data.ResponseData{}
|
||||
d.updateData()
|
||||
return d.dataByInterface[iface]
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package respondd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"reflect"
|
||||
|
||||
"github.com/bdlm/log"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/respond"
|
||||
)
|
||||
|
||||
func (d *Daemon) handler(socket *net.UDPConn) {
|
||||
socket.SetReadBuffer(respond.MaxDataGramSize)
|
||||
|
||||
// Loop forever reading from the socket
|
||||
for {
|
||||
buf := make([]byte, respond.MaxDataGramSize)
|
||||
n, src, err := socket.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
log.Errorf("ReadFromUDP failed: %s", err)
|
||||
}
|
||||
raw := make([]byte, n)
|
||||
copy(raw, buf)
|
||||
|
||||
get := string(raw)
|
||||
|
||||
data := d.getData(src.Zone)
|
||||
|
||||
log.WithFields(map[string]interface{}{
|
||||
"bytes": n,
|
||||
"data": get,
|
||||
"src": src.String(),
|
||||
}).Debug("recieve request")
|
||||
|
||||
if get[:3] == "GET" {
|
||||
res, err := respond.NewRespone(data, src)
|
||||
if err != nil {
|
||||
log.Errorf("Decode failed: %s", err)
|
||||
continue
|
||||
}
|
||||
n, err = socket.WriteToUDP(res.Raw, res.Address)
|
||||
if err != nil {
|
||||
log.Errorf("WriteToUDP failed: %s", err)
|
||||
continue
|
||||
}
|
||||
log.WithFields(map[string]interface{}{
|
||||
"bytes": n,
|
||||
"dest": res.Address.String(),
|
||||
}).Debug("send respond")
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
|
||||
t := reflect.TypeOf(data).Elem()
|
||||
v := reflect.ValueOf(data).Elem()
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
fv := v.FieldByName(f.Name)
|
||||
if f.Tag.Get("json") == get {
|
||||
log.WithFields(map[string]interface{}{
|
||||
"param": get,
|
||||
"dest": src.String(),
|
||||
}).Debug("found")
|
||||
raw, err = json.Marshal(fv.Interface())
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
log.WithFields(map[string]interface{}{
|
||||
"param": get,
|
||||
"dest": src.String(),
|
||||
}).Debug("not found")
|
||||
raw = []byte("ressource not found")
|
||||
}
|
||||
|
||||
n, err = socket.WriteToUDP(raw, src)
|
||||
if err != nil {
|
||||
log.Errorf("WriteToUDP failed: %s", err)
|
||||
continue
|
||||
}
|
||||
log.WithFields(map[string]interface{}{
|
||||
"bytes": n,
|
||||
"dest": src.String(),
|
||||
}).Debug("send respond")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package respondd
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/bdlm/log"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
"github.com/FreifunkBremen/yanic/lib/duration"
|
||||
)
|
||||
|
||||
type Daemon struct {
|
||||
MultiInstance bool `toml:"multi_instance"`
|
||||
DataInterval duration.Duration `toml:"data_interval"`
|
||||
Listen []struct {
|
||||
Address string `toml:"address"`
|
||||
Interface string `toml:"interface"`
|
||||
Port int `toml:"port"`
|
||||
} `toml:"listen"`
|
||||
Data *data.ResponseData `toml:"data"`
|
||||
dataByInterface map[string]*data.ResponseData
|
||||
}
|
||||
|
||||
func (d *Daemon) Start() {
|
||||
if d.Data == nil {
|
||||
d.Data = &data.ResponseData{}
|
||||
}
|
||||
|
||||
d.updateData()
|
||||
go d.updateWorker()
|
||||
|
||||
for _, listen := range d.Listen {
|
||||
var socket *net.UDPConn
|
||||
var err error
|
||||
addr := net.ParseIP(listen.Address)
|
||||
|
||||
if addr.IsMulticast() {
|
||||
var iface *net.Interface
|
||||
if listen.Interface != "" {
|
||||
iface, err = net.InterfaceByName(listen.Interface)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if socket, err = net.ListenMulticastUDP("udp6", iface, &net.UDPAddr{
|
||||
IP: addr,
|
||||
Port: listen.Port,
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if socket, err = net.ListenUDP("udp6", &net.UDPAddr{
|
||||
IP: addr,
|
||||
Port: listen.Port,
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
go d.handler(socket)
|
||||
}
|
||||
log.Debug("all listener started")
|
||||
}
|
||||
|
||||
func (d *Daemon) updateWorker() {
|
||||
c := time.Tick(d.DataInterval.Duration)
|
||||
|
||||
for range c {
|
||||
d.updateData()
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package respondd
|
Loading…
Reference in New Issue