[TASK] add node model
This commit is contained in:
parent
34ee3c5e62
commit
d954d7c851
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/NYTimes/gziphandler"
|
"github.com/NYTimes/gziphandler"
|
||||||
goji "goji.io"
|
goji "goji.io"
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
|
|
||||||
configPackage "github.com/FreifunkBremen/freifunkmanager/config"
|
configPackage "github.com/FreifunkBremen/freifunkmanager/config"
|
||||||
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
"github.com/FreifunkBremen/freifunkmanager/lib/log"
|
||||||
|
"github.com/FreifunkBremen/freifunkmanager/lib/worker"
|
||||||
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
"github.com/FreifunkBremen/freifunkmanager/runtime"
|
||||||
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
"github.com/FreifunkBremen/freifunkmanager/ssh"
|
||||||
"github.com/FreifunkBremen/freifunkmanager/yanic"
|
"github.com/FreifunkBremen/freifunkmanager/yanic"
|
||||||
|
@ -34,7 +36,12 @@ func main() {
|
||||||
log.Log.Info("starting...")
|
log.Log.Info("starting...")
|
||||||
|
|
||||||
sshmanager := ssh.NewManager(config.SSHPrivateKey)
|
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 {
|
if config.Yanic.Enable {
|
||||||
yanicDialer := yanic.Dial(config.Yanic.Type, config.Yanic.Address)
|
yanicDialer := yanic.Dial(config.Yanic.Type, config.Yanic.Address)
|
||||||
|
@ -69,6 +76,8 @@ func main() {
|
||||||
if config.Yanic.Enable {
|
if config.Yanic.Enable {
|
||||||
yanicDialer.Close()
|
yanicDialer.Close()
|
||||||
}
|
}
|
||||||
|
nodesSaveWorker.Close()
|
||||||
|
nodesUpdateWorker.Close()
|
||||||
sshmanager.Close()
|
sshmanager.Close()
|
||||||
|
|
||||||
log.Log.Info("stop recieve:", sig)
|
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)
|
//config file of this daemon (for more the config_example.conf in git repository)
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// prevent crashes
|
||||||
|
StatePath string `toml:"state_path"`
|
||||||
// address on which the api and static content webserver runs
|
// address on which the api and static content webserver runs
|
||||||
WebserverBind string `toml:"webserver_bind"`
|
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
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
yanic "github.com/FreifunkBremen/yanic/runtime"
|
yanic "github.com/FreifunkBremen/yanic/runtime"
|
||||||
|
|
||||||
|
@ -10,33 +12,85 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Nodes struct {
|
type Nodes struct {
|
||||||
node map[string]struct{}
|
List map[string]*Node `json:"nodes"`
|
||||||
ssh *ssh.Manager
|
ToUpdate map[string]struct{}
|
||||||
iface string
|
ssh *ssh.Manager
|
||||||
|
statePath string
|
||||||
|
iface string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNodes(iface string, mgmt *ssh.Manager) *Nodes {
|
func NewNodes(path string, iface string, mgmt *ssh.Manager) *Nodes {
|
||||||
return &Nodes{
|
nodes := &Nodes{
|
||||||
node: make(map[string]struct{}),
|
List: make(map[string]*Node),
|
||||||
ssh: mgmt,
|
ToUpdate: make(map[string]struct{}),
|
||||||
iface: iface,
|
ssh: mgmt,
|
||||||
|
statePath: path,
|
||||||
|
iface: iface,
|
||||||
}
|
}
|
||||||
|
nodes.load()
|
||||||
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nodes *Nodes) AddNode(node *yanic.Node) {
|
func (nodes *Nodes) AddNode(n *yanic.Node) {
|
||||||
logger := log.Log.WithField("method", "AddNode").WithField("node_id", node.Nodeinfo.NodeID)
|
node := NewNode(n)
|
||||||
// session := nodes.ssh.ConnectTo(node.Address)
|
if node == nil {
|
||||||
if _, ok := nodes.node[node.Address.String()]; ok {
|
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")
|
logger.Debugf("know already these node")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
address := net.TCPAddr{IP: node.Address, Port: 22, Zone: nodes.iface}
|
node.Lastseen = time.Now()
|
||||||
result, err := nodes.ssh.RunOn(address, "uptime")
|
// session := nodes.ssh.ConnectTo(node.Address)
|
||||||
|
result, err := nodes.ssh.RunOn(node.GetAddress(nodes.iface), "uptime")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("init ssh command not run")
|
logger.Error("init ssh command not run")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uptime := ssh.SSHResultToString(result)
|
uptime := ssh.SSHResultToString(result)
|
||||||
logger.Infof("new node with uptime: %s", uptime)
|
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