diff --git a/README.md b/README.md index d96dfb2..e0cd37e 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,9 @@ The `-client-tls` flag is not required if your controller certificate is signed Metric name | Description ---- | ---------- +anka_controller_state_count | Status of the Anka Controller (labels: state) +anka_registry_state_count | Status of the Anka Registry (labels: state) +-- | -- anka_instance_state_count | Count of Instances in a particular State (labels: arch, state) anka_instance_state_per_template_count | Count of Instances in a particular state, per Template (labels: state, template_uuid, template_name) anka_instance_state_per_group_count | Count of Instances in a particular state, per Group (labels: state, group_name) diff --git a/src/client/client.go b/src/client/client.go index c34b18b..34722b9 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -35,6 +35,7 @@ func NewClient(addr, username, password string, interval int, certs ClientTLSCer events.EVENT_REGISTRY_DISK_DATA_UPDATED: make([]func(interface{}) error, 0), events.EVENT_VM_DATA_UPDATED: make([]func(interface{}) error, 0), events.EVENT_REGISTRY_TEMPLATES_UPDATED: make([]func(interface{}) error, 0), + events.EVENT_STATUS_UPDATED: make([]func(interface{}) error, 0), }, communicator: communicator, timeoutSeconds: int64(interval), @@ -55,6 +56,7 @@ func (client *Client) Init() { go client.initDataLoop(client.communicator.GetVmsData, events.EVENT_VM_DATA_UPDATED) go client.initDataLoop(client.communicator.GetRegistryDiskData, events.EVENT_REGISTRY_DISK_DATA_UPDATED) go client.initDataLoop(client.communicator.GetRegistryTemplatesData, events.EVENT_REGISTRY_TEMPLATES_UPDATED) + go client.initDataLoop(client.communicator.GetStatus, events.EVENT_STATUS_UPDATED) } func (client *Client) Register(ev events.Event, eventHandler func(interface{}) error) error { diff --git a/src/client/communicator.go b/src/client/communicator.go index dc8b428..5b006b5 100644 --- a/src/client/communicator.go +++ b/src/client/communicator.go @@ -89,6 +89,18 @@ func (comm *Communicator) TestConnection() error { } } +func (comm *Communicator) GetStatus() (interface{}, error) { + lock.Lock() + defer lock.Unlock() + endpoint := "/api/v1/status" + resp := &types.StatusResponse{} + d, err := comm.getData(endpoint, resp) + if err != nil { + return nil, fmt.Errorf("getting status error: %s", err) + } + return d, nil +} + func (comm *Communicator) GetNodesData() (interface{}, error) { lock.Lock() defer lock.Unlock() diff --git a/src/events/events.go b/src/events/events.go index 951e414..4d85ee9 100644 --- a/src/events/events.go +++ b/src/events/events.go @@ -7,4 +7,5 @@ const ( EVENT_REGISTRY_DISK_DATA_UPDATED = 2 EVENT_VM_DATA_UPDATED = 3 EVENT_REGISTRY_TEMPLATES_UPDATED = 4 + EVENT_STATUS_UPDATED = 5 ) diff --git a/src/metrics/status.go b/src/metrics/status.go new file mode 100644 index 0000000..f14ae8b --- /dev/null +++ b/src/metrics/status.go @@ -0,0 +1,69 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/veertuinc/anka-prometheus-exporter/src/events" + "github.com/veertuinc/anka-prometheus-exporter/src/types" +) + +type StatusMetric struct { + BaseAnkaMetric + HandleData func(*types.Status, *prometheus.GaugeVec) +} + +func (sm StatusMetric) GetEventHandler() func(interface{}) error { + return func(d interface{}) error { + status, err := ConvertToStatusData(d) + if err != nil { + return err + } + metric, err := ConvertMetricToGaugeVec(sm.metric) + if err != nil { + return err + } + sm.HandleData( + status, + metric, + ) + return nil + } +} + +var ankaStatusMetrics = []StatusMetric{ + { + BaseAnkaMetric: BaseAnkaMetric{ + metric: CreateGaugeMetricVec("anka_controller_state_count", "Status of the Anka Controller", []string{"state"}), + event: events.EVENT_STATUS_UPDATED, + }, + HandleData: func(status *types.Status, metric *prometheus.GaugeVec) { + for _, state := range types.ControllerStates { + counter := 0 + if status.Status == state { + counter++ + } + metric.With(prometheus.Labels{"state": state}).Set(float64(counter)) + } + }, + }, + { + BaseAnkaMetric: BaseAnkaMetric{ + metric: CreateGaugeMetricVec("anka_registry_state_count", "Status of the Anka Registry", []string{"state"}), + event: events.EVENT_STATUS_UPDATED, + }, + HandleData: func(status *types.Status, metric *prometheus.GaugeVec) { + for _, state := range types.RegistryStates { + counter := 0 + if status.RegistryStatus == state { + counter++ + } + metric.With(prometheus.Labels{"state": state}).Set(float64(counter)) + } + }, + }, +} + +func init() { // runs on exporter init only (updates are made with the above EventHandler; triggered by the Client) + for _, statusMetric := range ankaStatusMetrics { + AddMetric(statusMetric) + } +} diff --git a/src/metrics/utils.go b/src/metrics/utils.go index 2aa0aa5..fd2abde 100644 --- a/src/metrics/utils.go +++ b/src/metrics/utils.go @@ -62,6 +62,14 @@ func CreateGaugeMetricVec(name string, help string, labels []string) *prometheus }, labels) } +func ConvertToStatusData(d interface{}) (*types.Status, error) { + data, ok := d.(types.Status) + if !ok { + return nil, fmt.Errorf("could not convert incoming data to required status information. original data: %v", d) + } + return &data, nil +} + func ConvertToNodeData(d interface{}) ([]types.Node, error) { data, ok := d.([]types.Node) if !ok { diff --git a/src/types/types.go b/src/types/types.go index d934bb8..fea9aa6 100644 --- a/src/types/types.go +++ b/src/types/types.go @@ -1,5 +1,14 @@ package types +var ControllerStates = []string{ + "Running", +} + +var RegistryStates = []string{ + "Running", + "FAIL", +} + var NodeStates = []string{ "Offline", "Inactive (Invalid License)", @@ -25,6 +34,14 @@ var InstanceStates = []string{ "Pushing", } +type Status struct { + Status string `json:"status"` + Version string `json:"version"` + RegistryAddress string `json:"registry_address"` + RegistryStatus string `json:"registry_status"` + License string `json:"license"` +} + type Node struct { NodeID string `json:"node_id"` NodeName string `json:"node_name"` @@ -89,6 +106,15 @@ func (dr *DefaultResponse) GetMessage() string { return dr.Message } +type StatusResponse struct { + DefaultResponse + Body Status `json:"body"` +} + +func (sr *StatusResponse) GetBody() interface{} { + return sr.Body +} + type NodesResponse struct { DefaultResponse Body []Node `json:"body"`