add API for aliases/ansible

This commit is contained in:
Martin Geno 2016-05-14 12:31:43 +02:00
parent b700426a3b
commit d1aa7ab4d7
9 changed files with 178 additions and 217 deletions

43
api/aliases.go Normal file
View 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
View 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)
}

View File

@ -7,7 +7,9 @@ webserver:
port: 8080
address: 127.0.0.1
webroot: webroot
websocketnode: false
api:
newnodes: true
aliases: true
nodes:
enable: true
nodes_path: /var/www/html/meshviewer/data/nodes.json

26
main.go
View File

@ -10,21 +10,21 @@ import (
"syscall"
"time"
"github.com/julienschmidt/httprouter"
"github.com/NYTimes/gziphandler"
"github.com/FreifunkBremen/respond-collector/data"
"github.com/FreifunkBremen/respond-collector/models"
"github.com/FreifunkBremen/respond-collector/respond"
"github.com/FreifunkBremen/respond-collector/websocketserver"
"github.com/NYTimes/gziphandler"
"github.com/FreifunkBremen/respond-collector/api"
)
var (
configFile string
config *models.Config
wsserverForNodes *websocketserver.Server
collector *respond.Collector
statsDb *StatsDb
nodes *models.Nodes
//aliases = models.NewNodes()
)
func main() {
@ -42,18 +42,21 @@ func main() {
collector = respond.NewCollector("nodeinfo statistics neighbours", collectInterval, onReceive)
}
if config.Webserver.WebsocketNode {
wsserverForNodes = websocketserver.NewServer("/nodes")
go wsserverForNodes.Listen()
}
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)
log.Println("starting webserver on", address)
// TODO bad
log.Fatal(http.ListenAndServe(address, nil))
log.Fatal(http.ListenAndServe(address, router))
}
// Wait for INT/TERM
@ -63,9 +66,6 @@ func main() {
log.Println("received", sig)
// Close everything at the end
if wsserverForNodes != nil {
wsserverForNodes.Close()
}
if collector != nil {
collector.Close()
}

68
models/aliases.go Normal file
View 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
View 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
}

View File

@ -20,14 +20,15 @@ type Config struct {
Port string `yaml:"port"`
Address string `yaml:"address"`
Webroot string `yaml:"webroot"`
WebsocketNode bool `yaml:"websocketnode"`
WebsocketAliases bool `yaml:"websocketaliases"`
Api struct {
NewNode bool `yaml:"newnode"`
Aliases bool `yaml:"aliases"`
} `yaml:"api"`
} `yaml:"webserver"`
Nodes struct {
Enable bool `yaml:"enable"`
NodesPath string `yaml:"nodes_path"`
GraphsPath string `yaml:"graphs_path"`
AliasesEnable bool `yaml:"aliases_enable"`
AliasesPath string `yaml:"aliases_path"`
SaveInterval int `yaml:"saveinterval"`
VpnAddresses []string `yaml:"vpn_addresses"`

View File

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

View File

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