add API for aliases/ansible
This commit is contained in:
parent
b700426a3b
commit
d1aa7ab4d7
43
api/aliases.go
Normal file
43
api/aliases.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"github.com/FreifunkBremen/respond-collector/models"
|
||||||
|
)
|
||||||
|
type ApiAliases struct {
|
||||||
|
aliases *models.Aliases
|
||||||
|
config *models.Config
|
||||||
|
nodes *models.Nodes
|
||||||
|
}
|
||||||
|
func NewAliases (config *models.Config, router *httprouter.Router,prefix string,nodes *models.Nodes) {
|
||||||
|
api := &ApiAliases{
|
||||||
|
aliases: models.NewAliases(config),
|
||||||
|
nodes: nodes,
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
router.GET(prefix, api.GetAll)
|
||||||
|
router.GET(prefix+"/ansible", api.AnsibleDiff)
|
||||||
|
router.GET(prefix+"/alias/:nodeid", api.GetOne)
|
||||||
|
router.POST(prefix+"/alias/:nodeid", api.SaveOne)
|
||||||
|
}
|
||||||
|
func (api *ApiAliases) GetAll(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
jsonOutput(w,api.aliases.List)
|
||||||
|
}
|
||||||
|
func (api *ApiAliases) GetOne(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
if alias := api.aliases.List[ps.ByName("nodeid")]; alias !=nil{
|
||||||
|
jsonOutput(w,alias)
|
||||||
|
}
|
||||||
|
fmt.Fprint(w, "Not found: ", ps.ByName("nodeid"),"\n")
|
||||||
|
}
|
||||||
|
func (api *ApiAliases) SaveOne(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
alias := &models.Alias{Hostname: ps.ByName("nodeid")}
|
||||||
|
api.aliases.Update(ps.ByName("nodeid"),alias)
|
||||||
|
api.GetOne(w,r,ps)
|
||||||
|
}
|
||||||
|
func (api *ApiAliases) AnsibleDiff(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
|
diff := api.aliases.List
|
||||||
|
//TODO diff between List and api.nodes (for run not at all)
|
||||||
|
jsonOutput(w,models.GenerateAnsible(api.nodes,diff))
|
||||||
|
}
|
17
api/lib.go
Normal file
17
api/lib.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func jsonOutput(w http.ResponseWriter,data interface{}){
|
||||||
|
js, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(js)
|
||||||
|
}
|
@ -7,7 +7,9 @@ webserver:
|
|||||||
port: 8080
|
port: 8080
|
||||||
address: 127.0.0.1
|
address: 127.0.0.1
|
||||||
webroot: webroot
|
webroot: webroot
|
||||||
websocketnode: false
|
api:
|
||||||
|
newnodes: true
|
||||||
|
aliases: true
|
||||||
nodes:
|
nodes:
|
||||||
enable: true
|
enable: true
|
||||||
nodes_path: /var/www/html/meshviewer/data/nodes.json
|
nodes_path: /var/www/html/meshviewer/data/nodes.json
|
||||||
|
26
main.go
26
main.go
@ -10,21 +10,21 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
"github.com/NYTimes/gziphandler"
|
||||||
|
|
||||||
"github.com/FreifunkBremen/respond-collector/data"
|
"github.com/FreifunkBremen/respond-collector/data"
|
||||||
"github.com/FreifunkBremen/respond-collector/models"
|
"github.com/FreifunkBremen/respond-collector/models"
|
||||||
"github.com/FreifunkBremen/respond-collector/respond"
|
"github.com/FreifunkBremen/respond-collector/respond"
|
||||||
"github.com/FreifunkBremen/respond-collector/websocketserver"
|
"github.com/FreifunkBremen/respond-collector/api"
|
||||||
"github.com/NYTimes/gziphandler"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configFile string
|
configFile string
|
||||||
config *models.Config
|
config *models.Config
|
||||||
wsserverForNodes *websocketserver.Server
|
|
||||||
collector *respond.Collector
|
collector *respond.Collector
|
||||||
statsDb *StatsDb
|
statsDb *StatsDb
|
||||||
nodes *models.Nodes
|
nodes *models.Nodes
|
||||||
//aliases = models.NewNodes()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -42,18 +42,21 @@ func main() {
|
|||||||
collector = respond.NewCollector("nodeinfo statistics neighbours", collectInterval, onReceive)
|
collector = respond.NewCollector("nodeinfo statistics neighbours", collectInterval, onReceive)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Webserver.WebsocketNode {
|
|
||||||
wsserverForNodes = websocketserver.NewServer("/nodes")
|
|
||||||
go wsserverForNodes.Listen()
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Webserver.Enable {
|
if config.Webserver.Enable {
|
||||||
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webserver.Webroot))))
|
router := httprouter.New()
|
||||||
|
if config.Webserver.Api.NewNode {
|
||||||
|
}
|
||||||
|
if config.Webserver.Api.Aliases {
|
||||||
|
api.NewAliases(config,router,"/api/aliases",nodes)
|
||||||
|
log.Println("api started")
|
||||||
|
}
|
||||||
|
router.NotFound = gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webserver.Webroot)))
|
||||||
|
|
||||||
address := net.JoinHostPort(config.Webserver.Address, config.Webserver.Port)
|
address := net.JoinHostPort(config.Webserver.Address, config.Webserver.Port)
|
||||||
log.Println("starting webserver on", address)
|
log.Println("starting webserver on", address)
|
||||||
// TODO bad
|
// TODO bad
|
||||||
log.Fatal(http.ListenAndServe(address, nil))
|
log.Fatal(http.ListenAndServe(address, router))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for INT/TERM
|
// Wait for INT/TERM
|
||||||
@ -63,9 +66,6 @@ func main() {
|
|||||||
log.Println("received", sig)
|
log.Println("received", sig)
|
||||||
|
|
||||||
// Close everything at the end
|
// Close everything at the end
|
||||||
if wsserverForNodes != nil {
|
|
||||||
wsserverForNodes.Close()
|
|
||||||
}
|
|
||||||
if collector != nil {
|
if collector != nil {
|
||||||
collector.Close()
|
collector.Close()
|
||||||
}
|
}
|
||||||
|
68
models/aliases.go
Normal file
68
models/aliases.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Alias struct {
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
}
|
||||||
|
// Nodes struct: cache DB of Node's structs
|
||||||
|
type Aliases struct {
|
||||||
|
List map[string]*Alias `json:"nodes"` // the current nodemap, indexed by node ID
|
||||||
|
config *Config
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
// NewNodes create Nodes structs
|
||||||
|
func NewAliases(config *Config) *Aliases {
|
||||||
|
aliases := &Aliases{
|
||||||
|
List: make(map[string]*Alias),
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Nodes.AliasesPath != "" {
|
||||||
|
aliases.load()
|
||||||
|
}
|
||||||
|
go aliases.worker()
|
||||||
|
|
||||||
|
return aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Aliases) Update(nodeID string, newalias *Alias) {
|
||||||
|
e.Lock()
|
||||||
|
e.List[nodeID] = newalias
|
||||||
|
e.Unlock()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Aliases) load() {
|
||||||
|
path := e.config.Nodes.AliasesPath
|
||||||
|
log.Println("loading", path)
|
||||||
|
|
||||||
|
if data, err := ioutil.ReadFile(path); err == nil {
|
||||||
|
if err := json.Unmarshal(data, e); err == nil {
|
||||||
|
log.Println("loaded", len(e.List), "aliases")
|
||||||
|
} else {
|
||||||
|
log.Println("failed to unmarshal nodes:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Println("failed loading cached nodes:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Periodically saves the cached DB to json file
|
||||||
|
func (e *Aliases) worker() {
|
||||||
|
c := time.Tick(time.Second * 5)
|
||||||
|
|
||||||
|
for range c {
|
||||||
|
log.Println("saving", len(e.List), "aliases")
|
||||||
|
e.Lock()
|
||||||
|
save(e, e.config.Nodes.AliasesPath)
|
||||||
|
e.Unlock()
|
||||||
|
}
|
||||||
|
}
|
30
models/ansible.go
Normal file
30
models/ansible.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type Ansible struct {
|
||||||
|
Nodes []string `json:"nodes"`
|
||||||
|
Meta struct {
|
||||||
|
HostVars []*AnsibleHostVars `json:"hostvars"`
|
||||||
|
} `json:"_meta"`
|
||||||
|
}
|
||||||
|
type AnsibleHostVars struct {
|
||||||
|
Address string `json:"ansible_ssh_host"`
|
||||||
|
Hostname string `json:"node_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateAnsible(nodes *Nodes,aliases map[string]*Alias) *Ansible{
|
||||||
|
ansible := &Ansible{Nodes:make([]string,0)}
|
||||||
|
for nodeid,alias := range aliases{
|
||||||
|
if node := nodes.List[nodeid]; node != nil {
|
||||||
|
|
||||||
|
ansible.Nodes = append(ansible.Nodes,nodeid)
|
||||||
|
|
||||||
|
vars := &AnsibleHostVars{
|
||||||
|
Address: node.Nodeinfo.Network.Addresses[0],
|
||||||
|
Hostname: alias.Hostname,
|
||||||
|
}
|
||||||
|
ansible.Meta.HostVars = append(ansible.Meta.HostVars,vars)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ansible
|
||||||
|
}
|
@ -20,14 +20,15 @@ type Config struct {
|
|||||||
Port string `yaml:"port"`
|
Port string `yaml:"port"`
|
||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
Webroot string `yaml:"webroot"`
|
Webroot string `yaml:"webroot"`
|
||||||
WebsocketNode bool `yaml:"websocketnode"`
|
Api struct {
|
||||||
WebsocketAliases bool `yaml:"websocketaliases"`
|
NewNode bool `yaml:"newnode"`
|
||||||
|
Aliases bool `yaml:"aliases"`
|
||||||
|
} `yaml:"api"`
|
||||||
} `yaml:"webserver"`
|
} `yaml:"webserver"`
|
||||||
Nodes struct {
|
Nodes struct {
|
||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
NodesPath string `yaml:"nodes_path"`
|
NodesPath string `yaml:"nodes_path"`
|
||||||
GraphsPath string `yaml:"graphs_path"`
|
GraphsPath string `yaml:"graphs_path"`
|
||||||
AliasesEnable bool `yaml:"aliases_enable"`
|
|
||||||
AliasesPath string `yaml:"aliases_path"`
|
AliasesPath string `yaml:"aliases_path"`
|
||||||
SaveInterval int `yaml:"saveinterval"`
|
SaveInterval int `yaml:"saveinterval"`
|
||||||
VpnAddresses []string `yaml:"vpn_addresses"`
|
VpnAddresses []string `yaml:"vpn_addresses"`
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
package websocketserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
const channelBufSize = 100
|
|
||||||
|
|
||||||
var maxID = 0
|
|
||||||
|
|
||||||
//Client struct
|
|
||||||
type Client struct {
|
|
||||||
id int
|
|
||||||
ws *websocket.Conn
|
|
||||||
server *Server
|
|
||||||
ch chan interface{}
|
|
||||||
doneCh chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewClient creates a new Client
|
|
||||||
func NewClient(ws *websocket.Conn, server *Server) *Client {
|
|
||||||
|
|
||||||
if ws == nil {
|
|
||||||
panic("ws cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if server == nil {
|
|
||||||
panic("server cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
maxID++
|
|
||||||
|
|
||||||
return &Client{
|
|
||||||
id: maxID,
|
|
||||||
ws: ws,
|
|
||||||
server: server,
|
|
||||||
ch: make(chan interface{}, channelBufSize),
|
|
||||||
doneCh: make(chan bool),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//GetConnection the websocket connection of a listen client
|
|
||||||
func (c *Client) GetConnection() *websocket.Conn {
|
|
||||||
return c.ws
|
|
||||||
}
|
|
||||||
|
|
||||||
//Write send the msg informations to the clients
|
|
||||||
func (c *Client) Write(msg interface{}) {
|
|
||||||
select {
|
|
||||||
case c.ch <- msg:
|
|
||||||
default:
|
|
||||||
c.server.Del(c)
|
|
||||||
err := fmt.Errorf("Client %d is disconnected.", c.id)
|
|
||||||
c.server.Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen Write and Read request via chanel
|
|
||||||
func (c *Client) Listen() {
|
|
||||||
c.listen()
|
|
||||||
}
|
|
||||||
|
|
||||||
// listen for new msg informations
|
|
||||||
func (c *Client) listen() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case msg := <-c.ch:
|
|
||||||
err := websocket.JSON.Send(c.ws, msg)
|
|
||||||
if err != nil {
|
|
||||||
c.doneCh <- true
|
|
||||||
}
|
|
||||||
case gone := <-c.doneCh:
|
|
||||||
if gone {
|
|
||||||
c.server.Del(c)
|
|
||||||
err := fmt.Errorf("Client %d is disconnected.", c.id)
|
|
||||||
c.server.Err(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,118 +0,0 @@
|
|||||||
package websocketserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
//Server struct
|
|
||||||
type Server struct {
|
|
||||||
pattern string
|
|
||||||
clients map[int]*Client
|
|
||||||
addCh chan *Client
|
|
||||||
delCh chan *Client
|
|
||||||
sendAllCh chan interface{}
|
|
||||||
closeCh chan bool
|
|
||||||
errCh chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
//NewServer creates a new server
|
|
||||||
func NewServer(pattern string) *Server {
|
|
||||||
return &Server{
|
|
||||||
pattern: pattern,
|
|
||||||
clients: make(map[int]*Client),
|
|
||||||
addCh: make(chan *Client),
|
|
||||||
delCh: make(chan *Client),
|
|
||||||
sendAllCh: make(chan interface{}),
|
|
||||||
closeCh: make(chan bool),
|
|
||||||
errCh: make(chan error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Add a listen client
|
|
||||||
func (s *Server) Add(c *Client) {
|
|
||||||
s.addCh <- c
|
|
||||||
}
|
|
||||||
|
|
||||||
//Del a listen client
|
|
||||||
func (s *Server) Del(c *Client) {
|
|
||||||
s.delCh <- c
|
|
||||||
}
|
|
||||||
|
|
||||||
//SendAll to all listen clients a msg
|
|
||||||
func (s *Server) SendAll(msg interface{}) {
|
|
||||||
s.sendAllCh <- msg
|
|
||||||
}
|
|
||||||
|
|
||||||
//Close stops server
|
|
||||||
func (s *Server) Close() {
|
|
||||||
s.closeCh <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Err send to server
|
|
||||||
func (s *Server) Err(err error) {
|
|
||||||
s.errCh <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) sendAll(msg interface{}) {
|
|
||||||
for _, c := range s.clients {
|
|
||||||
c.Write(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen and serve.
|
|
||||||
// It serves client connection and broadcast request.
|
|
||||||
func (s *Server) Listen() {
|
|
||||||
|
|
||||||
log.Println("Listening Server...")
|
|
||||||
|
|
||||||
// websocket handler
|
|
||||||
onConnected := func(ws *websocket.Conn) {
|
|
||||||
defer func() {
|
|
||||||
err := ws.Close()
|
|
||||||
if err != nil {
|
|
||||||
s.errCh <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
client := NewClient(ws, s)
|
|
||||||
s.Add(client)
|
|
||||||
defer func() {
|
|
||||||
s.Del(client)
|
|
||||||
err := fmt.Errorf("Client %d is disconnected.", client.id)
|
|
||||||
s.Err(err)
|
|
||||||
}()
|
|
||||||
client.Listen()
|
|
||||||
}
|
|
||||||
http.Handle(s.pattern, websocket.Handler(onConnected))
|
|
||||||
log.Println("Created handler")
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
|
|
||||||
// Add new a client
|
|
||||||
case c := <-s.addCh:
|
|
||||||
log.Println("Added new client")
|
|
||||||
s.clients[c.id] = c
|
|
||||||
log.Println("Now", len(s.clients), "clients connected.")
|
|
||||||
|
|
||||||
// del a client
|
|
||||||
case c := <-s.delCh:
|
|
||||||
log.Println("Delete client")
|
|
||||||
delete(s.clients, c.id)
|
|
||||||
|
|
||||||
// broadcast message for all clients
|
|
||||||
case msg := <-s.sendAllCh:
|
|
||||||
s.sendAll(msg)
|
|
||||||
|
|
||||||
case err := <-s.errCh:
|
|
||||||
log.Println("Error:", err.Error())
|
|
||||||
|
|
||||||
case <-s.closeCh:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user