// removeOldKeys removes keys older than specified age func removeOldKeys() error { datas, err := backends.List() if err != nil { return err } now := time.Now() lumber.Debug("Garbage Collector - Finding files...") for _, data := range datas { // clean-after defaults to Now() to ensure no files are deleted in case // cobra decides to change how 'Command.Flag().Changed' works. It does this // because no files, written by hoarder, will have a modified time before the // Unix epoch began if data.ModTime.Unix() < (now.Unix() - int64(viper.GetInt("clean-after"))) { lumber.Debug("Cleaning key: ", data.Name) if err := backends.Remove(data.Name); err != nil { return fmt.Errorf("Cleaning of '%s' failed - %v", data.Name, err.Error()) } } } return nil }
// writeBody func writeBody(v interface{}, rw http.ResponseWriter, status int, req *http.Request) error { b, err := json.Marshal(v) if err != nil { return err } // print the error only if there is one var msg map[string]string json.Unmarshal(b, &msg) var errMsg string if msg["error"] != "" { errMsg = msg["error"] } lumber.Debug(`[PULSE :: ACCESS] %v - [%v] %v %v %v - "User-Agent: %s" %s`, req.RemoteAddr, req.Proto, req.Method, req.RequestURI, status, req.Header.Get("User-Agent"), errMsg) rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(status) rw.Write(append(b, byte('\n'))) return nil }
// registerRoutes func registerRoutes() (*pat.Router, error) { lumber.Debug("[PULSE :: API] Registering routes...") // router := pat.New() // router.Get("/ping", func(rw http.ResponseWriter, req *http.Request) { rw.Write([]byte("pong")) }) router.Options("/", cors) router.Get("/keys", keysRequest) router.Get("/tags", tagsRequest) router.Get("/latest/{stat}", doCors(latestStat)) router.Get("/hourly/{stat}", doCors(hourlyStat)) router.Get("/daily/{stat}", doCors(dailyStat)) router.Get("/daily_peaks/{stat}", doCors(dailyStat)) // only expose alert routes if alerting configured if viper.GetString("kapacitor-address") != "" { // todo: maybe get and list tasks from kapacitor router.Post("/alerts", doCors(setAlert)) router.Put("/alerts", doCors(setAlert)) router.Delete("/alerts/{id}", doCors(deleteAlert)) } return router, nil }
// connect func (p *Proxy) connect() { lumber.Debug("Client connecting...") // this gofunc handles matching messages to subscriptions for the proxy go p.handleMessages() }
func startHoarder(ccmd *cobra.Command, args []string) error { // convert the log level logLvl := lumber.LvlInt(viper.GetString("log-level")) // configure the logger lumber.Prefix("[hoader]") lumber.Level(logLvl) // enable/start garbage collection if age config was changed if ccmd.Flag("clean-after").Changed { lumber.Debug("Starting garbage collector (data older than %vs)...\n", ccmd.Flag("clean-after").Value) // start garbage collector go collector.Start() } // set, and initialize, the backend driver if err := backends.Initialize(); err != nil { lumber.Error("Failed to initialize backend - %v", err) return err } // start the API if err := api.Start(); err != nil { lumber.Fatal("Failed to start API: ", err.Error()) return err } return nil }
func (t *Server) handlePrefix(id string, msg PrefixMsg) { log.Trace("Handling prefix message") if _, ok := t.prefixes[id]; !ok { t.prefixes[id] = make(PrefixMap) } if err := t.prefixes[id].RegisterPrefix(msg.Prefix, msg.URI); err != nil { log.Error("Error registering prefix: %s", err) } log.Debug("Client %s registered prefix '%s' for URI: %s", id, msg.Prefix, msg.URI) }
func (t *Server) handleUnsubscribe(id string, msg UnsubscribeMsg) { log.Trace("Handling unsubscribe message") t.subLock.Lock() topic := CheckCurie(t.prefixes[id], msg.TopicURI) if lm, ok := t.subscriptions[topic]; ok { lm.Remove(id) } t.subLock.Unlock() log.Debug("Client %s unsubscribed from topic: %s", id, topic) }
// runLoop handles communication from the server func (relay *Relay) runLoop(reader *bufio.Reader) { for { // when implementing relay, set `lumber.Level(lumber.LvlInt("TRACE"))` in client to view logs line, err := reader.ReadString('\n') if err != nil { lumber.Error("[PULSE :: RELAY] Disconnected from host %v!", relay.hostAddr) // retry indefinitely for { if reader, err = relay.establishConnection(); err == nil { lumber.Info("[PULSE :: RELAY] Reconnected to host %v!", relay.hostAddr) break } lumber.Debug("[PULSE :: RELAY] Reconnecting to host %v... Fail!", relay.hostAddr) <-time.After(5 * time.Second) } // we won't have anything in 'line' so continue continue } line = strings.TrimSuffix(line, "\n") split := strings.SplitN(line, " ", 2) cmd := split[0] switch cmd { case "ok": lumber.Trace("[PULSE :: RELAY] OK: %v", split) // just an ack case "get": lumber.Trace("[PULSE :: RELAY] GET: %v", split) if len(split) != 2 { continue } stats := strings.Split(split[1], ",") results := make([]string, 0) for _, stat := range stats { tagCollector, ok := relay.collectors[stat] if !ok { continue } for name, value := range tagCollector.collector.Collect() { formatted := strconv.FormatFloat(value, 'f', 4, 64) if name == "" { name = stat } results = append(results, fmt.Sprintf("%s-%s:%s", stat, name, formatted)) } } response := fmt.Sprintf("got %s\n", strings.Join(results, ",")) relay.conn.Write([]byte(response)) default: lumber.Trace("[PULSE :: RELAY] BAD: %v", split) relay.conn.Write([]byte("unknown command\n")) } } }
func (t *Server) handleSubscribe(id string, msg SubscribeMsg) { log.Trace("Handling subscribe message") t.subLock.Lock() topic := CheckCurie(t.prefixes[id], msg.TopicURI) if _, ok := t.subscriptions[topic]; !ok { t.subscriptions[topic] = make(map[string]bool) } t.subscriptions[topic].Add(id) t.subLock.Unlock() log.Debug("Client %s subscribed to topic: %s", id, topic) }
func (c *Client) ReceiveWelcome() error { log.Trace("Receive welcome") var rec string err := websocket.Message.Receive(c.ws, &rec) if err != nil { return fmt.Errorf("Error receiving welcome message: %s", err) } if typ := ParseType(rec); typ != WELCOME { return fmt.Errorf("First message received was not welcome") } var msg WelcomeMsg err = json.Unmarshal([]byte(rec), &msg) if err != nil { return fmt.Errorf("Error unmarshalling welcome message: %s", err) } c.SessionId = msg.SessionId log.Debug("Session id: %s", c.SessionId) c.ProtocolVersion = msg.ProtocolVersion log.Debug("Protocol version: %d", c.ProtocolVersion) c.ServerIdent = msg.ServerIdent log.Debug("Server ident: %s", c.ServerIdent) return nil }
// handleRequest is a wrapper for the actual route handler, simply to provide some // debug output func handleRequest(fn http.HandlerFunc) http.HandlerFunc { return func(rw http.ResponseWriter, req *http.Request) { // fn(rw, req) // must be after req returns getStatus := func(trw http.ResponseWriter) string { r, _ := regexp.Compile("status:([0-9]*)") return r.FindStringSubmatch(fmt.Sprintf("%+v", trw))[1] } getWrote := func(trw http.ResponseWriter) string { r, _ := regexp.Compile("written:([0-9]*)") return r.FindStringSubmatch(fmt.Sprintf("%+v", trw))[1] } lumber.Debug(`%v - [%v] %v %v %v(%s) - "User-Agent: %s"`, req.RemoteAddr, req.Proto, req.Method, req.RequestURI, getStatus(rw), getWrote(rw), // %v(%s) req.Header.Get("User-Agent")) } }
// routes registers all api routes with the router func routes() *pat.Router { lumber.Debug("Registering routes...\n") // create new router router := pat.New() // define ping router.Get("/ping", func(rw http.ResponseWriter, req *http.Request) { rw.Write([]byte("pong\n")) }) // blobs router.Add("HEAD", "/blobs/{blob}", handleRequest(getHead)) router.Get("/blobs/{blob}", handleRequest(get)) router.Post("/blobs/{blob}", handleRequest(create)) router.Put("/blobs/{blob}", handleRequest(update)) router.Delete("/blobs/{blob}", handleRequest(delete)) router.Add("HEAD", "/blobs", handleRequest(list)) router.Get("/blobs", handleRequest(list)) // return router }
func (t *Server) HandleWebsocket(conn *websocket.Conn) { defer conn.Close() log.Debug("Received websocket connection") tid, err := uuid.NewV4() if err != nil { log.Error("Could not create unique id, refusing client connection") return } id := tid.String() arr, err := CreateWelcome(id, TURNPIKE_SERVER_IDENT) if err != nil { log.Error("Error encoding welcome message") return } log.Debug("Sending welcome message: %s", arr) err = websocket.Message.Send(conn, string(arr)) if err != nil { log.Error("Error sending welcome message, aborting connection: %s", err) return } c := make(chan string, serverBacklog) t.clients[id] = c go func() { for msg := range c { log.Trace("Sending message: %s", msg) err := websocket.Message.Send(conn, string(msg)) if err != nil { log.Error("Error sending message: %s", err) } } }() for { var rec string err := websocket.Message.Receive(conn, &rec) if err != nil { if err != io.EOF { log.Error("Error receiving message, aborting connection: %s", err) } break } log.Trace("Message received: %s", rec) data := []byte(rec) switch typ := ParseType(rec); typ { case PREFIX: var msg PrefixMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling prefix message: %s", err) } t.handlePrefix(id, msg) case CALL: var msg CallMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling call message: %s", err) } t.handleCall(id, msg) case SUBSCRIBE: var msg SubscribeMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling subscribe message: %s", err) } t.handleSubscribe(id, msg) case UNSUBSCRIBE: var msg UnsubscribeMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling unsubscribe message: %s", err) } t.handleUnsubscribe(id, msg) case PUBLISH: var msg PublishMsg err := json.Unmarshal(data, &msg) if err != nil { log.Error("Error unmarshalling publish message: %s", err) } t.handlePublish(id, msg) case WELCOME, CALLRESULT, CALLERROR, EVENT: log.Error("Server -> client message received, ignored: %s", TypeString(typ)) default: log.Error("Invalid message format, message dropped: %s", data) } } delete(t.clients, id) close(c) }
func (t *Server) handlePublish(id string, msg PublishMsg) { log.Trace("Handling publish message") topic := CheckCurie(t.prefixes[id], msg.TopicURI) lm, ok := t.subscriptions[topic] if !ok { return } out, err := CreateEvent(topic, msg.Event) if err != nil { log.Error("Error creating event message: %s", err) return } var sendTo []string if len(msg.ExcludeList) > 0 || len(msg.EligibleList) > 0 { // this is super ugly, but I couldn't think of a better way... for tid := range lm { include := true for _, _tid := range msg.ExcludeList { if tid == _tid { include = false break } } if include { sendTo = append(sendTo, tid) } } for _, tid := range msg.EligibleList { include := true for _, _tid := range sendTo { if _tid == tid { include = false break } } if include { sendTo = append(sendTo, tid) } } } else { for tid := range lm { if tid == id && msg.ExcludeMe { continue } sendTo = append(sendTo, tid) } } log.Debug("Sending event messages") for _, tid := range sendTo { // we're not locking anything, so we need // to make sure the client didn't disconnecct in the // last few nanoseconds... if client, ok := t.clients[tid]; ok { client <- string(out) } } }
// handleConnection takes an incoming connection from a mist client (or other client) // and sets up a new subscription for that connection, and a 'publish Handler' // that is used to publish messages to the data channel of the subscription func handleConnection(conn net.Conn, errChan chan<- error) { // close the connection when we're done here defer conn.Close() // create a new client for each connection proxy := mist.NewProxy() defer proxy.Close() // add basic TCP command handlers for this connection handlers = GenerateHandlers() encoder := json.NewEncoder(conn) decoder := json.NewDecoder(conn) // publish mist messages (pong, etc.. and messages if subscriber attatched) // to connected tcp client (non-blocking) go func() { for msg := range proxy.Pipe { lumber.Trace("Got message - %#v", msg) // if the message fails to encode its probably a syntax issue and needs to // break the loop here because it will never be able to encode it; this will // disconnect the client. if err := encoder.Encode(msg); err != nil { errChan <- fmt.Errorf("Failed to pubilsh proxy.Pipe contents to TCP clients - %v", err) break } } }() // connection loop (blocking); continually read off the connection. Once something // is read, check to see if it's a message the client understands to be one of // its commands. If so attempt to execute the command. for { msg := mist.Message{} // if the message fails to decode its probably a syntax issue and needs to // break the loop here because it will never be able to decode it; this will // disconnect the client. if err := decoder.Decode(&msg); err != nil { switch err { case io.EOF: lumber.Debug("Client disconnected") case io.ErrUnexpectedEOF: lumber.Debug("Client disconnected unexpedtedly") default: errChan <- fmt.Errorf("Failed to decode message from TCP connection - %v", err) } return } // if an authenticator was passed, check for a token on connect to see if // auth commands are allowed if auth.DefaultAuth != nil && !proxy.Authenticated { // if the next input does not match the token then if msg.Data != authtoken { lumber.Debug("Client data doesn't match configured auth token") // break // allow connection w/o admin commands return // disconnect client } // todo: is this still used? // add auth commands ("admin" mode) for k, v := range auth.GenerateHandlers() { handlers[k] = v } // establish that the connection has already authenticated proxy.Authenticated = true } // look for the command handler, found := handlers[msg.Command] // if the command isn't found, return an error and wait for the next command if !found { lumber.Trace("Command '%v' not found", msg.Command) encoder.Encode(&mist.Message{Command: msg.Command, Tags: msg.Tags, Data: msg.Data, Error: "Unknown Command"}) continue } // attempt to run the command; if the command fails return the error and wait // for the next command lumber.Trace("TCP Running '%v'...", msg.Command) if err := handler(proxy, msg); err != nil { lumber.Debug("TCP Failed to run '%v' - %v", msg.Command, err) encoder.Encode(&mist.Message{Command: msg.Command, Error: err.Error()}) continue } } }
// connect dials the remote mist server and handles any incoming responses back // from mist func (c *TCP) connect() error { // attempt to connect to the server conn, err := net.Dial("tcp", c.host) if err != nil { return fmt.Errorf("Failed to dial '%v' - %v", c.host, err) } // set the connection for the client c.conn = conn // create a new json encoder for the clients connection c.encoder = json.NewEncoder(c.conn) // if the client was created with a token, authentication is needed if c.token != "" { err = c.encoder.Encode(&mist.Message{Command: "auth", Data: c.token}) if err != nil { return fmt.Errorf("Failed to send auth - %v", err) } } // ensure we are authorized/still connected (unauthorized clients get disconnected) c.Ping() decoder := json.NewDecoder(conn) msg := mist.Message{} if err := decoder.Decode(&msg); err != nil { conn.Close() close(c.messages) return fmt.Errorf("Ping failed, possibly bad token, or can't read from mist") } // connection loop (blocking); continually read off the connection. Once something // is read, check to see if it's a message the client understands to be one of // its commands. If so attempt to execute the command. go func() { for { msg := mist.Message{} // decode an array value (Message) if err := decoder.Decode(&msg); err != nil { switch err { case io.EOF: lumber.Debug("[mist client] Mist terminated connection") case io.ErrUnexpectedEOF: lumber.Debug("[mist client] Mist terminated connection unexpedtedly") default: lumber.Error("[mist client] Failed to get message from mist - %s", err.Error()) } conn.Close() close(c.messages) return } c.messages <- msg // read from this using the .Messages() function lumber.Trace("[mist client] Received message - %#v", msg) } }() return nil }