Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support individual health check endpoints #173

Merged
merged 22 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ RabbitMQ HTTP API has to be [configured to use TLS](http://www.rabbitmq.com/mana
### Getting Overview

``` go
res, err := rmqc.Overview()
resp, err := rmqc.Overview()
```


### Node and Cluster Status

``` go
Expand Down
150 changes: 150 additions & 0 deletions health_checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package rabbithole

import (
"encoding/json"
"net/http"
"strconv"
)

type TimeUnit string

const (
SECONDS TimeUnit = "seconds"
DAYS TimeUnit = "days"
MONTHS TimeUnit = "months"
YEARS TimeUnit = "years"
)

type Protocol string

const (
AMQP091 Protocol = "amqp091"
AMQP10 Protocol = "amqp10"
MQTT Protocol = "mqtt"
STOMP Protocol = "stomp"
WEB_MQTT Protocol = "web-mqtt"
WEB_STOMP Protocol = "web-stomp"
)

type Check interface {
// Returns true if the check is ok, otherwise false
Ok() bool

// Returns true if the check failed, otherwise false
Failed() bool
}

// Represents general response from health check endpoints if no dedicated representation is defined
type Health struct {
Check
Status string `json:"status"`
Reason string `json:"reason"`
}

func (h *Health) Ok() bool {
return h.Status == "ok"
}

func (h *Health) Failed() bool {
return !h.Ok()
}

// Checks if there are alarms in effect in the cluster
func (c *Client) HealthCheckAlarms() (rec Health, err error) {
err = executeCheck(c, "health/checks/alarms", &rec)
return rec, err
}

// Checks if there are local alarms in effect on the target node
func (c *Client) HealthCheckLocalAlarms() (rec Health, err error) {
err = executeCheck(c, "health/checks/local-alarms", &rec)
return rec, err
}

// Checks the expiration date on the certificates for every listener configured to use TLS.
// Valid units: days, weeks, months, years. The value of the within argument is the number of units.
// So, when within is 2 and unit is "months", the expiration period used by the check will be the next two months.
func (c *Client) HealthCheckCertificateExpiration(within uint, unit TimeUnit) (rec Health, err error) {
err = executeCheck(c, "health/checks/certificate-expiration/"+strconv.Itoa(int(within))+"/"+string(unit), &rec)
return rec, err
}

// Represents the response from HealthCheckPortListener
type PortListenerHealth struct {
Check
Status string `json:"status"`
Reason string `json:"reason"`
Missing string `json:"missing"`
Port uint `json:"port"`
Ports []uint `json:"ports"`
}

// Checks if there is an active listener on the give port
func (c *Client) HealthCheckPortListener(port uint) (rec PortListenerHealth, err error) {
err = executeCheck(c, "health/checks/port-listener/"+strconv.Itoa(int(port)), &rec)
return rec, err
}

// Represents the response from HealthCheckProtocolListener
type ProtocolListenerHealth struct {
Check
Status string `json:"status"`
Reason string `json:"reason"`
Missing string `json:"missing"`
Protocols []string `json:"protocols"`
}

// Checks if there is an active listener for the given protocol
// Valid protocol names are: amqp091, amqp10, mqtt, stomp, web-mqtt, web-stomp.
func (c *Client) HealthCheckProtocolListener(protocol Protocol) (rec ProtocolListenerHealth, err error) {
err = executeCheck(c, "health/checks/protocol-listener/"+string(protocol), &rec)
return rec, err
}

// Checks if all virtual hosts and running on the target node
func (c *Client) HealthCheckVirtualHosts() (rec Health, err error) {
err = executeCheck(c, "health/checks/virtual-hosts", &rec)
return rec, err
}

// Checks if there are classic mirrored queues without synchronised mirrors online (queues that would potentially lose data if the target node is shut down).
func (c *Client) HealthCheckNodeIsMirrorSyncCritical() (rec Health, err error) {
err = executeCheck(c, "health/checks/node-is-mirror-sync-critical", &rec)
return rec, err
}

// Checks if there are quorum queues with minimum online quorum (queues that would lose their quorum and availability if the target node is shut down).
func (c *Client) HealthCheckNodeIsQuorumCritical() (rec Health, err error) {
err = executeCheck(c, "health/checks/node-is-quorum-critical", &rec)
return rec, err
}

func executeCheck(client *Client, path string, rec interface{}) error {
req, err := newGETRequest(client, path)
httpc := &http.Client{
Timeout: client.timeout,
}
if client.transport != nil {
httpc.Transport = client.transport
}
resp, err := httpc.Do(req)
if err != nil {
return err
}

defer resp.Body.Close()

if resp.StatusCode < http.StatusBadRequest || resp.StatusCode == http.StatusServiceUnavailable {
if err = json.NewDecoder(resp.Body).Decode(&rec); err != nil {
return err
}

return nil
}

if err = parseResponseErrors(resp); err != nil {
return err
}

return nil
}
128 changes: 128 additions & 0 deletions rabbithole_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,134 @@ var _ = Describe("Rabbithole", func() {
rmqc, _ = NewClient("http://127.0.0.1:15672", "guest", "guest")
})

Context("GET /health/checks/alarms", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckAlarms()
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /health/checks/local-alarms", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckLocalAlarms()
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /health/checks/certificate-expiration/1/days", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckCertificateExpiration(1, DAYS)
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /health/checks/port-listener/5672", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckPortListener(5672)
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /health/checks/protocol-listener/amqp091", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckProtocolListener(AMQP091)
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /health/checks/virtual-hosts", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckVirtualHosts()
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /health/checks/node-is-mirror-sync-critical", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckNodeIsMirrorSyncCritical()
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /health/checks/node-is-quorum-critical", func() {
It("returns decoded response", func() {
conn := openConnection("/")
defer conn.Close()

ch, err := conn.Channel()
Ω(err).Should(BeNil())
defer ch.Close()

res, err := rmqc.HealthCheckNodeIsMirrorSyncCritical()
Ω(err).Should(BeNil())

Ω(res.Status).Should(Equal("ok"))
})
})

Context("GET /overview", func() {
It("returns decoded response", func() {
conn := openConnection("/")
Expand Down