diff --git a/uchiwa/daemon/clients.go b/uchiwa/daemon/clients.go index eb9b5c5..c369149 100644 --- a/uchiwa/daemon/clients.go +++ b/uchiwa/daemon/clients.go @@ -1,16 +1,76 @@ package daemon +import ( + "fmt" + + "github.com/mitchellh/mapstructure" + "github.com/palourde/logger" + "github.com/sensu/uchiwa/uchiwa/structs" +) + // buildClients constructs clients objects for frontend consumption func (d *Daemon) buildClients() { for _, c := range d.Data.Clients { - m := c.(map[string]interface{}) + client := c.(map[string]interface{}) - if m["version"] == nil { - m["version"] = "0.12.x" + if client["version"] == nil { + client["version"] = "0.12.x" } - d.findStatus(m) + client = findClientEvents(client, &d.Data.Events) - m["acknowledged"] = IsAcknowledged(m["name"].(string), "", m["dc"].(string), d.Data.Stashes) + client["acknowledged"] = IsAcknowledged(client["name"].(string), "", client["dc"].(string), d.Data.Stashes) } } + +// 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 { + + var event structs.GenericEvent + err := mapstructure.Decode(e, &event) + if err != nil { + logger.Warningf("Could not convert the event to a generic event structure: %s", err) + continue + } + + // skip this event if not the right client + if event.Client.Name != client["name"] || event.Dc != client["dc"] { + continue + } + + if event.Check.Status == 2 { + criticals++ + } else if event.Check.Status == 1 { + warnings++ + } + + results = append(results, event.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 +} diff --git a/uchiwa/daemon/clients_test.go b/uchiwa/daemon/clients_test.go new file mode 100644 index 0000000..f9a73a6 --- /dev/null +++ b/uchiwa/daemon/clients_test.go @@ -0,0 +1,83 @@ +package daemon + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFindClientEvents(t *testing.T) { + + // no events + client := map[string]interface{}{"dc": "us-east-1", "name": "foo"} + events := []interface{}{nil} + expectedClient := map[string]interface{}{"dc": "us-east-1", "name": "foo", "status": 0} + result := findClientEvents(client, &events) + assert.Equal(t, expectedClient, result) + + var statusFloat = 1.0 + events = []interface{}{ + map[string]interface{}{ + "check": map[string]interface{}{"output": "http_critical", "status": 2}, + "client": map[string]interface{}{"name": "foo"}, + "dc": "us-east-1", + }, + map[string]interface{}{ + "check": map[string]interface{}{"output": "http_warning", "status": statusFloat}, + "client": map[string]interface{}{"name": "bar"}, + "dc": "us-west-1", + }, + } + + // event where status is a float64 + client = map[string]interface{}{"dc": "us-west-1", "name": "bar"} + expectedClient = map[string]interface{}{"dc": "us-west-1", "name": "bar", "output": "http_warning", "status": 1} + result = findClientEvents(client, &events) + assert.Equal(t, expectedClient, result) + + // event where status is an int + client = map[string]interface{}{"dc": "us-east-1", "name": "foo"} + expectedClient = map[string]interface{}{"dc": "us-east-1", "name": "foo", "output": "http_critical", "status": 2} + result = findClientEvents(client, &events) + assert.Equal(t, expectedClient, result) + + // client has no events + client = map[string]interface{}{"dc": "us-east-1", "name": "qux"} + expectedClient = map[string]interface{}{"dc": "us-east-1", "name": "qux", "status": 0} + result = findClientEvents(client, &events) + assert.Equal(t, expectedClient, result) + + // client has multiple events + newEvents := []interface{}{ + map[string]interface{}{ + "check": map[string]interface{}{"output": "http_critical", "status": 2}, + "client": map[string]interface{}{"name": "qux"}, + "dc": "us-east-1", + }, + map[string]interface{}{ + "check": map[string]interface{}{"output": "http_warning", "status": 1}, + "client": map[string]interface{}{"name": "qux"}, + "dc": "us-east-1", + }, + } + events = append(events, newEvents[0]) + events = append(events, newEvents[1]) + + client = map[string]interface{}{"dc": "us-east-1", "name": "qux"} + expectedClient = map[string]interface{}{"dc": "us-east-1", "name": "qux", "output": "http_critical and 1 more...", "status": 2} + result = findClientEvents(client, &events) + assert.Equal(t, expectedClient, result) + + // event with unknown status + newEvent := map[string]interface{}{ + "check": map[string]interface{}{"output": "http_unknown", "status": 3}, + "client": map[string]interface{}{"name": "baz"}, + "dc": "us-west-1", + } + events = append(events, newEvent) + + client = map[string]interface{}{"dc": "us-west-1", "name": "baz"} + expectedClient = map[string]interface{}{"dc": "us-west-1", "name": "baz", "output": "http_unknown", "status": 3} + result = findClientEvents(client, &events) + assert.Equal(t, expectedClient, result) +} diff --git a/uchiwa/daemon/helpers.go b/uchiwa/daemon/helpers.go index 397871d..5260130 100644 --- a/uchiwa/daemon/helpers.go +++ b/uchiwa/daemon/helpers.go @@ -32,70 +32,6 @@ func FindDcFromInterface(data interface{}, datacenters *[]sensu.Sensu) (*sensu.S return nil, nil, fmt.Errorf("Could not find the datacenter %s", id) } -func (d *Daemon) findStatus(client map[string]interface{}) { - if len(d.Data.Events) == 0 { - client["status"] = 0 - } else { - var criticals, warnings int - var results []string - for _, event := range d.Data.Events { - m, ok := event.(map[string]interface{}) - if !ok { - logger.Warningf("Could not assert the event %+v", event) - continue - } - - // skip this event if another dc - if m["dc"] != client["dc"] { - continue - } - - c, ok := m["client"].(map[string]interface{}) - if !ok { - logger.Warningf("Could not assert event's client: %+v", c) - continue - } - - // skip this event if another client - if c["name"] != client["name"] || m["dc"] != client["dc"] { - continue - } - - check := m["check"].(map[string]interface{}) - if !ok { - logger.Warningf("Could not assert event's check interface: %+v", check) - continue - } - - results = append(results, check["output"].(string)) - - status := int(check["status"].(float64)) - if status == 2 { - criticals++ - } else if status == 1 { - warnings++ - } - } - - 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 - } - } -} - // IsAcknowledged ... func IsAcknowledged(client string, check string, dc string, stashes []interface{}) bool { if len(stashes) == 0 { diff --git a/uchiwa/structs/structs.go b/uchiwa/structs/structs.go index d41356d..19c78c2 100644 --- a/uchiwa/structs/structs.go +++ b/uchiwa/structs/structs.go @@ -27,19 +27,23 @@ type Generic struct { // GenericCheck is a structure for holding a generic check type GenericCheck struct { Dc string `json:"dc"` + Output string `json:"output"` + Status int `json:"status"` Subscribers []string `json:"subscribers"` } // GenericClient is a structure for holding a generic client type GenericClient struct { Dc string `json:"dc"` + Name string `json:"name"` Subscriptions []string `json:"subscriptions"` } // GenericEvent is a structure for holding a generic event type GenericEvent struct { - Dc string `json:"dc"` - Check GenericCheck `json:"check"` + Check GenericCheck `json:"check"` + Client GenericClient `json:"client"` + Dc string `json:"dc"` } // Health is a structure for holding health informaton about Sensu & Uchiwa