[TASK] add node model

This commit is contained in:
Martin Geno 2017-05-07 03:37:30 +02:00
parent 34ee3c5e62
commit d954d7c851
No known key found for this signature in database
GPG Key ID: F0D39A37E925E941
7 changed files with 352 additions and 17 deletions

View File

@ -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)

View File

@ -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"`

41
lib/worker/worker.go Normal file
View File

@ -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)
}

24
lib/worker/worker_test.go Normal file
View File

@ -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)
}

170
runtime/node.go Normal file
View File

@ -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
}

35
runtime/node_test.go Normal file
View File

@ -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))
}

View File

@ -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)
}