From d1aa7ab4d7a8d2310041e174ad4c82d6b454098f Mon Sep 17 00:00:00 2001 From: Martin Geno Date: Sat, 14 May 2016 12:31:43 +0200 Subject: [PATCH] add API for aliases/ansible --- api/aliases.go | 43 ++++++++++++++ api/lib.go | 17 ++++++ config_example.yml | 4 +- main.go | 26 ++++----- models/aliases.go | 68 ++++++++++++++++++++++ models/ansible.go | 30 ++++++++++ models/config.go | 7 ++- websocketserver/client.go | 82 -------------------------- websocketserver/server.go | 118 -------------------------------------- 9 files changed, 178 insertions(+), 217 deletions(-) create mode 100644 api/aliases.go create mode 100644 api/lib.go create mode 100644 models/aliases.go create mode 100644 models/ansible.go delete mode 100644 websocketserver/client.go delete mode 100644 websocketserver/server.go diff --git a/api/aliases.go b/api/aliases.go new file mode 100644 index 0000000..e7be5b1 --- /dev/null +++ b/api/aliases.go @@ -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)) +} diff --git a/api/lib.go b/api/lib.go new file mode 100644 index 0000000..38a7c47 --- /dev/null +++ b/api/lib.go @@ -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) +} diff --git a/config_example.yml b/config_example.yml index 1b6db9d..80978d4 100644 --- a/config_example.yml +++ b/config_example.yml @@ -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 diff --git a/main.go b/main.go index 1989d01..57fd286 100644 --- a/main.go +++ b/main.go @@ -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() } diff --git a/models/aliases.go b/models/aliases.go new file mode 100644 index 0000000..635c386 --- /dev/null +++ b/models/aliases.go @@ -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() + } +} diff --git a/models/ansible.go b/models/ansible.go new file mode 100644 index 0000000..cc21f58 --- /dev/null +++ b/models/ansible.go @@ -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 +} diff --git a/models/config.go b/models/config.go index 205f247..e1044b0 100644 --- a/models/config.go +++ b/models/config.go @@ -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"` diff --git a/websocketserver/client.go b/websocketserver/client.go deleted file mode 100644 index e1d8fd4..0000000 --- a/websocketserver/client.go +++ /dev/null @@ -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) - } - } - } -} diff --git a/websocketserver/server.go b/websocketserver/server.go deleted file mode 100644 index 6712d80..0000000 --- a/websocketserver/server.go +++ /dev/null @@ -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 - } - } -}