[TASK] add node model
This commit is contained in:
parent
34ee3c5e62
commit
d954d7c851
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/NYTimes/gziphandler"
|
||||
goji "goji.io"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
|
||||
configPackage "github.com/FreifunkBremen/freifunkmanager/config"
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||
"github.com/FreifunkBremen/freifunkmanager/lib/worker"
|
||||
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||
"github.com/FreifunkBremen/freifunkmanager/yanic"
|
||||
|
@ -34,7 +36,12 @@ func main() {
|
|||
log.Log.Info("starting...")
|
||||
|
||||
sshmanager := ssh.NewManager(config.SSHPrivateKey)
|
||||
nodes := runtime.NewNodes(config.SSHInterface, sshmanager)
|
||||
nodes := runtime.NewNodes(config.StatePath, config.SSHInterface, sshmanager)
|
||||
nodesUpdateWorker := worker.NewWorker(time.Duration(3)*time.Minute, nodes.Updater)
|
||||
nodesSaveWorker := worker.NewWorker(time.Duration(3)*time.Minute, nodes.Saver)
|
||||
|
||||
nodesUpdateWorker.Start()
|
||||
nodesSaveWorker.Start()
|
||||
|
||||
if config.Yanic.Enable {
|
||||
yanicDialer := yanic.Dial(config.Yanic.Type, config.Yanic.Address)
|
||||
|
@ -69,6 +76,8 @@ func main() {
|
|||
if config.Yanic.Enable {
|
||||
yanicDialer.Close()
|
||||
}
|
||||
nodesSaveWorker.Close()
|
||||
nodesUpdateWorker.Close()
|
||||
sshmanager.Close()
|
||||
|
||||
log.Log.Info("stop recieve:", sig)
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
|
||||
//config file of this daemon (for more the config_example.conf in git repository)
|
||||
type Config struct {
|
||||
// prevent crashes
|
||||
StatePath string `toml:"state_path"`
|
||||
// address on which the api and static content webserver runs
|
||||
WebserverBind string `toml:"webserver_bind"`
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// A little lib for cronjobs to run it in background
|
||||
package worker
|
||||
|
||||
import "time"
|
||||
|
||||
// a struct which handle the job
|
||||
type Worker struct {
|
||||
every time.Duration
|
||||
run func()
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// create a new Worker with timestamp run every and his function
|
||||
func NewWorker(every time.Duration, f func()) (w *Worker) {
|
||||
w = &Worker{
|
||||
every: every,
|
||||
run: f,
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// start the worker
|
||||
// please us it as a goroutine: go w.Start()
|
||||
func (w *Worker) Start() {
|
||||
ticker := time.NewTicker(w.every)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
w.run()
|
||||
case <-w.quit:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stop the worker
|
||||
func (w *Worker) Close() {
|
||||
close(w.quit)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package worker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWorker(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
runtime := 0
|
||||
|
||||
w := NewWorker(time.Duration(5)*time.Millisecond, func() {
|
||||
runtime = runtime + 1
|
||||
})
|
||||
go w.Start()
|
||||
time.Sleep(time.Duration(18) * time.Millisecond)
|
||||
w.Close()
|
||||
|
||||
assert.Equal(3, runtime)
|
||||
time.Sleep(time.Duration(8) * time.Millisecond)
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
SSHUpdateHostname = "uci set system.@system[0].hostname='%s';uci commit system;echo $(uci get system.@system[0].hostname) > /proc/sys/kernel/hostname"
|
||||
SSHUpdateOwner = "uci set gluon-node-info.@owner[0].contact='%s';uci commit gluon-node-info;"
|
||||
SSHUpdateLocation = "uci set gluon-node-info.@location[0].latitude='%d';uci set gluon-node-info.@location[0].longitude='%d';uci set gluon-node-info.@location[0].share_location=1;uci commit gluon-node-info;"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
Lastseen time.Time `json:"lastseen"`
|
||||
NodeID string `json:"node_id"`
|
||||
Hostname string `json:"hostname"`
|
||||
Location data.Location `json:"location"`
|
||||
Wireless data.Wireless `json:"wireless"`
|
||||
Owner string `json:"owner"`
|
||||
Address net.IP `json:"address"`
|
||||
}
|
||||
|
||||
func NewNode(node *yanicRuntime.Node) *Node {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
node := &Node{
|
||||
Hostname: nodeinfo.Hostname,
|
||||
NodeID: nodeinfo.NodeID,
|
||||
Address: node.Address,
|
||||
}
|
||||
if owner := nodeinfo.Owner; owner != nil {
|
||||
node.Owner = owner.Contact
|
||||
}
|
||||
if location := nodeinfo.Location; location != nil {
|
||||
node.Location = *location
|
||||
}
|
||||
if wireless := nodeinfo.Wireless; wireless != nil {
|
||||
node.Wireless = *wireless
|
||||
}
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) SSHUpdate(ssh *ssh.Manager, iface string, oldnode *Node) {
|
||||
addr := n.GetAddress(iface)
|
||||
if n.Hostname != oldnode.Hostname {
|
||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateHostname, n.Hostname))
|
||||
}
|
||||
if n.Owner != oldnode.Owner {
|
||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateOwner, n.Owner))
|
||||
}
|
||||
if !locationEqual(&n.Location, &oldnode.Location) {
|
||||
ssh.ExecuteOn(addr, fmt.Sprintf(SSHUpdateLocation, n.Location.Latitude, n.Location.Longtitude))
|
||||
}
|
||||
}
|
||||
func (n *Node) SSHSet(ssh *ssh.Manager, iface string) {
|
||||
n.SSHUpdate(ssh, iface, nil)
|
||||
}
|
||||
func (n *Node) GetAddress(iface string) net.TCPAddr {
|
||||
return net.TCPAddr{IP: n.Address, Port: 22, Zone: iface}
|
||||
}
|
||||
func (n *Node) Update(node *yanicRuntime.Node) {
|
||||
if nodeinfo := node.Nodeinfo; nodeinfo != nil {
|
||||
n.Hostname = nodeinfo.Hostname
|
||||
n.NodeID = nodeinfo.NodeID
|
||||
n.Address = node.Address
|
||||
|
||||
if owner := nodeinfo.Owner; owner != nil {
|
||||
n.Owner = owner.Contact
|
||||
}
|
||||
if location := nodeinfo.Location; location != nil {
|
||||
n.Location = *location
|
||||
}
|
||||
if wireless := nodeinfo.Wireless; wireless != nil {
|
||||
n.Wireless = *wireless
|
||||
}
|
||||
}
|
||||
}
|
||||
func (n *Node) IsEqual(node *Node) bool {
|
||||
if n.NodeID != node.NodeID {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(n.Address, node.Address) {
|
||||
return false
|
||||
}
|
||||
if n.Hostname != node.Hostname {
|
||||
return false
|
||||
}
|
||||
if n.Owner != node.Owner {
|
||||
return false
|
||||
}
|
||||
if !locationEqual(&n.Location, &node.Location) {
|
||||
return false
|
||||
}
|
||||
if !wirelessEqual(&n.Wireless, &node.Wireless) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (n *Node) IsEqualNode(node *yanicRuntime.Node) bool {
|
||||
nodeinfo := node.Nodeinfo
|
||||
if nodeinfo == nil {
|
||||
return false
|
||||
}
|
||||
owner := nodeinfo.Owner
|
||||
if owner == nil {
|
||||
return false
|
||||
}
|
||||
if n.NodeID != nodeinfo.NodeID {
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(n.Address, node.Address) {
|
||||
return false
|
||||
}
|
||||
if n.Hostname != nodeinfo.Hostname {
|
||||
return false
|
||||
}
|
||||
if n.Owner != owner.Contact {
|
||||
return false
|
||||
}
|
||||
if !locationEqual(&n.Location, nodeinfo.Location) {
|
||||
return false
|
||||
}
|
||||
if !wirelessEqual(&n.Wireless, nodeinfo.Wireless) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func locationEqual(a, b *data.Location) bool {
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
if a.Latitude != b.Latitude {
|
||||
return false
|
||||
}
|
||||
if a.Longtitude != b.Longtitude {
|
||||
return false
|
||||
}
|
||||
if a.Altitude != b.Altitude {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func wirelessEqual(a, b *data.Wireless) bool {
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
if a.Channel24 != b.Channel24 {
|
||||
return false
|
||||
}
|
||||
if a.Channel5 != b.Channel5 {
|
||||
return false
|
||||
}
|
||||
if a.TxPower24 != b.TxPower24 {
|
||||
return false
|
||||
}
|
||||
if a.TxPower5 != b.TxPower5 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/FreifunkBremen/yanic/data"
|
||||
yanicRuntime "github.com/FreifunkBremen/yanic/runtime"
|
||||
)
|
||||
|
||||
func TestNode(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
node1 := &yanicRuntime.Node{}
|
||||
n1 := NewNode(node1)
|
||||
assert.Nil(n1)
|
||||
|
||||
node1.Nodeinfo = &data.NodeInfo{
|
||||
Owner: &data.Owner{Contact: "blub"},
|
||||
Wireless: &data.Wireless{},
|
||||
Location: &data.Location{Altitude: 13},
|
||||
}
|
||||
n1 = NewNode(node1)
|
||||
assert.NotNil(n1)
|
||||
assert.Equal(float64(13), n1.Location.Altitude)
|
||||
|
||||
n2 := NewNode(node1)
|
||||
assert.True(n2.IsEqual(n1))
|
||||
assert.True(n2.IsEqualNode(node1))
|
||||
|
||||
node1.Nodeinfo.Owner.Contact = "blub2"
|
||||
assert.False(n2.IsEqualNode(node1))
|
||||
n2.Update(node1)
|
||||
assert.False(n2.IsEqual(n1))
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"net"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
yanic "github.com/FreifunkBremen/yanic/runtime"
|
||||
|
||||
|
@ -10,33 +12,85 @@ import (
|
|||
)
|
||||
|
||||
type Nodes struct {
|
||||
node map[string]struct{}
|
||||
List map[string]*Node `json:"nodes"`
|
||||
ToUpdate map[string]struct{}
|
||||
ssh *ssh.Manager
|
||||
statePath string
|
||||
iface string
|
||||
}
|
||||
|
||||
func NewNodes(iface string, mgmt *ssh.Manager) *Nodes {
|
||||
return &Nodes{
|
||||
node: make(map[string]struct{}),
|
||||
func NewNodes(path string, iface string, mgmt *ssh.Manager) *Nodes {
|
||||
nodes := &Nodes{
|
||||
List: make(map[string]*Node),
|
||||
ToUpdate: make(map[string]struct{}),
|
||||
ssh: mgmt,
|
||||
statePath: path,
|
||||
iface: iface,
|
||||
}
|
||||
nodes.load()
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (nodes *Nodes) AddNode(node *yanic.Node) {
|
||||
logger := log.Log.WithField("method", "AddNode").WithField("node_id", node.Nodeinfo.NodeID)
|
||||
// session := nodes.ssh.ConnectTo(node.Address)
|
||||
if _, ok := nodes.node[node.Address.String()]; ok {
|
||||
func (nodes *Nodes) AddNode(n *yanic.Node) {
|
||||
node := NewNode(n)
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
logger := log.Log.WithField("method", "AddNode").WithField("node_id", node.NodeID)
|
||||
|
||||
if cNode := nodes.List[node.NodeID]; cNode != nil {
|
||||
cNode.Lastseen = time.Now()
|
||||
if _, ok := nodes.ToUpdate[node.NodeID]; ok {
|
||||
if nodes.List[node.NodeID].IsEqual(node) {
|
||||
delete(nodes.ToUpdate, node.NodeID)
|
||||
}
|
||||
} else {
|
||||
nodes.List[node.NodeID] = node
|
||||
}
|
||||
logger.Debugf("know already these node")
|
||||
return
|
||||
}
|
||||
address := net.TCPAddr{IP: node.Address, Port: 22, Zone: nodes.iface}
|
||||
result, err := nodes.ssh.RunOn(address, "uptime")
|
||||
node.Lastseen = time.Now()
|
||||
// session := nodes.ssh.ConnectTo(node.Address)
|
||||
result, err := nodes.ssh.RunOn(node.GetAddress(nodes.iface), "uptime")
|
||||
if err != nil {
|
||||
logger.Error("init ssh command not run")
|
||||
return
|
||||
}
|
||||
uptime := ssh.SSHResultToString(result)
|
||||
logger.Infof("new node with uptime: %s", uptime)
|
||||
nodes.node[node.Address.String()] = struct{}{}
|
||||
|
||||
nodes.List[node.NodeID] = node
|
||||
}
|
||||
|
||||
func (nodes *Nodes) UpdateNode(node *Node) {
|
||||
if n, ok := nodes.List[node.NodeID]; ok {
|
||||
go node.SSHUpdate(nodes.ssh, nodes.iface, n)
|
||||
}
|
||||
nodes.List[node.NodeID] = node
|
||||
nodes.ToUpdate[node.NodeID] = struct{}{}
|
||||
}
|
||||
|
||||
func (nodes *Nodes) Updater() {
|
||||
for nodeid := range nodes.ToUpdate {
|
||||
if node := nodes.List[nodeid]; node != nil {
|
||||
go node.SSHSet(nodes.ssh, nodes.iface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (nodes *Nodes) load() {
|
||||
if f, err := os.Open(nodes.statePath); err == nil { // transform data to legacy meshviewer
|
||||
if err = json.NewDecoder(f).Decode(nodes); err == nil {
|
||||
log.Log.Info("loaded", len(nodes.List), "nodes")
|
||||
} else {
|
||||
log.Log.Error("failed to unmarshal nodes:", err)
|
||||
}
|
||||
} else {
|
||||
log.Log.Error("failed to load cached nodes:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (nodes *Nodes) Saver() {
|
||||
yanic.SaveJSON(nodes, nodes.statePath)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue