// BuildEventsMetrics builds the metrics for the events func BuildEventsMetrics(events *[]interface{}) *structs.StatusMetrics { metrics := structs.StatusMetrics{} metrics.Total = len(*events) for _, e := range *events { event := e.(map[string]interface{}) check, ok := event["check"].(map[string]interface{}) if !ok { logger.Warningf("Could not assert this check to an interface: %+v", event["check"]) continue } status, ok := check["status"].(float64) if !ok { logger.Warningf("Could not assert this status to a flot64: %+v", check["status"]) continue } if status == 2.0 { metrics.Critical++ continue } else if status == 1.0 { metrics.Warning++ continue } metrics.Unknown++ } return &metrics }
func initSensu(apis []SensuConfig) []SensuConfig { for i, api := range apis { // Set a datacenter name if missing if api.Name == "" { logger.Warningf("Sensu API %s has no name property, make sure to set it in your configuration. Generating a temporary one...", api.URL) apis[i].Name = fmt.Sprintf("sensu-%v", rand.Intn(100)) } // Escape special characters in DC name r := strings.NewReplacer(":", "", "/", "", ";", "", "?", "") apis[i].Name = r.Replace(apis[i].Name) // Make sure the host is not empty if api.Host == "" { logger.Fatalf("Sensu API %q Host is missing", api.Name) } // Determine the protocol to use prot := "http" if api.Ssl { prot += "s" } // Set the API URL apis[i].URL = fmt.Sprintf("%s://%s:%d%s", prot, api.Host, api.Port, api.Path) } return apis }
// BuildClientsMetrics builds the metrics for the events func BuildClientsMetrics(clients *[]interface{}) *structs.StatusMetrics { metrics := structs.StatusMetrics{} metrics.Total = len(*clients) for _, c := range *clients { client := c.(map[string]interface{}) status, ok := client["status"].(int) if !ok { logger.Warningf("Could not assert this status to an int: %+v", client["status"]) continue } if status == 2.0 { metrics.Critical++ continue } else if status == 1.0 { metrics.Warning++ continue } else if status == 0.0 { continue } metrics.Unknown++ } return &metrics }
func (c *Config) initSensu() { for i, api := range c.Sensu { prot := "http" if api.Name == "" { logger.Warningf("Sensu API %s has no name property. Generating random one...", api.URL) c.Sensu[i].Name = fmt.Sprintf("sensu-%v", rand.Intn(100)) } // escape special characters in DC name r := strings.NewReplacer(":", "", "/", "", ";", "", "?", "") c.Sensu[i].Name = r.Replace(api.Name) if api.Host == "" { logger.Fatalf("Sensu API %q Host is missing", api.Name) } if api.Timeout == 0 { c.Sensu[i].Timeout = 10 } else if api.Timeout >= 1000 { // backward compatibility with < 0.3.0 version c.Sensu[i].Timeout = api.Timeout / 1000 } if api.Port == 0 { c.Sensu[i].Port = 4567 } if api.Ssl { prot += "s" } c.Sensu[i].URL = fmt.Sprintf("%s://%s:%d%s", prot, api.Host, c.Sensu[i].Port, api.Path) } }
// WebServer starts the web server and serves GET & POST requests func (u *Uchiwa) WebServer(publicPath *string, auth auth.Config) { // Private endpoints http.Handle("/aggregates", auth.Authenticate(http.HandlerFunc(u.aggregatesHandler))) http.Handle("/aggregates/", auth.Authenticate(http.HandlerFunc(u.aggregatesHandler))) http.Handle("/checks", auth.Authenticate(http.HandlerFunc(u.checksHandler))) http.Handle("/clients", auth.Authenticate(http.HandlerFunc(u.clientsHandler))) http.Handle("/clients/", auth.Authenticate(http.HandlerFunc(u.clientsHandler))) http.Handle("/config", auth.Authenticate(http.HandlerFunc(u.configHandler))) http.Handle("/datacenters", auth.Authenticate(http.HandlerFunc(u.datacentersHandler))) http.Handle("/events", auth.Authenticate(http.HandlerFunc(u.eventsHandler))) http.Handle("/events/", auth.Authenticate(http.HandlerFunc(u.eventsHandler))) http.Handle("/request", auth.Authenticate(http.HandlerFunc(u.requestHandler))) http.Handle("/results/", auth.Authenticate(http.HandlerFunc(u.resultsHandler))) http.Handle("/stashes", auth.Authenticate(http.HandlerFunc(u.stashesHandler))) http.Handle("/stashes/", auth.Authenticate(http.HandlerFunc(u.stashesHandler))) http.Handle("/subscriptions", auth.Authenticate(http.HandlerFunc(u.subscriptionsHandler))) if u.Config.Uchiwa.Enterprise == false { http.Handle("/metrics", auth.Authenticate(http.HandlerFunc(u.metricsHandler))) } // Static files http.Handle("/", http.FileServer(http.Dir(*publicPath))) // Public endpoints http.Handle("/config/", http.HandlerFunc(u.configHandler)) http.Handle("/health", http.HandlerFunc(u.healthHandler)) http.Handle("/health/", http.HandlerFunc(u.healthHandler)) http.Handle("/login", auth.GetIdentification()) listen := fmt.Sprintf("%s:%d", u.Config.Uchiwa.Host, u.Config.Uchiwa.Port) logger.Warningf("Uchiwa is now listening on %s", listen) logger.Fatal(http.ListenAndServe(listen, nil)) }
func setDc(v interface{}, dc string) { m, ok := v.(map[string]interface{}) if !ok { logger.Warningf("Could not assert interface: %+v", v) } else { m["dc"] = dc } }
func (u *Uchiwa) buildClientHistory(client, dc string, history []interface{}) []interface{} { for _, h := range history { m, ok := h.(map[string]interface{}) if !ok { logger.Warningf("Could not assert this client history to an interface: %+v", h) continue } // Set some attributes for easier frontend consumption check, ok := m["check"].(string) if !ok { continue } m["client"] = client m["dc"] = dc m["acknowledged"] = helpers.IsAcknowledged(check, client, dc, u.Data.Stashes) // Add missing attributes to last_result object if m["last_result"] != nil { if m["last_status"] == 0.0 { continue } event, err := helpers.GetEvent(check, client, dc, &u.Data.Events) if err != nil { continue } lastResult, ok := m["last_result"].(map[string]interface{}) if !ok { continue } if event["action"] != nil { lastResult["action"] = event["action"] } if event["occurrences"] != nil { lastResult["occurrences"] = event["occurrences"] } } // Maintain backward compatiblity with Sensu <= 0.17 // by constructing the last_result object if m["last_status"] != nil && m["last_status"] != 0.0 { event, err := helpers.GetEvent(check, client, dc, &u.Data.Events) if err != nil { continue } m["last_result"] = event } else { m["last_result"] = map[string]interface{}{"last_execution": m["last_execution"], "status": m["last_status"]} } } return history }
func (u *Uchiwa) findOutput(id *string, h map[string]interface{}, dc *string) string { if h["last_status"] == 0 { return "" } for _, e := range u.Data.Events { // does the dc match? m, ok := e.(map[string]interface{}) if !ok { logger.Warningf("Could not assert this event to an interface %+v", e) continue } if m["dc"] != *dc { continue } // does the client match? c, ok := m["client"].(map[string]interface{}) if !ok { logger.Warningf("Could not assert this client to an interface: %+v", c) continue } if c["name"] != *id { continue } // does the check match? k := m["check"].(map[string]interface{}) if !ok { logger.Warningf("Could not assert this check to an interface: %+v", k) continue } if k["name"] != h["check"] { continue } return k["output"].(string) } return "" }
// FindDcFromInterface ... func FindDcFromInterface(data interface{}, datacenters *[]sensu.Sensu) (*sensu.Sensu, map[string]interface{}, error) { m, ok := data.(map[string]interface{}) if !ok { logger.Warningf("Type assertion failed. Could not assert the given interface into a map: %+v", data) return nil, nil, errors.New("Could not determine the datacenter.") } id := m["dc"].(string) if id == "" { logger.Warningf("The received interface does not contain any datacenter information: ", data) return nil, nil, errors.New("Could not determine the datacenter.") } for _, dc := range *datacenters { if dc.Name == id { return &dc, m, nil } } logger.Warningf("Could not find the datacenter %s into %+v: ", id, data) return nil, nil, fmt.Errorf("Could not find the datacenter %s", id) }
func findModel(id string, dc string, checks []interface{}) map[string]interface{} { for _, k := range checks { m, ok := k.(map[string]interface{}) if !ok { logger.Warningf("Could not assert check interface %+v", k) continue } if m["name"] == id && m["dc"] == dc { return m } } return nil }
func (u *Uchiwa) findClientInClients(id *string, dc *string) (map[string]interface{}, error) { for _, c := range u.Data.Clients { m, ok := c.(map[string]interface{}) if !ok { logger.Warningf("Could not assert this client to an interface %+v", c) continue } if m["name"] == *id && m["dc"] == *dc { return m, nil } } return nil, fmt.Errorf("Could not find client %s", *id) }
func (s *Sensu) getSlice(endpoint string, limit int) ([]interface{}, error) { apis := shuffle(s.APIs) for i := 0; i < len(apis); i++ { logger.Debugf("GET %s/%s", s.APIs[i].URL, endpoint) slice, err := apis[i].getSlice(endpoint, limit) if err == nil { return slice, err } logger.Warningf("GET %s/%s returned: %v", s.APIs[i].URL, endpoint, err) } return nil, errors.New("") }
func (s *Sensu) postPayload(endpoint string, payload string) (map[string]interface{}, error) { apis := shuffle(s.APIs) for i := 0; i < len(apis); i++ { logger.Debugf("POST %s/%s", s.APIs[i].URL, endpoint) m, err := apis[i].postPayload(endpoint, payload) if err == nil { return m, err } logger.Warningf("POST %s/%s returned: %v", s.APIs[i].URL, endpoint, err) } return nil, errors.New("") }
func (s *Sensu) getBytes(endpoint string) ([]byte, *http.Response, error) { apis := shuffle(s.APIs) for i := 0; i < len(apis); i++ { logger.Debugf("GET %s/%s", s.APIs[i].URL, endpoint) bytes, res, err := apis[i].getBytes(endpoint) if err == nil { return bytes, res, err } logger.Warningf("GET %s/%s returned: %v", s.APIs[i].URL, endpoint, err) } return nil, nil, errors.New("") }
func (s *Sensu) delete(endpoint string) error { apis := shuffle(s.APIs) var err error for i := 0; i < len(apis); i++ { logger.Infof("DELETE %s/%s", s.APIs[i].URL, endpoint) err = apis[i].delete(endpoint) if err == nil { return err } logger.Warningf("DELETE %s/%s returned: %v", s.APIs[i].URL, endpoint, err) } return err }
// loadFile loads a Config struct from a configuration file func loadFile(path string) (*Config, error) { logger.Warningf("Loading the configuration file %s", path) c := new(Config) file, err := os.Open(path) if err != nil { if len(path) > 1 { return nil, fmt.Errorf("Error: could not read config file %s.", path) } } decoder := json.NewDecoder(file) err = decoder.Decode(c) if err != nil { return nil, fmt.Errorf("Error decoding file %s: %s", path, err) } return c, nil }
// Stashes func (u *Uchiwa) stashesHandler(w http.ResponseWriter, r *http.Request) { token := auth.GetTokenFromContext(r) resources := strings.Split(r.URL.Path, "/") // DELETE on /stashes/{dc}/{path} if r.Method == "DELETE" && len(resources) >= 3 { dc := resources[2] path := strings.Join(resources[3:], "/") if dc == "" || path == "" { http.Error(w, "", http.StatusBadRequest) return } unauthorized := FilterGetRequest(dc, token) if unauthorized { http.Error(w, fmt.Sprint(""), http.StatusNotFound) return } err := u.DeleteStash(dc, path) if err != nil { logger.Warningf("Could not delete the stash '%s': %s", path, err) http.Error(w, "Could not create the stash", http.StatusNotFound) return } } else if r.Method == "GET" { // GET on /stashes stashes := FilterStashes(&u.Data.Stashes, token) // Create header w.Header().Add("Accept-Charset", "utf-8") w.Header().Add("Content-Type", "application/json") w.Header().Set("Content-Encoding", "gzip") gz := gzip.NewWriter(w) defer gz.Close() if err := json.NewEncoder(gz).Encode(stashes); err != nil { http.Error(w, fmt.Sprintf("Cannot encode response data: %v", err), http.StatusInternalServerError) return } return } else if r.Method == "POST" { // POST on /stashes decoder := json.NewDecoder(r.Body) var data stash err := decoder.Decode(&data) if err != nil { http.Error(w, "Could not decode body", http.StatusInternalServerError) return } // verify that the authenticated user is authorized to access this resource unauthorized := FilterGetRequest(data.Dc, token) if unauthorized { http.Error(w, fmt.Sprint(""), http.StatusNotFound) return } if token != nil && token.Claims["Username"] != nil { data.Content["username"] = token.Claims["Username"] } err = u.PostStash(data) if err != nil { http.Error(w, "Could not create the stash", http.StatusNotFound) return } } else { http.Error(w, "", http.StatusBadRequest) return } }
// GetIdentification retrieves the user & pass from a POST and authenticates the user against the Identification driver func (a *Config) GetIdentification() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Redirect(w, r, "/#/login", http.StatusFound) return } decoder := json.NewDecoder(r.Body) var data interface{} err := decoder.Decode(&data) if err != nil { logger.Warningf("Could not decode the body: %s", err) http.Error(w, "", http.StatusInternalServerError) return } m, ok := data.(map[string]interface{}) if !ok { logger.Warningf("Could not assert the body: %s", err) http.Error(w, "", http.StatusInternalServerError) return } u := m["user"].(string) p := m["pass"].(string) if u == "" || p == "" { logger.Info("Authentication failed: user and password must not be empty") http.Error(w, "", http.StatusUnauthorized) return } // validate the user with the Login authentication driver user, err := a.Driver(u, p) if err != nil { message := fmt.Sprintf("Authentication failed: %s", err) // Output to stdout logger.Info(message) // Output to audit log log := structs.AuditLog{Action: "loginfailure", Level: "default", Output: message} log.RemoteAddr = helpers.GetIP(r) audit.Log(log) http.Error(w, "", http.StatusUnauthorized) return } // obfuscate the user's salt & hash user.PasswordHash = "" user.PasswordSalt = "" token, err := GetToken(&user.Role, u) if err != nil { logger.Warningf("Authentication failed, could not create the token: %s", err) http.Error(w, "", http.StatusInternalServerError) return } // Add token to the user struct user.Token = token j, err := json.Marshal(user) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(j) return }) }
// BuildEvents constructs events objects for frontend consumption func (d *Daemon) buildEvents() { for _, e := range d.Data.Events { m := e.(map[string]interface{}) // build backward compatible event object for Sensu < 0.13.0 if m["id"] == nil { // build client object c := m["client"] delete(m, "client") m["client"] = map[string]interface{}{"name": c} // build check object c = m["check"] delete(m, "check") m["check"] = map[string]interface{}{"name": c, "issued": m["issued"], "output": m["output"], "status": m["status"]} // is flapping? if m["action"] == false { m["action"] = "create" } else { m["action"] = "flapping" } // remove old entries delete(m, "issued") delete(m, "output") delete(m, "status") } // we assume the event isn't acknowledged in case we can't assert the following values m["acknowledged"] = false // get client name c, ok := m["client"].(map[string]interface{}) if !ok { logger.Warningf("Could not assert event's client interface from %+v", c) continue } clientName, ok := c["name"].(string) if !ok { logger.Warningf("Could not assert event's client name from %+v", c) continue } // get check name k, ok := m["check"].(map[string]interface{}) if !ok { logger.Warningf("Could not assert event's check interface from %+v", k) continue } checkName, ok := k["name"].(string) if !ok { logger.Warningf("Could not assert event's check name from %+v", k) continue } // get dc name dcName, ok := m["dc"].(string) if !ok { logger.Warningf("Could not assert event's datacenter name from %+v", m) continue } // determine if the event is acknowledged m["acknowledged"] = IsAcknowledged(clientName, checkName, dcName, d.Data.Stashes) // detertermine if the client is acknowledged m["client"].(map[string]interface{})["acknowledged"] = IsAcknowledged(clientName, "", dcName, d.Data.Stashes) } }
// getData retrieves all endpoints for every datacenter func (d *Daemon) fetchData() { d.Data.Health.Sensu = make(map[string]structs.SensuHealth, len(*d.Datacenters)) for _, datacenter := range *d.Datacenters { logger.Infof("Updating the datacenter %s", datacenter.Name) // set default health status d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: datacenterErrorString, Status: 2} d.Data.Health.Uchiwa = "ok" // fetch sensu data from the datacenter stashes, err := datacenter.GetStashes() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } checks, err := datacenter.GetChecks() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } clients, err := datacenter.GetClients() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } events, err := datacenter.GetEvents() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } info, err := datacenter.GetInfo() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } aggregates, err := datacenter.GetAggregates() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } if d.Enterprise { d.Data.SERawMetrics = *getEnterpriseMetrics(&datacenter, &d.Data.SERawMetrics) } // Determine the status of the datacenter if !info.Redis.Connected { d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: "Not connected to Redis", Status: 1} } else if !info.Transport.Connected { d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: "Not connected to the transport", Status: 1} } else { d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: "ok", Status: 0} } // add fetched data into d.Data interface for _, v := range stashes { setDc(v, datacenter.Name) d.Data.Stashes = append(d.Data.Stashes, v) } for _, v := range checks { setDc(v, datacenter.Name) d.Data.Checks = append(d.Data.Checks, v) } for _, v := range clients { setDc(v, datacenter.Name) d.Data.Clients = append(d.Data.Clients, v) } for _, v := range events { setDc(v, datacenter.Name) d.Data.Events = append(d.Data.Events, v) } for _, v := range aggregates { setDc(v, datacenter.Name) d.Data.Aggregates = append(d.Data.Aggregates, v) } // build datacenter dc := d.buildDatacenter(&datacenter.Name, info) dc.Stats["aggregates"] = len(aggregates) dc.Stats["checks"] = len(checks) dc.Stats["clients"] = len(clients) dc.Stats["events"] = len(events) dc.Stats["stashes"] = len(stashes) d.Data.Dc = append(d.Data.Dc, dc) } }
// getData retrieves all endpoints for every datacenter func (d *Daemon) fetchData() { d.Data.Health.Sensu = make(map[string]structs.SensuHealth, len(*d.Datacenters)) for _, datacenter := range *d.Datacenters { logger.Infof("Updating the datacenter %s", datacenter.Name) // set default health status d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: datacenterErrorString, Status: 2} d.Data.Health.Uchiwa = "ok" // fetch sensu data from the datacenter stashes, err := datacenter.GetStashes() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } checks, err := datacenter.GetChecks() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } clients, err := datacenter.GetClients() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } events, err := datacenter.GetEvents() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } info, err := datacenter.GetInfo() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } aggregates, err := datacenter.GetAggregates() if err != nil { logger.Warningf("Connection failed to the datacenter %s", datacenter.Name) continue } if d.Enterprise { hasMetrics := true metrics := make(map[string]*structs.SERawMetric) metricsEndpoints := []string{"clients", "events", "keepalives_avg_60", "check_requests", "results"} for _, metric := range metricsEndpoints { metrics[metric], err = datacenter.Metric(metric) if err != nil { logger.Warningf("Could not retrieve the %s metrics. Discarding all metrics for the datacenter %s", metric, datacenter.Name) hasMetrics = false break } } if hasMetrics { metrics["events"].Name = datacenter.Name d.Data.SERawMetrics.Clients = append(d.Data.SERawMetrics.Clients, metrics["clients"]) d.Data.SERawMetrics.Events = append(d.Data.SERawMetrics.Events, metrics["events"]) d.Data.SERawMetrics.KeepalivesAVG60 = append(d.Data.SERawMetrics.KeepalivesAVG60, metrics["keepalives_avg_60"]) d.Data.SERawMetrics.Requests = append(d.Data.SERawMetrics.Requests, metrics["check_requests"]) d.Data.SERawMetrics.Results = append(d.Data.SERawMetrics.Results, metrics["results"]) } } // Determine the status of the datacenter if !info.Redis.Connected { d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: "Not connected to Redis", Status: 1} } else if !info.Transport.Connected { d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: "Not connected to the transport", Status: 1} } else { d.Data.Health.Sensu[datacenter.Name] = structs.SensuHealth{Output: "ok", Status: 0} } // add fetched data into d.Data interface for _, v := range stashes { setDc(v, datacenter.Name) d.Data.Stashes = append(d.Data.Stashes, v) } for _, v := range checks { setDc(v, datacenter.Name) d.Data.Checks = append(d.Data.Checks, v) } for _, v := range clients { setDc(v, datacenter.Name) d.Data.Clients = append(d.Data.Clients, v) } for _, v := range events { setDc(v, datacenter.Name) d.Data.Events = append(d.Data.Events, v) } for _, v := range aggregates { setDc(v, datacenter.Name) d.Data.Aggregates = append(d.Data.Aggregates, v) } // build datacenter dc := d.buildDatacenter(&datacenter.Name, info) dc.Stats["aggregates"] = len(aggregates) dc.Stats["checks"] = len(checks) dc.Stats["clients"] = len(clients) dc.Stats["events"] = len(events) dc.Stats["stashes"] = len(stashes) d.Data.Dc = append(d.Data.Dc, dc) } }
// findClientEvents searches for all events related to a particular client // and set the status and output attributes of this client based on the events found func findClientEvents(client map[string]interface{}, events *[]interface{}) map[string]interface{} { if len(*events) == 0 { client["status"] = 0 } else { var criticals, warnings int var results []string for _, e := range *events { eventMap, ok := e.(map[string]interface{}) if !ok { logger.Warningf("Could not convert the event to a map: %+v", eventMap) continue } // skip this event if the check attribute does not exist if eventMap["check"] == nil { continue } // skip this event if the datacenter isn't the right one if eventMap["dc"] == nil || eventMap["dc"] != client["dc"] { continue } clientMap, ok := eventMap["client"].(map[string]interface{}) if !ok { logger.Warningf("Could not convert the event's client to a map: %+v", eventMap) continue } // skip this event if the client isn't the right one if clientMap["name"] == nil || clientMap["name"] != client["name"] { continue } // convert the check to a structure for easier handling var check structs.GenericCheck err := mapstructure.Decode(eventMap["check"], &check) if err != nil { logger.Warningf("Could not convert the event's check to a generic check structure: %s", err) continue } if check.Status == 2 { criticals++ } else if check.Status == 1 { warnings++ } results = append(results, check.Output) } if len(results) == 0 { client["status"] = 0 } else if criticals > 0 { client["status"] = 2 } else if warnings > 0 { client["status"] = 1 } else { client["status"] = 3 } if len(results) == 1 { client["output"] = results[0] } else if len(results) > 1 { output := fmt.Sprintf("%s and %d more...", results[0], (len(results) - 1)) client["output"] = output } } return client }